Implement roaring of Lion
[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 void
7118 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7119 {
7120     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7121     Markers *m = (Markers *) closure;
7122     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7123         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7124                          || kind == WhiteCapturesEnPassant
7125                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7126     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7127 }
7128
7129 void
7130 MarkTargetSquares (int clear)
7131 {
7132   int x, y, sum=0;
7133   if(clear) { // no reason to ever suppress clearing
7134     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7135     if(!sum) return; // nothing was cleared,no redraw needed
7136   } else {
7137     int capt = 0;
7138     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7139        !appData.testLegality || gameMode == EditPosition) return;
7140     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7141     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7142       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7143       if(capt)
7144       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7145     }
7146   }
7147   DrawPosition(FALSE, NULL);
7148 }
7149
7150 int
7151 Explode (Board board, int fromX, int fromY, int toX, int toY)
7152 {
7153     if(gameInfo.variant == VariantAtomic &&
7154        (board[toY][toX] != EmptySquare ||                     // capture?
7155         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7156                          board[fromY][fromX] == BlackPawn   )
7157       )) {
7158         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7159         return TRUE;
7160     }
7161     return FALSE;
7162 }
7163
7164 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7165
7166 int
7167 CanPromote (ChessSquare piece, int y)
7168 {
7169         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7170         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7171         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7172            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7173            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7174          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7175         return (piece == BlackPawn && y == 1 ||
7176                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7177                 piece == BlackLance && y == 1 ||
7178                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7179 }
7180
7181 void
7182 HoverEvent (int xPix, int yPix, int x, int y)
7183 {
7184         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7185         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7186         int r, f;
7187         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7188         if(!first.highlight) return;
7189         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7190         if(x == oldX && y == oldY) return; // only do something if we enter new square
7191         oldFromX = fromX; oldFromY = fromY;
7192         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7193           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7194             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7195         else if(oldX != x || oldY != y) {
7196           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7197           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7198             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7199           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7200             char buf[MSG_SIZ];
7201             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7202             SendToProgram(buf, &first);
7203           }
7204           oldX = x; oldY = y;
7205 //        SetHighlights(fromX, fromY, x, y);
7206         }
7207 }
7208
7209 void ReportClick(char *action, int x, int y)
7210 {
7211         char buf[MSG_SIZ]; // Inform engine of what user does
7212         int r, f;
7213         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7214           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7215         if(!first.highlight || gameMode == EditPosition) return;
7216         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7217         SendToProgram(buf, &first);
7218 }
7219
7220 void
7221 LeftClick (ClickType clickType, int xPix, int yPix)
7222 {
7223     int x, y;
7224     Boolean saveAnimate;
7225     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7226     char promoChoice = NULLCHAR;
7227     ChessSquare piece;
7228     static TimeMark lastClickTime, prevClickTime;
7229
7230     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7231
7232     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7233
7234     if (clickType == Press) ErrorPopDown();
7235     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7236
7237     x = EventToSquare(xPix, BOARD_WIDTH);
7238     y = EventToSquare(yPix, BOARD_HEIGHT);
7239     if (!flipView && y >= 0) {
7240         y = BOARD_HEIGHT - 1 - y;
7241     }
7242     if (flipView && x >= 0) {
7243         x = BOARD_WIDTH - 1 - x;
7244     }
7245
7246     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7247         defaultPromoChoice = promoSweep;
7248         promoSweep = EmptySquare;   // terminate sweep
7249         promoDefaultAltered = TRUE;
7250         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7251     }
7252
7253     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7254         if(clickType == Release) return; // ignore upclick of click-click destination
7255         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7256         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7257         if(gameInfo.holdingsWidth &&
7258                 (WhiteOnMove(currentMove)
7259                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7260                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7261             // click in right holdings, for determining promotion piece
7262             ChessSquare p = boards[currentMove][y][x];
7263             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7264             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7265             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7266                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7267                 fromX = fromY = -1;
7268                 return;
7269             }
7270         }
7271         DrawPosition(FALSE, boards[currentMove]);
7272         return;
7273     }
7274
7275     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7276     if(clickType == Press
7277             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7278               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7279               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7280         return;
7281
7282     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7283         // could be static click on premove from-square: abort premove
7284         gotPremove = 0;
7285         ClearPremoveHighlights();
7286     }
7287
7288     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7289         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7290
7291     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7292         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7293                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7294         defaultPromoChoice = DefaultPromoChoice(side);
7295     }
7296
7297     autoQueen = appData.alwaysPromoteToQueen;
7298
7299     if (fromX == -1) {
7300       int originalY = y;
7301       gatingPiece = EmptySquare;
7302       if (clickType != Press) {
7303         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7304             DragPieceEnd(xPix, yPix); dragging = 0;
7305             DrawPosition(FALSE, NULL);
7306         }
7307         return;
7308       }
7309       doubleClick = FALSE;
7310       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7311         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7312       }
7313       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7314       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7315          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7316          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7317             /* First square */
7318             if (OKToStartUserMove(fromX, fromY)) {
7319                 second = 0;
7320                 ReportClick("lift", x, y);
7321                 MarkTargetSquares(0);
7322                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7323                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7324                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7325                     promoSweep = defaultPromoChoice;
7326                     selectFlag = 0; lastX = xPix; lastY = yPix;
7327                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7328                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7329                 }
7330                 if (appData.highlightDragging) {
7331                     SetHighlights(fromX, fromY, -1, -1);
7332                 } else {
7333                     ClearHighlights();
7334                 }
7335             } else fromX = fromY = -1;
7336             return;
7337         }
7338     }
7339
7340     /* fromX != -1 */
7341     if (clickType == Press && gameMode != EditPosition) {
7342         ChessSquare fromP;
7343         ChessSquare toP;
7344         int frc;
7345
7346         // ignore off-board to clicks
7347         if(y < 0 || x < 0) return;
7348
7349         /* Check if clicking again on the same color piece */
7350         fromP = boards[currentMove][fromY][fromX];
7351         toP = boards[currentMove][y][x];
7352         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7353         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7354            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7355              WhitePawn <= toP && toP <= WhiteKing &&
7356              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7357              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7358             (BlackPawn <= fromP && fromP <= BlackKing &&
7359              BlackPawn <= toP && toP <= BlackKing &&
7360              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7361              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7362             /* Clicked again on same color piece -- changed his mind */
7363             second = (x == fromX && y == fromY);
7364             killX = killY = -1;
7365             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7366                 second = FALSE; // first double-click rather than scond click
7367                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7368             }
7369             promoDefaultAltered = FALSE;
7370             MarkTargetSquares(1);
7371            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7372             if (appData.highlightDragging) {
7373                 SetHighlights(x, y, -1, -1);
7374             } else {
7375                 ClearHighlights();
7376             }
7377             if (OKToStartUserMove(x, y)) {
7378                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7379                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7380                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7381                  gatingPiece = boards[currentMove][fromY][fromX];
7382                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7383                 fromX = x;
7384                 fromY = y; dragging = 1;
7385                 ReportClick("lift", x, y);
7386                 MarkTargetSquares(0);
7387                 DragPieceBegin(xPix, yPix, FALSE);
7388                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7389                     promoSweep = defaultPromoChoice;
7390                     selectFlag = 0; lastX = xPix; lastY = yPix;
7391                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7392                 }
7393             }
7394            }
7395            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7396            second = FALSE;
7397         }
7398         // ignore clicks on holdings
7399         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7400     }
7401
7402     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7403         DragPieceEnd(xPix, yPix); dragging = 0;
7404         if(clearFlag) {
7405             // a deferred attempt to click-click move an empty square on top of a piece
7406             boards[currentMove][y][x] = EmptySquare;
7407             ClearHighlights();
7408             DrawPosition(FALSE, boards[currentMove]);
7409             fromX = fromY = -1; clearFlag = 0;
7410             return;
7411         }
7412         if (appData.animateDragging) {
7413             /* Undo animation damage if any */
7414             DrawPosition(FALSE, NULL);
7415         }
7416         if (second || sweepSelecting) {
7417             /* Second up/down in same square; just abort move */
7418             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7419             second = sweepSelecting = 0;
7420             fromX = fromY = -1;
7421             gatingPiece = EmptySquare;
7422             MarkTargetSquares(1);
7423             ClearHighlights();
7424             gotPremove = 0;
7425             ClearPremoveHighlights();
7426         } else {
7427             /* First upclick in same square; start click-click mode */
7428             SetHighlights(x, y, -1, -1);
7429         }
7430         return;
7431     }
7432
7433     clearFlag = 0;
7434
7435     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7436         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7437         DisplayMessage(_("only marked squares are legal"),"");
7438         DrawPosition(TRUE, NULL);
7439         return; // ignore to-click
7440     }
7441
7442     /* we now have a different from- and (possibly off-board) to-square */
7443     /* Completed move */
7444     if(!sweepSelecting) {
7445         toX = x;
7446         toY = y;
7447     }
7448
7449     saveAnimate = appData.animate;
7450     if (clickType == Press) {
7451         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7452             // must be Edit Position mode with empty-square selected
7453             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7454             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7455             return;
7456         }
7457         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7458             dragging = 1;
7459             return;
7460         }
7461         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7462             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7463         } else
7464         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7465         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7466           if(appData.sweepSelect) {
7467             ChessSquare piece = boards[currentMove][fromY][fromX];
7468             promoSweep = defaultPromoChoice;
7469             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7470             selectFlag = 0; lastX = xPix; lastY = yPix;
7471             Sweep(0); // Pawn that is going to promote: preview promotion piece
7472             sweepSelecting = 1;
7473             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7474             MarkTargetSquares(1);
7475           }
7476           return; // promo popup appears on up-click
7477         }
7478         /* Finish clickclick move */
7479         if (appData.animate || appData.highlightLastMove) {
7480             SetHighlights(fromX, fromY, toX, toY);
7481         } else {
7482             ClearHighlights();
7483         }
7484     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7485         sweepSelecting = 0;
7486         if (appData.animate || appData.highlightLastMove) {
7487             SetHighlights(fromX, fromY, toX, toY);
7488         } else {
7489             ClearHighlights();
7490         }
7491     } else {
7492 #if 0
7493 // [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
7494         /* Finish drag move */
7495         if (appData.highlightLastMove) {
7496             SetHighlights(fromX, fromY, toX, toY);
7497         } else {
7498             ClearHighlights();
7499         }
7500 #endif
7501         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7502           dragging *= 2;            // flag button-less dragging if we are dragging
7503           MarkTargetSquares(1);
7504           if(x == killX && y == killY) killX = killY = -1; else {
7505             killX = x; killY = y;     //remeber this square as intermediate
7506             MarkTargetSquares(0);
7507             ReportClick("put", x, y); // and inform engine
7508             ReportClick("lift", x, y);
7509             return;
7510           }
7511         }
7512         DragPieceEnd(xPix, yPix); dragging = 0;
7513         /* Don't animate move and drag both */
7514         appData.animate = FALSE;
7515     }
7516
7517     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7518     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7519         ChessSquare piece = boards[currentMove][fromY][fromX];
7520         if(gameMode == EditPosition && piece != EmptySquare &&
7521            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7522             int n;
7523
7524             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7525                 n = PieceToNumber(piece - (int)BlackPawn);
7526                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7527                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7528                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7529             } else
7530             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7531                 n = PieceToNumber(piece);
7532                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7533                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7534                 boards[currentMove][n][BOARD_WIDTH-2]++;
7535             }
7536             boards[currentMove][fromY][fromX] = EmptySquare;
7537         }
7538         ClearHighlights();
7539         fromX = fromY = -1;
7540         MarkTargetSquares(1);
7541         DrawPosition(TRUE, boards[currentMove]);
7542         return;
7543     }
7544
7545     // off-board moves should not be highlighted
7546     if(x < 0 || y < 0) ClearHighlights();
7547     else ReportClick("put", x, y);
7548
7549     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7550
7551     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7552         SetHighlights(fromX, fromY, toX, toY);
7553         MarkTargetSquares(1);
7554         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7555             // [HGM] super: promotion to captured piece selected from holdings
7556             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7557             promotionChoice = TRUE;
7558             // kludge follows to temporarily execute move on display, without promoting yet
7559             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7560             boards[currentMove][toY][toX] = p;
7561             DrawPosition(FALSE, boards[currentMove]);
7562             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7563             boards[currentMove][toY][toX] = q;
7564             DisplayMessage("Click in holdings to choose piece", "");
7565             return;
7566         }
7567         PromotionPopUp();
7568     } else {
7569         int oldMove = currentMove;
7570         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7571         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7572         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7573         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7574            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7575             DrawPosition(TRUE, boards[currentMove]);
7576         MarkTargetSquares(1);
7577         fromX = fromY = -1;
7578     }
7579     appData.animate = saveAnimate;
7580     if (appData.animate || appData.animateDragging) {
7581         /* Undo animation damage if needed */
7582         DrawPosition(FALSE, NULL);
7583     }
7584 }
7585
7586 int
7587 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7588 {   // front-end-free part taken out of PieceMenuPopup
7589     int whichMenu; int xSqr, ySqr;
7590
7591     if(seekGraphUp) { // [HGM] seekgraph
7592         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7593         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7594         return -2;
7595     }
7596
7597     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7598          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7599         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7600         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7601         if(action == Press)   {
7602             originalFlip = flipView;
7603             flipView = !flipView; // temporarily flip board to see game from partners perspective
7604             DrawPosition(TRUE, partnerBoard);
7605             DisplayMessage(partnerStatus, "");
7606             partnerUp = TRUE;
7607         } else if(action == Release) {
7608             flipView = originalFlip;
7609             DrawPosition(TRUE, boards[currentMove]);
7610             partnerUp = FALSE;
7611         }
7612         return -2;
7613     }
7614
7615     xSqr = EventToSquare(x, BOARD_WIDTH);
7616     ySqr = EventToSquare(y, BOARD_HEIGHT);
7617     if (action == Release) {
7618         if(pieceSweep != EmptySquare) {
7619             EditPositionMenuEvent(pieceSweep, toX, toY);
7620             pieceSweep = EmptySquare;
7621         } else UnLoadPV(); // [HGM] pv
7622     }
7623     if (action != Press) return -2; // return code to be ignored
7624     switch (gameMode) {
7625       case IcsExamining:
7626         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7627       case EditPosition:
7628         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7629         if (xSqr < 0 || ySqr < 0) return -1;
7630         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7631         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7632         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7633         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7634         NextPiece(0);
7635         return 2; // grab
7636       case IcsObserving:
7637         if(!appData.icsEngineAnalyze) return -1;
7638       case IcsPlayingWhite:
7639       case IcsPlayingBlack:
7640         if(!appData.zippyPlay) goto noZip;
7641       case AnalyzeMode:
7642       case AnalyzeFile:
7643       case MachinePlaysWhite:
7644       case MachinePlaysBlack:
7645       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7646         if (!appData.dropMenu) {
7647           LoadPV(x, y);
7648           return 2; // flag front-end to grab mouse events
7649         }
7650         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7651            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7652       case EditGame:
7653       noZip:
7654         if (xSqr < 0 || ySqr < 0) return -1;
7655         if (!appData.dropMenu || appData.testLegality &&
7656             gameInfo.variant != VariantBughouse &&
7657             gameInfo.variant != VariantCrazyhouse) return -1;
7658         whichMenu = 1; // drop menu
7659         break;
7660       default:
7661         return -1;
7662     }
7663
7664     if (((*fromX = xSqr) < 0) ||
7665         ((*fromY = ySqr) < 0)) {
7666         *fromX = *fromY = -1;
7667         return -1;
7668     }
7669     if (flipView)
7670       *fromX = BOARD_WIDTH - 1 - *fromX;
7671     else
7672       *fromY = BOARD_HEIGHT - 1 - *fromY;
7673
7674     return whichMenu;
7675 }
7676
7677 void
7678 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7679 {
7680 //    char * hint = lastHint;
7681     FrontEndProgramStats stats;
7682
7683     stats.which = cps == &first ? 0 : 1;
7684     stats.depth = cpstats->depth;
7685     stats.nodes = cpstats->nodes;
7686     stats.score = cpstats->score;
7687     stats.time = cpstats->time;
7688     stats.pv = cpstats->movelist;
7689     stats.hint = lastHint;
7690     stats.an_move_index = 0;
7691     stats.an_move_count = 0;
7692
7693     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7694         stats.hint = cpstats->move_name;
7695         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7696         stats.an_move_count = cpstats->nr_moves;
7697     }
7698
7699     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
7700
7701     SetProgramStats( &stats );
7702 }
7703
7704 void
7705 ClearEngineOutputPane (int which)
7706 {
7707     static FrontEndProgramStats dummyStats;
7708     dummyStats.which = which;
7709     dummyStats.pv = "#";
7710     SetProgramStats( &dummyStats );
7711 }
7712
7713 #define MAXPLAYERS 500
7714
7715 char *
7716 TourneyStandings (int display)
7717 {
7718     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7719     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7720     char result, *p, *names[MAXPLAYERS];
7721
7722     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7723         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7724     names[0] = p = strdup(appData.participants);
7725     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7726
7727     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7728
7729     while(result = appData.results[nr]) {
7730         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7731         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7732         wScore = bScore = 0;
7733         switch(result) {
7734           case '+': wScore = 2; break;
7735           case '-': bScore = 2; break;
7736           case '=': wScore = bScore = 1; break;
7737           case ' ':
7738           case '*': return strdup("busy"); // tourney not finished
7739         }
7740         score[w] += wScore;
7741         score[b] += bScore;
7742         games[w]++;
7743         games[b]++;
7744         nr++;
7745     }
7746     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7747     for(w=0; w<nPlayers; w++) {
7748         bScore = -1;
7749         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7750         ranking[w] = b; points[w] = bScore; score[b] = -2;
7751     }
7752     p = malloc(nPlayers*34+1);
7753     for(w=0; w<nPlayers && w<display; w++)
7754         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7755     free(names[0]);
7756     return p;
7757 }
7758
7759 void
7760 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7761 {       // count all piece types
7762         int p, f, r;
7763         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7764         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7765         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7766                 p = board[r][f];
7767                 pCnt[p]++;
7768                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7769                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7770                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7771                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7772                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7773                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7774         }
7775 }
7776
7777 int
7778 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7779 {
7780         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7781         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7782
7783         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7784         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7785         if(myPawns == 2 && nMine == 3) // KPP
7786             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7787         if(myPawns == 1 && nMine == 2) // KP
7788             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7789         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7790             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7791         if(myPawns) return FALSE;
7792         if(pCnt[WhiteRook+side])
7793             return pCnt[BlackRook-side] ||
7794                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7795                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7796                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7797         if(pCnt[WhiteCannon+side]) {
7798             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7799             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7800         }
7801         if(pCnt[WhiteKnight+side])
7802             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7803         return FALSE;
7804 }
7805
7806 int
7807 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7808 {
7809         VariantClass v = gameInfo.variant;
7810
7811         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7812         if(v == VariantShatranj) return TRUE; // always winnable through baring
7813         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7814         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7815
7816         if(v == VariantXiangqi) {
7817                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7818
7819                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7820                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7821                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7822                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7823                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7824                 if(stale) // we have at least one last-rank P plus perhaps C
7825                     return majors // KPKX
7826                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7827                 else // KCA*E*
7828                     return pCnt[WhiteFerz+side] // KCAK
7829                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7830                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7831                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7832
7833         } else if(v == VariantKnightmate) {
7834                 if(nMine == 1) return FALSE;
7835                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7836         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7837                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7838
7839                 if(nMine == 1) return FALSE; // bare King
7840                 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
7841                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7842                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7843                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7844                 if(pCnt[WhiteKnight+side])
7845                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7846                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7847                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7848                 if(nBishops)
7849                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7850                 if(pCnt[WhiteAlfil+side])
7851                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7852                 if(pCnt[WhiteWazir+side])
7853                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7854         }
7855
7856         return TRUE;
7857 }
7858
7859 int
7860 CompareWithRights (Board b1, Board b2)
7861 {
7862     int rights = 0;
7863     if(!CompareBoards(b1, b2)) return FALSE;
7864     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7865     /* compare castling rights */
7866     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7867            rights++; /* King lost rights, while rook still had them */
7868     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7869         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7870            rights++; /* but at least one rook lost them */
7871     }
7872     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7873            rights++;
7874     if( b1[CASTLING][5] != NoRights ) {
7875         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7876            rights++;
7877     }
7878     return rights == 0;
7879 }
7880
7881 int
7882 Adjudicate (ChessProgramState *cps)
7883 {       // [HGM] some adjudications useful with buggy engines
7884         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7885         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7886         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7887         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7888         int k, drop, count = 0; static int bare = 1;
7889         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7890         Boolean canAdjudicate = !appData.icsActive;
7891
7892         // most tests only when we understand the game, i.e. legality-checking on
7893             if( appData.testLegality )
7894             {   /* [HGM] Some more adjudications for obstinate engines */
7895                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7896                 static int moveCount = 6;
7897                 ChessMove result;
7898                 char *reason = NULL;
7899
7900                 /* Count what is on board. */
7901                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7902
7903                 /* Some material-based adjudications that have to be made before stalemate test */
7904                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7905                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7906                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7907                      if(canAdjudicate && appData.checkMates) {
7908                          if(engineOpponent)
7909                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7910                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7911                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7912                          return 1;
7913                      }
7914                 }
7915
7916                 /* Bare King in Shatranj (loses) or Losers (wins) */
7917                 if( nrW == 1 || nrB == 1) {
7918                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7919                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7920                      if(canAdjudicate && appData.checkMates) {
7921                          if(engineOpponent)
7922                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7923                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7924                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7925                          return 1;
7926                      }
7927                   } else
7928                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7929                   {    /* bare King */
7930                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7931                         if(canAdjudicate && appData.checkMates) {
7932                             /* but only adjudicate if adjudication enabled */
7933                             if(engineOpponent)
7934                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7935                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7936                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7937                             return 1;
7938                         }
7939                   }
7940                 } else bare = 1;
7941
7942
7943             // don't wait for engine to announce game end if we can judge ourselves
7944             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7945               case MT_CHECK:
7946                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7947                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7948                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7949                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7950                             checkCnt++;
7951                         if(checkCnt >= 2) {
7952                             reason = "Xboard adjudication: 3rd check";
7953                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7954                             break;
7955                         }
7956                     }
7957                 }
7958               case MT_NONE:
7959               default:
7960                 break;
7961               case MT_STALEMATE:
7962               case MT_STAINMATE:
7963                 reason = "Xboard adjudication: Stalemate";
7964                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7965                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7966                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7967                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7968                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7969                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7970                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7971                                                                         EP_CHECKMATE : EP_WINS);
7972                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7973                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7974                 }
7975                 break;
7976               case MT_CHECKMATE:
7977                 reason = "Xboard adjudication: Checkmate";
7978                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7979                 if(gameInfo.variant == VariantShogi) {
7980                     if(forwardMostMove > backwardMostMove
7981                        && moveList[forwardMostMove-1][1] == '@'
7982                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7983                         reason = "XBoard adjudication: pawn-drop mate";
7984                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7985                     }
7986                 }
7987                 break;
7988             }
7989
7990                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7991                     case EP_STALEMATE:
7992                         result = GameIsDrawn; break;
7993                     case EP_CHECKMATE:
7994                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7995                     case EP_WINS:
7996                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7997                     default:
7998                         result = EndOfFile;
7999                 }
8000                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8001                     if(engineOpponent)
8002                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8003                     GameEnds( result, reason, GE_XBOARD );
8004                     return 1;
8005                 }
8006
8007                 /* Next absolutely insufficient mating material. */
8008                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8009                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8010                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8011
8012                      /* always flag draws, for judging claims */
8013                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8014
8015                      if(canAdjudicate && appData.materialDraws) {
8016                          /* but only adjudicate them if adjudication enabled */
8017                          if(engineOpponent) {
8018                            SendToProgram("force\n", engineOpponent); // suppress reply
8019                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8020                          }
8021                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8022                          return 1;
8023                      }
8024                 }
8025
8026                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8027                 if(gameInfo.variant == VariantXiangqi ?
8028                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8029                  : nrW + nrB == 4 &&
8030                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8031                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8032                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8033                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8034                    ) ) {
8035                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8036                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8037                           if(engineOpponent) {
8038                             SendToProgram("force\n", engineOpponent); // suppress reply
8039                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8040                           }
8041                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8042                           return 1;
8043                      }
8044                 } else moveCount = 6;
8045             }
8046
8047         // Repetition draws and 50-move rule can be applied independently of legality testing
8048
8049                 /* Check for rep-draws */
8050                 count = 0;
8051                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8052                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8053                 for(k = forwardMostMove-2;
8054                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8055                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8056                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8057                     k-=2)
8058                 {   int rights=0;
8059                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8060                         /* compare castling rights */
8061                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8062                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8063                                 rights++; /* King lost rights, while rook still had them */
8064                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8065                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8066                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8067                                    rights++; /* but at least one rook lost them */
8068                         }
8069                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8070                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8071                                 rights++;
8072                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8073                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8074                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8075                                    rights++;
8076                         }
8077                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8078                             && appData.drawRepeats > 1) {
8079                              /* adjudicate after user-specified nr of repeats */
8080                              int result = GameIsDrawn;
8081                              char *details = "XBoard adjudication: repetition draw";
8082                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8083                                 // [HGM] xiangqi: check for forbidden perpetuals
8084                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8085                                 for(m=forwardMostMove; m>k; m-=2) {
8086                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8087                                         ourPerpetual = 0; // the current mover did not always check
8088                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8089                                         hisPerpetual = 0; // the opponent did not always check
8090                                 }
8091                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8092                                                                         ourPerpetual, hisPerpetual);
8093                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8094                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8095                                     details = "Xboard adjudication: perpetual checking";
8096                                 } else
8097                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8098                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8099                                 } else
8100                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8101                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8102                                         result = BlackWins;
8103                                         details = "Xboard adjudication: repetition";
8104                                     }
8105                                 } else // it must be XQ
8106                                 // Now check for perpetual chases
8107                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8108                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8109                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8110                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8111                                         static char resdet[MSG_SIZ];
8112                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8113                                         details = resdet;
8114                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8115                                     } else
8116                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8117                                         break; // Abort repetition-checking loop.
8118                                 }
8119                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8120                              }
8121                              if(engineOpponent) {
8122                                SendToProgram("force\n", engineOpponent); // suppress reply
8123                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8124                              }
8125                              GameEnds( result, details, GE_XBOARD );
8126                              return 1;
8127                         }
8128                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8129                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8130                     }
8131                 }
8132
8133                 /* Now we test for 50-move draws. Determine ply count */
8134                 count = forwardMostMove;
8135                 /* look for last irreversble move */
8136                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8137                     count--;
8138                 /* if we hit starting position, add initial plies */
8139                 if( count == backwardMostMove )
8140                     count -= initialRulePlies;
8141                 count = forwardMostMove - count;
8142                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8143                         // adjust reversible move counter for checks in Xiangqi
8144                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8145                         if(i < backwardMostMove) i = backwardMostMove;
8146                         while(i <= forwardMostMove) {
8147                                 lastCheck = inCheck; // check evasion does not count
8148                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8149                                 if(inCheck || lastCheck) count--; // check does not count
8150                                 i++;
8151                         }
8152                 }
8153                 if( count >= 100)
8154                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8155                          /* this is used to judge if draw claims are legal */
8156                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8157                          if(engineOpponent) {
8158                            SendToProgram("force\n", engineOpponent); // suppress reply
8159                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8160                          }
8161                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8162                          return 1;
8163                 }
8164
8165                 /* if draw offer is pending, treat it as a draw claim
8166                  * when draw condition present, to allow engines a way to
8167                  * claim draws before making their move to avoid a race
8168                  * condition occurring after their move
8169                  */
8170                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8171                          char *p = NULL;
8172                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8173                              p = "Draw claim: 50-move rule";
8174                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8175                              p = "Draw claim: 3-fold repetition";
8176                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8177                              p = "Draw claim: insufficient mating material";
8178                          if( p != NULL && canAdjudicate) {
8179                              if(engineOpponent) {
8180                                SendToProgram("force\n", engineOpponent); // suppress reply
8181                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8182                              }
8183                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8184                              return 1;
8185                          }
8186                 }
8187
8188                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8189                     if(engineOpponent) {
8190                       SendToProgram("force\n", engineOpponent); // suppress reply
8191                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8192                     }
8193                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8194                     return 1;
8195                 }
8196         return 0;
8197 }
8198
8199 char *
8200 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8201 {   // [HGM] book: this routine intercepts moves to simulate book replies
8202     char *bookHit = NULL;
8203
8204     //first determine if the incoming move brings opponent into his book
8205     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8206         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8207     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8208     if(bookHit != NULL && !cps->bookSuspend) {
8209         // make sure opponent is not going to reply after receiving move to book position
8210         SendToProgram("force\n", cps);
8211         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8212     }
8213     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8214     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8215     // now arrange restart after book miss
8216     if(bookHit) {
8217         // after a book hit we never send 'go', and the code after the call to this routine
8218         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8219         char buf[MSG_SIZ], *move = bookHit;
8220         if(cps->useSAN) {
8221             int fromX, fromY, toX, toY;
8222             char promoChar;
8223             ChessMove moveType;
8224             move = buf + 30;
8225             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8226                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8227                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8228                                     PosFlags(forwardMostMove),
8229                                     fromY, fromX, toY, toX, promoChar, move);
8230             } else {
8231                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8232                 bookHit = NULL;
8233             }
8234         }
8235         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8236         SendToProgram(buf, cps);
8237         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8238     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8239         SendToProgram("go\n", cps);
8240         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8241     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8242         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8243             SendToProgram("go\n", cps);
8244         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8245     }
8246     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8247 }
8248
8249 int
8250 LoadError (char *errmess, ChessProgramState *cps)
8251 {   // unloads engine and switches back to -ncp mode if it was first
8252     if(cps->initDone) return FALSE;
8253     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8254     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8255     cps->pr = NoProc;
8256     if(cps == &first) {
8257         appData.noChessProgram = TRUE;
8258         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8259         gameMode = BeginningOfGame; ModeHighlight();
8260         SetNCPMode();
8261     }
8262     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8263     DisplayMessage("", ""); // erase waiting message
8264     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8265     return TRUE;
8266 }
8267
8268 char *savedMessage;
8269 ChessProgramState *savedState;
8270 void
8271 DeferredBookMove (void)
8272 {
8273         if(savedState->lastPing != savedState->lastPong)
8274                     ScheduleDelayedEvent(DeferredBookMove, 10);
8275         else
8276         HandleMachineMove(savedMessage, savedState);
8277 }
8278
8279 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8280 static ChessProgramState *stalledEngine;
8281 static char stashedInputMove[MSG_SIZ];
8282
8283 void
8284 HandleMachineMove (char *message, ChessProgramState *cps)
8285 {
8286     static char firstLeg[20];
8287     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8288     char realname[MSG_SIZ];
8289     int fromX, fromY, toX, toY;
8290     ChessMove moveType;
8291     char promoChar, roar;
8292     char *p, *pv=buf1;
8293     int machineWhite, oldError;
8294     char *bookHit;
8295
8296     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8297         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8298         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8299             DisplayError(_("Invalid pairing from pairing engine"), 0);
8300             return;
8301         }
8302         pairingReceived = 1;
8303         NextMatchGame();
8304         return; // Skim the pairing messages here.
8305     }
8306
8307     oldError = cps->userError; cps->userError = 0;
8308
8309 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8310     /*
8311      * Kludge to ignore BEL characters
8312      */
8313     while (*message == '\007') message++;
8314
8315     /*
8316      * [HGM] engine debug message: ignore lines starting with '#' character
8317      */
8318     if(cps->debug && *message == '#') return;
8319
8320     /*
8321      * Look for book output
8322      */
8323     if (cps == &first && bookRequested) {
8324         if (message[0] == '\t' || message[0] == ' ') {
8325             /* Part of the book output is here; append it */
8326             strcat(bookOutput, message);
8327             strcat(bookOutput, "  \n");
8328             return;
8329         } else if (bookOutput[0] != NULLCHAR) {
8330             /* All of book output has arrived; display it */
8331             char *p = bookOutput;
8332             while (*p != NULLCHAR) {
8333                 if (*p == '\t') *p = ' ';
8334                 p++;
8335             }
8336             DisplayInformation(bookOutput);
8337             bookRequested = FALSE;
8338             /* Fall through to parse the current output */
8339         }
8340     }
8341
8342     /*
8343      * Look for machine move.
8344      */
8345     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8346         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8347     {
8348         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8349             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8350             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8351             stalledEngine = cps;
8352             if(appData.ponderNextMove) { // bring opponent out of ponder
8353                 if(gameMode == TwoMachinesPlay) {
8354                     if(cps->other->pause)
8355                         PauseEngine(cps->other);
8356                     else
8357                         SendToProgram("easy\n", cps->other);
8358                 }
8359             }
8360             StopClocks();
8361             return;
8362         }
8363
8364         /* This method is only useful on engines that support ping */
8365         if (cps->lastPing != cps->lastPong) {
8366           if (gameMode == BeginningOfGame) {
8367             /* Extra move from before last new; ignore */
8368             if (appData.debugMode) {
8369                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8370             }
8371           } else {
8372             if (appData.debugMode) {
8373                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8374                         cps->which, gameMode);
8375             }
8376
8377             SendToProgram("undo\n", cps);
8378           }
8379           return;
8380         }
8381
8382         switch (gameMode) {
8383           case BeginningOfGame:
8384             /* Extra move from before last reset; ignore */
8385             if (appData.debugMode) {
8386                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8387             }
8388             return;
8389
8390           case EndOfGame:
8391           case IcsIdle:
8392           default:
8393             /* Extra move after we tried to stop.  The mode test is
8394                not a reliable way of detecting this problem, but it's
8395                the best we can do on engines that don't support ping.
8396             */
8397             if (appData.debugMode) {
8398                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8399                         cps->which, gameMode);
8400             }
8401             SendToProgram("undo\n", cps);
8402             return;
8403
8404           case MachinePlaysWhite:
8405           case IcsPlayingWhite:
8406             machineWhite = TRUE;
8407             break;
8408
8409           case MachinePlaysBlack:
8410           case IcsPlayingBlack:
8411             machineWhite = FALSE;
8412             break;
8413
8414           case TwoMachinesPlay:
8415             machineWhite = (cps->twoMachinesColor[0] == 'w');
8416             break;
8417         }
8418         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8419             if (appData.debugMode) {
8420                 fprintf(debugFP,
8421                         "Ignoring move out of turn by %s, gameMode %d"
8422                         ", forwardMost %d\n",
8423                         cps->which, gameMode, forwardMostMove);
8424             }
8425             return;
8426         }
8427
8428         if(cps->alphaRank) AlphaRank(machineMove, 4);
8429
8430         // [HGM] lion: (some very limited) support for Alien protocol
8431         killX = killY = -1;
8432         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8433             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8434             return;
8435         } else if(firstLeg[0]) { // there was a previous leg;
8436             // only support case where same piece makes two step (and don't even test that!)
8437             char buf[20], *p = machineMove+1, *q = buf+1, f;
8438             safeStrCpy(buf, machineMove, 20);
8439             while(isdigit(*q)) q++; // find start of to-square
8440             safeStrCpy(machineMove, firstLeg, 20);
8441             while(isdigit(*p)) p++;
8442             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8443             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8444             firstLeg[0] = NULLCHAR;
8445         }
8446
8447         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8448                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8449             /* Machine move could not be parsed; ignore it. */
8450           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8451                     machineMove, _(cps->which));
8452             DisplayMoveError(buf1);
8453             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8454                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8455             if (gameMode == TwoMachinesPlay) {
8456               GameEnds(machineWhite ? BlackWins : WhiteWins,
8457                        buf1, GE_XBOARD);
8458             }
8459             return;
8460         }
8461
8462         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8463         /* So we have to redo legality test with true e.p. status here,  */
8464         /* to make sure an illegal e.p. capture does not slip through,   */
8465         /* to cause a forfeit on a justified illegal-move complaint      */
8466         /* of the opponent.                                              */
8467         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8468            ChessMove moveType;
8469            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8470                              fromY, fromX, toY, toX, promoChar);
8471             if(moveType == IllegalMove) {
8472               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8473                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8474                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8475                            buf1, GE_XBOARD);
8476                 return;
8477            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8478            /* [HGM] Kludge to handle engines that send FRC-style castling
8479               when they shouldn't (like TSCP-Gothic) */
8480            switch(moveType) {
8481              case WhiteASideCastleFR:
8482              case BlackASideCastleFR:
8483                toX+=2;
8484                currentMoveString[2]++;
8485                break;
8486              case WhiteHSideCastleFR:
8487              case BlackHSideCastleFR:
8488                toX--;
8489                currentMoveString[2]--;
8490                break;
8491              default: ; // nothing to do, but suppresses warning of pedantic compilers
8492            }
8493         }
8494         hintRequested = FALSE;
8495         lastHint[0] = NULLCHAR;
8496         bookRequested = FALSE;
8497         /* Program may be pondering now */
8498         cps->maybeThinking = TRUE;
8499         if (cps->sendTime == 2) cps->sendTime = 1;
8500         if (cps->offeredDraw) cps->offeredDraw--;
8501
8502         /* [AS] Save move info*/
8503         pvInfoList[ forwardMostMove ].score = programStats.score;
8504         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8505         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8506
8507         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8508
8509         /* Test suites abort the 'game' after one move */
8510         if(*appData.finger) {
8511            static FILE *f;
8512            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8513            if(!f) f = fopen(appData.finger, "w");
8514            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8515            else { DisplayFatalError("Bad output file", errno, 0); return; }
8516            free(fen);
8517            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8518         }
8519
8520         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8521         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8522             int count = 0;
8523
8524             while( count < adjudicateLossPlies ) {
8525                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8526
8527                 if( count & 1 ) {
8528                     score = -score; /* Flip score for winning side */
8529                 }
8530
8531                 if( score > adjudicateLossThreshold ) {
8532                     break;
8533                 }
8534
8535                 count++;
8536             }
8537
8538             if( count >= adjudicateLossPlies ) {
8539                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8540
8541                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8542                     "Xboard adjudication",
8543                     GE_XBOARD );
8544
8545                 return;
8546             }
8547         }
8548
8549         if(Adjudicate(cps)) {
8550             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8551             return; // [HGM] adjudicate: for all automatic game ends
8552         }
8553
8554 #if ZIPPY
8555         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8556             first.initDone) {
8557           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8558                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8559                 SendToICS("draw ");
8560                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8561           }
8562           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8563           ics_user_moved = 1;
8564           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8565                 char buf[3*MSG_SIZ];
8566
8567                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8568                         programStats.score / 100.,
8569                         programStats.depth,
8570                         programStats.time / 100.,
8571                         (unsigned int)programStats.nodes,
8572                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8573                         programStats.movelist);
8574                 SendToICS(buf);
8575 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8576           }
8577         }
8578 #endif
8579
8580         /* [AS] Clear stats for next move */
8581         ClearProgramStats();
8582         thinkOutput[0] = NULLCHAR;
8583         hiddenThinkOutputState = 0;
8584
8585         bookHit = NULL;
8586         if (gameMode == TwoMachinesPlay) {
8587             /* [HGM] relaying draw offers moved to after reception of move */
8588             /* and interpreting offer as claim if it brings draw condition */
8589             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8590                 SendToProgram("draw\n", cps->other);
8591             }
8592             if (cps->other->sendTime) {
8593                 SendTimeRemaining(cps->other,
8594                                   cps->other->twoMachinesColor[0] == 'w');
8595             }
8596             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8597             if (firstMove && !bookHit) {
8598                 firstMove = FALSE;
8599                 if (cps->other->useColors) {
8600                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8601                 }
8602                 SendToProgram("go\n", cps->other);
8603             }
8604             cps->other->maybeThinking = TRUE;
8605         }
8606
8607         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8608
8609         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8610
8611         if (!pausing && appData.ringBellAfterMoves) {
8612             if(!roar) RingBell();
8613         }
8614
8615         /*
8616          * Reenable menu items that were disabled while
8617          * machine was thinking
8618          */
8619         if (gameMode != TwoMachinesPlay)
8620             SetUserThinkingEnables();
8621
8622         // [HGM] book: after book hit opponent has received move and is now in force mode
8623         // force the book reply into it, and then fake that it outputted this move by jumping
8624         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8625         if(bookHit) {
8626                 static char bookMove[MSG_SIZ]; // a bit generous?
8627
8628                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8629                 strcat(bookMove, bookHit);
8630                 message = bookMove;
8631                 cps = cps->other;
8632                 programStats.nodes = programStats.depth = programStats.time =
8633                 programStats.score = programStats.got_only_move = 0;
8634                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8635
8636                 if(cps->lastPing != cps->lastPong) {
8637                     savedMessage = message; // args for deferred call
8638                     savedState = cps;
8639                     ScheduleDelayedEvent(DeferredBookMove, 10);
8640                     return;
8641                 }
8642                 goto FakeBookMove;
8643         }
8644
8645         return;
8646     }
8647
8648     /* Set special modes for chess engines.  Later something general
8649      *  could be added here; for now there is just one kludge feature,
8650      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8651      *  when "xboard" is given as an interactive command.
8652      */
8653     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8654         cps->useSigint = FALSE;
8655         cps->useSigterm = FALSE;
8656     }
8657     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8658       ParseFeatures(message+8, cps);
8659       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8660     }
8661
8662     if (!strncmp(message, "setup ", 6) && 
8663         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8664                                         ) { // [HGM] allow first engine to define opening position
8665       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8666       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8667       *buf = NULLCHAR;
8668       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8669       if(startedFromSetupPosition) return;
8670       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8671       if(dummy >= 3) {
8672         while(message[s] && message[s++] != ' ');
8673         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8674            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8675             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8676             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8677           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8678           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8679         }
8680       }
8681       ParseFEN(boards[0], &dummy, message+s, FALSE);
8682       DrawPosition(TRUE, boards[0]);
8683       startedFromSetupPosition = TRUE;
8684       return;
8685     }
8686     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8687      * want this, I was asked to put it in, and obliged.
8688      */
8689     if (!strncmp(message, "setboard ", 9)) {
8690         Board initial_position;
8691
8692         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8693
8694         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8695             DisplayError(_("Bad FEN received from engine"), 0);
8696             return ;
8697         } else {
8698            Reset(TRUE, FALSE);
8699            CopyBoard(boards[0], initial_position);
8700            initialRulePlies = FENrulePlies;
8701            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8702            else gameMode = MachinePlaysBlack;
8703            DrawPosition(FALSE, boards[currentMove]);
8704         }
8705         return;
8706     }
8707
8708     /*
8709      * Look for communication commands
8710      */
8711     if (!strncmp(message, "telluser ", 9)) {
8712         if(message[9] == '\\' && message[10] == '\\')
8713             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8714         PlayTellSound();
8715         DisplayNote(message + 9);
8716         return;
8717     }
8718     if (!strncmp(message, "tellusererror ", 14)) {
8719         cps->userError = 1;
8720         if(message[14] == '\\' && message[15] == '\\')
8721             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8722         PlayTellSound();
8723         DisplayError(message + 14, 0);
8724         return;
8725     }
8726     if (!strncmp(message, "tellopponent ", 13)) {
8727       if (appData.icsActive) {
8728         if (loggedOn) {
8729           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8730           SendToICS(buf1);
8731         }
8732       } else {
8733         DisplayNote(message + 13);
8734       }
8735       return;
8736     }
8737     if (!strncmp(message, "tellothers ", 11)) {
8738       if (appData.icsActive) {
8739         if (loggedOn) {
8740           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8741           SendToICS(buf1);
8742         }
8743       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8744       return;
8745     }
8746     if (!strncmp(message, "tellall ", 8)) {
8747       if (appData.icsActive) {
8748         if (loggedOn) {
8749           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8750           SendToICS(buf1);
8751         }
8752       } else {
8753         DisplayNote(message + 8);
8754       }
8755       return;
8756     }
8757     if (strncmp(message, "warning", 7) == 0) {
8758         /* Undocumented feature, use tellusererror in new code */
8759         DisplayError(message, 0);
8760         return;
8761     }
8762     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8763         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8764         strcat(realname, " query");
8765         AskQuestion(realname, buf2, buf1, cps->pr);
8766         return;
8767     }
8768     /* Commands from the engine directly to ICS.  We don't allow these to be
8769      *  sent until we are logged on. Crafty kibitzes have been known to
8770      *  interfere with the login process.
8771      */
8772     if (loggedOn) {
8773         if (!strncmp(message, "tellics ", 8)) {
8774             SendToICS(message + 8);
8775             SendToICS("\n");
8776             return;
8777         }
8778         if (!strncmp(message, "tellicsnoalias ", 15)) {
8779             SendToICS(ics_prefix);
8780             SendToICS(message + 15);
8781             SendToICS("\n");
8782             return;
8783         }
8784         /* The following are for backward compatibility only */
8785         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8786             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8787             SendToICS(ics_prefix);
8788             SendToICS(message);
8789             SendToICS("\n");
8790             return;
8791         }
8792     }
8793     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8794         return;
8795     }
8796     if(!strncmp(message, "highlight ", 10)) {
8797         if(appData.testLegality && appData.markers) return;
8798         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8799         return;
8800     }
8801     if(!strncmp(message, "click ", 6)) {
8802         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8803         if(appData.testLegality || !appData.oneClick) return;
8804         sscanf(message+6, "%c%d%c", &f, &y, &c);
8805         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8806         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8807         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8808         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8809         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8810         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8811             LeftClick(Release, lastLeftX, lastLeftY);
8812         controlKey  = (c == ',');
8813         LeftClick(Press, x, y);
8814         LeftClick(Release, x, y);
8815         first.highlight = f;
8816         return;
8817     }
8818     /*
8819      * If the move is illegal, cancel it and redraw the board.
8820      * Also deal with other error cases.  Matching is rather loose
8821      * here to accommodate engines written before the spec.
8822      */
8823     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8824         strncmp(message, "Error", 5) == 0) {
8825         if (StrStr(message, "name") ||
8826             StrStr(message, "rating") || StrStr(message, "?") ||
8827             StrStr(message, "result") || StrStr(message, "board") ||
8828             StrStr(message, "bk") || StrStr(message, "computer") ||
8829             StrStr(message, "variant") || StrStr(message, "hint") ||
8830             StrStr(message, "random") || StrStr(message, "depth") ||
8831             StrStr(message, "accepted")) {
8832             return;
8833         }
8834         if (StrStr(message, "protover")) {
8835           /* Program is responding to input, so it's apparently done
8836              initializing, and this error message indicates it is
8837              protocol version 1.  So we don't need to wait any longer
8838              for it to initialize and send feature commands. */
8839           FeatureDone(cps, 1);
8840           cps->protocolVersion = 1;
8841           return;
8842         }
8843         cps->maybeThinking = FALSE;
8844
8845         if (StrStr(message, "draw")) {
8846             /* Program doesn't have "draw" command */
8847             cps->sendDrawOffers = 0;
8848             return;
8849         }
8850         if (cps->sendTime != 1 &&
8851             (StrStr(message, "time") || StrStr(message, "otim"))) {
8852           /* Program apparently doesn't have "time" or "otim" command */
8853           cps->sendTime = 0;
8854           return;
8855         }
8856         if (StrStr(message, "analyze")) {
8857             cps->analysisSupport = FALSE;
8858             cps->analyzing = FALSE;
8859 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8860             EditGameEvent(); // [HGM] try to preserve loaded game
8861             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8862             DisplayError(buf2, 0);
8863             return;
8864         }
8865         if (StrStr(message, "(no matching move)st")) {
8866           /* Special kludge for GNU Chess 4 only */
8867           cps->stKludge = TRUE;
8868           SendTimeControl(cps, movesPerSession, timeControl,
8869                           timeIncrement, appData.searchDepth,
8870                           searchTime);
8871           return;
8872         }
8873         if (StrStr(message, "(no matching move)sd")) {
8874           /* Special kludge for GNU Chess 4 only */
8875           cps->sdKludge = TRUE;
8876           SendTimeControl(cps, movesPerSession, timeControl,
8877                           timeIncrement, appData.searchDepth,
8878                           searchTime);
8879           return;
8880         }
8881         if (!StrStr(message, "llegal")) {
8882             return;
8883         }
8884         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8885             gameMode == IcsIdle) return;
8886         if (forwardMostMove <= backwardMostMove) return;
8887         if (pausing) PauseEvent();
8888       if(appData.forceIllegal) {
8889             // [HGM] illegal: machine refused move; force position after move into it
8890           SendToProgram("force\n", cps);
8891           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8892                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8893                 // when black is to move, while there might be nothing on a2 or black
8894                 // might already have the move. So send the board as if white has the move.
8895                 // But first we must change the stm of the engine, as it refused the last move
8896                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8897                 if(WhiteOnMove(forwardMostMove)) {
8898                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8899                     SendBoard(cps, forwardMostMove); // kludgeless board
8900                 } else {
8901                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8902                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8903                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8904                 }
8905           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8906             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8907                  gameMode == TwoMachinesPlay)
8908               SendToProgram("go\n", cps);
8909             return;
8910       } else
8911         if (gameMode == PlayFromGameFile) {
8912             /* Stop reading this game file */
8913             gameMode = EditGame;
8914             ModeHighlight();
8915         }
8916         /* [HGM] illegal-move claim should forfeit game when Xboard */
8917         /* only passes fully legal moves                            */
8918         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8919             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8920                                 "False illegal-move claim", GE_XBOARD );
8921             return; // do not take back move we tested as valid
8922         }
8923         currentMove = forwardMostMove-1;
8924         DisplayMove(currentMove-1); /* before DisplayMoveError */
8925         SwitchClocks(forwardMostMove-1); // [HGM] race
8926         DisplayBothClocks();
8927         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8928                 parseList[currentMove], _(cps->which));
8929         DisplayMoveError(buf1);
8930         DrawPosition(FALSE, boards[currentMove]);
8931
8932         SetUserThinkingEnables();
8933         return;
8934     }
8935     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8936         /* Program has a broken "time" command that
8937            outputs a string not ending in newline.
8938            Don't use it. */
8939         cps->sendTime = 0;
8940     }
8941
8942     /*
8943      * If chess program startup fails, exit with an error message.
8944      * Attempts to recover here are futile. [HGM] Well, we try anyway
8945      */
8946     if ((StrStr(message, "unknown host") != NULL)
8947         || (StrStr(message, "No remote directory") != NULL)
8948         || (StrStr(message, "not found") != NULL)
8949         || (StrStr(message, "No such file") != NULL)
8950         || (StrStr(message, "can't alloc") != NULL)
8951         || (StrStr(message, "Permission denied") != NULL)) {
8952
8953         cps->maybeThinking = FALSE;
8954         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8955                 _(cps->which), cps->program, cps->host, message);
8956         RemoveInputSource(cps->isr);
8957         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8958             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8959             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8960         }
8961         return;
8962     }
8963
8964     /*
8965      * Look for hint output
8966      */
8967     if (sscanf(message, "Hint: %s", buf1) == 1) {
8968         if (cps == &first && hintRequested) {
8969             hintRequested = FALSE;
8970             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8971                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8972                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8973                                     PosFlags(forwardMostMove),
8974                                     fromY, fromX, toY, toX, promoChar, buf1);
8975                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8976                 DisplayInformation(buf2);
8977             } else {
8978                 /* Hint move could not be parsed!? */
8979               snprintf(buf2, sizeof(buf2),
8980                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8981                         buf1, _(cps->which));
8982                 DisplayError(buf2, 0);
8983             }
8984         } else {
8985           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8986         }
8987         return;
8988     }
8989
8990     /*
8991      * Ignore other messages if game is not in progress
8992      */
8993     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8994         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8995
8996     /*
8997      * look for win, lose, draw, or draw offer
8998      */
8999     if (strncmp(message, "1-0", 3) == 0) {
9000         char *p, *q, *r = "";
9001         p = strchr(message, '{');
9002         if (p) {
9003             q = strchr(p, '}');
9004             if (q) {
9005                 *q = NULLCHAR;
9006                 r = p + 1;
9007             }
9008         }
9009         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9010         return;
9011     } else if (strncmp(message, "0-1", 3) == 0) {
9012         char *p, *q, *r = "";
9013         p = strchr(message, '{');
9014         if (p) {
9015             q = strchr(p, '}');
9016             if (q) {
9017                 *q = NULLCHAR;
9018                 r = p + 1;
9019             }
9020         }
9021         /* Kludge for Arasan 4.1 bug */
9022         if (strcmp(r, "Black resigns") == 0) {
9023             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9024             return;
9025         }
9026         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9027         return;
9028     } else if (strncmp(message, "1/2", 3) == 0) {
9029         char *p, *q, *r = "";
9030         p = strchr(message, '{');
9031         if (p) {
9032             q = strchr(p, '}');
9033             if (q) {
9034                 *q = NULLCHAR;
9035                 r = p + 1;
9036             }
9037         }
9038
9039         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9040         return;
9041
9042     } else if (strncmp(message, "White resign", 12) == 0) {
9043         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9044         return;
9045     } else if (strncmp(message, "Black resign", 12) == 0) {
9046         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9047         return;
9048     } else if (strncmp(message, "White matches", 13) == 0 ||
9049                strncmp(message, "Black matches", 13) == 0   ) {
9050         /* [HGM] ignore GNUShogi noises */
9051         return;
9052     } else if (strncmp(message, "White", 5) == 0 &&
9053                message[5] != '(' &&
9054                StrStr(message, "Black") == NULL) {
9055         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9056         return;
9057     } else if (strncmp(message, "Black", 5) == 0 &&
9058                message[5] != '(') {
9059         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9060         return;
9061     } else if (strcmp(message, "resign") == 0 ||
9062                strcmp(message, "computer resigns") == 0) {
9063         switch (gameMode) {
9064           case MachinePlaysBlack:
9065           case IcsPlayingBlack:
9066             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9067             break;
9068           case MachinePlaysWhite:
9069           case IcsPlayingWhite:
9070             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9071             break;
9072           case TwoMachinesPlay:
9073             if (cps->twoMachinesColor[0] == 'w')
9074               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9075             else
9076               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9077             break;
9078           default:
9079             /* can't happen */
9080             break;
9081         }
9082         return;
9083     } else if (strncmp(message, "opponent mates", 14) == 0) {
9084         switch (gameMode) {
9085           case MachinePlaysBlack:
9086           case IcsPlayingBlack:
9087             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9088             break;
9089           case MachinePlaysWhite:
9090           case IcsPlayingWhite:
9091             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9092             break;
9093           case TwoMachinesPlay:
9094             if (cps->twoMachinesColor[0] == 'w')
9095               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9096             else
9097               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9098             break;
9099           default:
9100             /* can't happen */
9101             break;
9102         }
9103         return;
9104     } else if (strncmp(message, "computer mates", 14) == 0) {
9105         switch (gameMode) {
9106           case MachinePlaysBlack:
9107           case IcsPlayingBlack:
9108             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9109             break;
9110           case MachinePlaysWhite:
9111           case IcsPlayingWhite:
9112             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9113             break;
9114           case TwoMachinesPlay:
9115             if (cps->twoMachinesColor[0] == 'w')
9116               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9117             else
9118               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9119             break;
9120           default:
9121             /* can't happen */
9122             break;
9123         }
9124         return;
9125     } else if (strncmp(message, "checkmate", 9) == 0) {
9126         if (WhiteOnMove(forwardMostMove)) {
9127             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9128         } else {
9129             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9130         }
9131         return;
9132     } else if (strstr(message, "Draw") != NULL ||
9133                strstr(message, "game is a draw") != NULL) {
9134         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9135         return;
9136     } else if (strstr(message, "offer") != NULL &&
9137                strstr(message, "draw") != NULL) {
9138 #if ZIPPY
9139         if (appData.zippyPlay && first.initDone) {
9140             /* Relay offer to ICS */
9141             SendToICS(ics_prefix);
9142             SendToICS("draw\n");
9143         }
9144 #endif
9145         cps->offeredDraw = 2; /* valid until this engine moves twice */
9146         if (gameMode == TwoMachinesPlay) {
9147             if (cps->other->offeredDraw) {
9148                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9149             /* [HGM] in two-machine mode we delay relaying draw offer      */
9150             /* until after we also have move, to see if it is really claim */
9151             }
9152         } else if (gameMode == MachinePlaysWhite ||
9153                    gameMode == MachinePlaysBlack) {
9154           if (userOfferedDraw) {
9155             DisplayInformation(_("Machine accepts your draw offer"));
9156             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9157           } else {
9158             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9159           }
9160         }
9161     }
9162
9163
9164     /*
9165      * Look for thinking output
9166      */
9167     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9168           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9169                                 ) {
9170         int plylev, mvleft, mvtot, curscore, time;
9171         char mvname[MOVE_LEN];
9172         u64 nodes; // [DM]
9173         char plyext;
9174         int ignore = FALSE;
9175         int prefixHint = FALSE;
9176         mvname[0] = NULLCHAR;
9177
9178         switch (gameMode) {
9179           case MachinePlaysBlack:
9180           case IcsPlayingBlack:
9181             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9182             break;
9183           case MachinePlaysWhite:
9184           case IcsPlayingWhite:
9185             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9186             break;
9187           case AnalyzeMode:
9188           case AnalyzeFile:
9189             break;
9190           case IcsObserving: /* [DM] icsEngineAnalyze */
9191             if (!appData.icsEngineAnalyze) ignore = TRUE;
9192             break;
9193           case TwoMachinesPlay:
9194             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9195                 ignore = TRUE;
9196             }
9197             break;
9198           default:
9199             ignore = TRUE;
9200             break;
9201         }
9202
9203         if (!ignore) {
9204             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9205             buf1[0] = NULLCHAR;
9206             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9207                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9208
9209                 if (plyext != ' ' && plyext != '\t') {
9210                     time *= 100;
9211                 }
9212
9213                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9214                 if( cps->scoreIsAbsolute &&
9215                     ( gameMode == MachinePlaysBlack ||
9216                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9217                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9218                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9219                      !WhiteOnMove(currentMove)
9220                     ) )
9221                 {
9222                     curscore = -curscore;
9223                 }
9224
9225                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9226
9227                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9228                         char buf[MSG_SIZ];
9229                         FILE *f;
9230                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9231                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9232                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9233                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9234                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9235                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9236                                 fclose(f);
9237                         }
9238                         else
9239                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9240                           DisplayError(_("failed writing PV"), 0);
9241                 }
9242
9243                 tempStats.depth = plylev;
9244                 tempStats.nodes = nodes;
9245                 tempStats.time = time;
9246                 tempStats.score = curscore;
9247                 tempStats.got_only_move = 0;
9248
9249                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9250                         int ticklen;
9251
9252                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9253                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9254                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9255                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9256                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9257                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9258                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9259                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9260                 }
9261
9262                 /* Buffer overflow protection */
9263                 if (pv[0] != NULLCHAR) {
9264                     if (strlen(pv) >= sizeof(tempStats.movelist)
9265                         && appData.debugMode) {
9266                         fprintf(debugFP,
9267                                 "PV is too long; using the first %u bytes.\n",
9268                                 (unsigned) sizeof(tempStats.movelist) - 1);
9269                     }
9270
9271                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9272                 } else {
9273                     sprintf(tempStats.movelist, " no PV\n");
9274                 }
9275
9276                 if (tempStats.seen_stat) {
9277                     tempStats.ok_to_send = 1;
9278                 }
9279
9280                 if (strchr(tempStats.movelist, '(') != NULL) {
9281                     tempStats.line_is_book = 1;
9282                     tempStats.nr_moves = 0;
9283                     tempStats.moves_left = 0;
9284                 } else {
9285                     tempStats.line_is_book = 0;
9286                 }
9287
9288                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9289                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9290
9291                 SendProgramStatsToFrontend( cps, &tempStats );
9292
9293                 /*
9294                     [AS] Protect the thinkOutput buffer from overflow... this
9295                     is only useful if buf1 hasn't overflowed first!
9296                 */
9297                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9298                          plylev,
9299                          (gameMode == TwoMachinesPlay ?
9300                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9301                          ((double) curscore) / 100.0,
9302                          prefixHint ? lastHint : "",
9303                          prefixHint ? " " : "" );
9304
9305                 if( buf1[0] != NULLCHAR ) {
9306                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9307
9308                     if( strlen(pv) > max_len ) {
9309                         if( appData.debugMode) {
9310                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9311                         }
9312                         pv[max_len+1] = '\0';
9313                     }
9314
9315                     strcat( thinkOutput, pv);
9316                 }
9317
9318                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9319                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9320                     DisplayMove(currentMove - 1);
9321                 }
9322                 return;
9323
9324             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9325                 /* crafty (9.25+) says "(only move) <move>"
9326                  * if there is only 1 legal move
9327                  */
9328                 sscanf(p, "(only move) %s", buf1);
9329                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9330                 sprintf(programStats.movelist, "%s (only move)", buf1);
9331                 programStats.depth = 1;
9332                 programStats.nr_moves = 1;
9333                 programStats.moves_left = 1;
9334                 programStats.nodes = 1;
9335                 programStats.time = 1;
9336                 programStats.got_only_move = 1;
9337
9338                 /* Not really, but we also use this member to
9339                    mean "line isn't going to change" (Crafty
9340                    isn't searching, so stats won't change) */
9341                 programStats.line_is_book = 1;
9342
9343                 SendProgramStatsToFrontend( cps, &programStats );
9344
9345                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9346                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9347                     DisplayMove(currentMove - 1);
9348                 }
9349                 return;
9350             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9351                               &time, &nodes, &plylev, &mvleft,
9352                               &mvtot, mvname) >= 5) {
9353                 /* The stat01: line is from Crafty (9.29+) in response
9354                    to the "." command */
9355                 programStats.seen_stat = 1;
9356                 cps->maybeThinking = TRUE;
9357
9358                 if (programStats.got_only_move || !appData.periodicUpdates)
9359                   return;
9360
9361                 programStats.depth = plylev;
9362                 programStats.time = time;
9363                 programStats.nodes = nodes;
9364                 programStats.moves_left = mvleft;
9365                 programStats.nr_moves = mvtot;
9366                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9367                 programStats.ok_to_send = 1;
9368                 programStats.movelist[0] = '\0';
9369
9370                 SendProgramStatsToFrontend( cps, &programStats );
9371
9372                 return;
9373
9374             } else if (strncmp(message,"++",2) == 0) {
9375                 /* Crafty 9.29+ outputs this */
9376                 programStats.got_fail = 2;
9377                 return;
9378
9379             } else if (strncmp(message,"--",2) == 0) {
9380                 /* Crafty 9.29+ outputs this */
9381                 programStats.got_fail = 1;
9382                 return;
9383
9384             } else if (thinkOutput[0] != NULLCHAR &&
9385                        strncmp(message, "    ", 4) == 0) {
9386                 unsigned message_len;
9387
9388                 p = message;
9389                 while (*p && *p == ' ') p++;
9390
9391                 message_len = strlen( p );
9392
9393                 /* [AS] Avoid buffer overflow */
9394                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9395                     strcat(thinkOutput, " ");
9396                     strcat(thinkOutput, p);
9397                 }
9398
9399                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9400                     strcat(programStats.movelist, " ");
9401                     strcat(programStats.movelist, p);
9402                 }
9403
9404                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9405                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9406                     DisplayMove(currentMove - 1);
9407                 }
9408                 return;
9409             }
9410         }
9411         else {
9412             buf1[0] = NULLCHAR;
9413
9414             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9415                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9416             {
9417                 ChessProgramStats cpstats;
9418
9419                 if (plyext != ' ' && plyext != '\t') {
9420                     time *= 100;
9421                 }
9422
9423                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9424                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9425                     curscore = -curscore;
9426                 }
9427
9428                 cpstats.depth = plylev;
9429                 cpstats.nodes = nodes;
9430                 cpstats.time = time;
9431                 cpstats.score = curscore;
9432                 cpstats.got_only_move = 0;
9433                 cpstats.movelist[0] = '\0';
9434
9435                 if (buf1[0] != NULLCHAR) {
9436                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9437                 }
9438
9439                 cpstats.ok_to_send = 0;
9440                 cpstats.line_is_book = 0;
9441                 cpstats.nr_moves = 0;
9442                 cpstats.moves_left = 0;
9443
9444                 SendProgramStatsToFrontend( cps, &cpstats );
9445             }
9446         }
9447     }
9448 }
9449
9450
9451 /* Parse a game score from the character string "game", and
9452    record it as the history of the current game.  The game
9453    score is NOT assumed to start from the standard position.
9454    The display is not updated in any way.
9455    */
9456 void
9457 ParseGameHistory (char *game)
9458 {
9459     ChessMove moveType;
9460     int fromX, fromY, toX, toY, boardIndex;
9461     char promoChar;
9462     char *p, *q;
9463     char buf[MSG_SIZ];
9464
9465     if (appData.debugMode)
9466       fprintf(debugFP, "Parsing game history: %s\n", game);
9467
9468     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9469     gameInfo.site = StrSave(appData.icsHost);
9470     gameInfo.date = PGNDate();
9471     gameInfo.round = StrSave("-");
9472
9473     /* Parse out names of players */
9474     while (*game == ' ') game++;
9475     p = buf;
9476     while (*game != ' ') *p++ = *game++;
9477     *p = NULLCHAR;
9478     gameInfo.white = StrSave(buf);
9479     while (*game == ' ') game++;
9480     p = buf;
9481     while (*game != ' ' && *game != '\n') *p++ = *game++;
9482     *p = NULLCHAR;
9483     gameInfo.black = StrSave(buf);
9484
9485     /* Parse moves */
9486     boardIndex = blackPlaysFirst ? 1 : 0;
9487     yynewstr(game);
9488     for (;;) {
9489         yyboardindex = boardIndex;
9490         moveType = (ChessMove) Myylex();
9491         switch (moveType) {
9492           case IllegalMove:             /* maybe suicide chess, etc. */
9493   if (appData.debugMode) {
9494     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9495     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9496     setbuf(debugFP, NULL);
9497   }
9498           case WhitePromotion:
9499           case BlackPromotion:
9500           case WhiteNonPromotion:
9501           case BlackNonPromotion:
9502           case NormalMove:
9503           case FirstLeg:
9504           case WhiteCapturesEnPassant:
9505           case BlackCapturesEnPassant:
9506           case WhiteKingSideCastle:
9507           case WhiteQueenSideCastle:
9508           case BlackKingSideCastle:
9509           case BlackQueenSideCastle:
9510           case WhiteKingSideCastleWild:
9511           case WhiteQueenSideCastleWild:
9512           case BlackKingSideCastleWild:
9513           case BlackQueenSideCastleWild:
9514           /* PUSH Fabien */
9515           case WhiteHSideCastleFR:
9516           case WhiteASideCastleFR:
9517           case BlackHSideCastleFR:
9518           case BlackASideCastleFR:
9519           /* POP Fabien */
9520             fromX = currentMoveString[0] - AAA;
9521             fromY = currentMoveString[1] - ONE;
9522             toX = currentMoveString[2] - AAA;
9523             toY = currentMoveString[3] - ONE;
9524             promoChar = currentMoveString[4];
9525             break;
9526           case WhiteDrop:
9527           case BlackDrop:
9528             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9529             fromX = moveType == WhiteDrop ?
9530               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9531             (int) CharToPiece(ToLower(currentMoveString[0]));
9532             fromY = DROP_RANK;
9533             toX = currentMoveString[2] - AAA;
9534             toY = currentMoveString[3] - ONE;
9535             promoChar = NULLCHAR;
9536             break;
9537           case AmbiguousMove:
9538             /* bug? */
9539             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9540   if (appData.debugMode) {
9541     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9542     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9543     setbuf(debugFP, NULL);
9544   }
9545             DisplayError(buf, 0);
9546             return;
9547           case ImpossibleMove:
9548             /* bug? */
9549             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9550   if (appData.debugMode) {
9551     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9552     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9553     setbuf(debugFP, NULL);
9554   }
9555             DisplayError(buf, 0);
9556             return;
9557           case EndOfFile:
9558             if (boardIndex < backwardMostMove) {
9559                 /* Oops, gap.  How did that happen? */
9560                 DisplayError(_("Gap in move list"), 0);
9561                 return;
9562             }
9563             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9564             if (boardIndex > forwardMostMove) {
9565                 forwardMostMove = boardIndex;
9566             }
9567             return;
9568           case ElapsedTime:
9569             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9570                 strcat(parseList[boardIndex-1], " ");
9571                 strcat(parseList[boardIndex-1], yy_text);
9572             }
9573             continue;
9574           case Comment:
9575           case PGNTag:
9576           case NAG:
9577           default:
9578             /* ignore */
9579             continue;
9580           case WhiteWins:
9581           case BlackWins:
9582           case GameIsDrawn:
9583           case GameUnfinished:
9584             if (gameMode == IcsExamining) {
9585                 if (boardIndex < backwardMostMove) {
9586                     /* Oops, gap.  How did that happen? */
9587                     return;
9588                 }
9589                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9590                 return;
9591             }
9592             gameInfo.result = moveType;
9593             p = strchr(yy_text, '{');
9594             if (p == NULL) p = strchr(yy_text, '(');
9595             if (p == NULL) {
9596                 p = yy_text;
9597                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9598             } else {
9599                 q = strchr(p, *p == '{' ? '}' : ')');
9600                 if (q != NULL) *q = NULLCHAR;
9601                 p++;
9602             }
9603             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9604             gameInfo.resultDetails = StrSave(p);
9605             continue;
9606         }
9607         if (boardIndex >= forwardMostMove &&
9608             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9609             backwardMostMove = blackPlaysFirst ? 1 : 0;
9610             return;
9611         }
9612         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9613                                  fromY, fromX, toY, toX, promoChar,
9614                                  parseList[boardIndex]);
9615         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9616         /* currentMoveString is set as a side-effect of yylex */
9617         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9618         strcat(moveList[boardIndex], "\n");
9619         boardIndex++;
9620         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9621         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9622           case MT_NONE:
9623           case MT_STALEMATE:
9624           default:
9625             break;
9626           case MT_CHECK:
9627             if(gameInfo.variant != VariantShogi)
9628                 strcat(parseList[boardIndex - 1], "+");
9629             break;
9630           case MT_CHECKMATE:
9631           case MT_STAINMATE:
9632             strcat(parseList[boardIndex - 1], "#");
9633             break;
9634         }
9635     }
9636 }
9637
9638
9639 /* Apply a move to the given board  */
9640 void
9641 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9642 {
9643   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9644   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9645
9646     /* [HGM] compute & store e.p. status and castling rights for new position */
9647     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9648
9649       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9650       oldEP = (signed char)board[EP_STATUS];
9651       board[EP_STATUS] = EP_NONE;
9652
9653   if (fromY == DROP_RANK) {
9654         /* must be first */
9655         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9656             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9657             return;
9658         }
9659         piece = board[toY][toX] = (ChessSquare) fromX;
9660   } else {
9661       ChessSquare victim;
9662       int i;
9663
9664       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9665            victim = board[killY][killX],
9666            board[killY][killX] = EmptySquare,
9667            board[EP_STATUS] = EP_CAPTURE;
9668
9669       if( board[toY][toX] != EmptySquare ) {
9670            board[EP_STATUS] = EP_CAPTURE;
9671            if( (fromX != toX || fromY != toY) && // not igui!
9672                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9673                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9674                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9675            }
9676       }
9677
9678       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9679            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9680                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9681       } else
9682       if( board[fromY][fromX] == WhitePawn ) {
9683            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9684                board[EP_STATUS] = EP_PAWN_MOVE;
9685            if( toY-fromY==2) {
9686                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9687                         gameInfo.variant != VariantBerolina || toX < fromX)
9688                       board[EP_STATUS] = toX | berolina;
9689                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9690                         gameInfo.variant != VariantBerolina || toX > fromX)
9691                       board[EP_STATUS] = toX;
9692            }
9693       } else
9694       if( board[fromY][fromX] == BlackPawn ) {
9695            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9696                board[EP_STATUS] = EP_PAWN_MOVE;
9697            if( toY-fromY== -2) {
9698                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9699                         gameInfo.variant != VariantBerolina || toX < fromX)
9700                       board[EP_STATUS] = toX | berolina;
9701                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9702                         gameInfo.variant != VariantBerolina || toX > fromX)
9703                       board[EP_STATUS] = toX;
9704            }
9705        }
9706
9707        for(i=0; i<nrCastlingRights; i++) {
9708            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9709               board[CASTLING][i] == toX   && castlingRank[i] == toY
9710              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9711        }
9712
9713        if(gameInfo.variant == VariantSChess) { // update virginity
9714            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9715            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9716            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9717            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9718        }
9719
9720      if (fromX == toX && fromY == toY) return;
9721
9722      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9723      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9724      if(gameInfo.variant == VariantKnightmate)
9725          king += (int) WhiteUnicorn - (int) WhiteKing;
9726
9727     /* Code added by Tord: */
9728     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9729     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9730         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9731       board[fromY][fromX] = EmptySquare;
9732       board[toY][toX] = EmptySquare;
9733       if((toX > fromX) != (piece == WhiteRook)) {
9734         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9735       } else {
9736         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9737       }
9738     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9739                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9740       board[fromY][fromX] = EmptySquare;
9741       board[toY][toX] = EmptySquare;
9742       if((toX > fromX) != (piece == BlackRook)) {
9743         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9744       } else {
9745         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9746       }
9747     /* End of code added by Tord */
9748
9749     } else if (board[fromY][fromX] == king
9750         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9751         && toY == fromY && toX > fromX+1) {
9752         board[fromY][fromX] = EmptySquare;
9753         board[toY][toX] = king;
9754         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9755         board[fromY][BOARD_RGHT-1] = EmptySquare;
9756     } else if (board[fromY][fromX] == king
9757         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9758                && toY == fromY && toX < fromX-1) {
9759         board[fromY][fromX] = EmptySquare;
9760         board[toY][toX] = king;
9761         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9762         board[fromY][BOARD_LEFT] = EmptySquare;
9763     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9764                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9765                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9766                ) {
9767         /* white pawn promotion */
9768         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9769         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9770             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9771         board[fromY][fromX] = EmptySquare;
9772     } else if ((fromY >= BOARD_HEIGHT>>1)
9773                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9774                && (toX != fromX)
9775                && gameInfo.variant != VariantXiangqi
9776                && gameInfo.variant != VariantBerolina
9777                && (board[fromY][fromX] == WhitePawn)
9778                && (board[toY][toX] == EmptySquare)) {
9779         board[fromY][fromX] = EmptySquare;
9780         board[toY][toX] = WhitePawn;
9781         captured = board[toY - 1][toX];
9782         board[toY - 1][toX] = EmptySquare;
9783     } else if ((fromY == BOARD_HEIGHT-4)
9784                && (toX == fromX)
9785                && gameInfo.variant == VariantBerolina
9786                && (board[fromY][fromX] == WhitePawn)
9787                && (board[toY][toX] == EmptySquare)) {
9788         board[fromY][fromX] = EmptySquare;
9789         board[toY][toX] = WhitePawn;
9790         if(oldEP & EP_BEROLIN_A) {
9791                 captured = board[fromY][fromX-1];
9792                 board[fromY][fromX-1] = EmptySquare;
9793         }else{  captured = board[fromY][fromX+1];
9794                 board[fromY][fromX+1] = EmptySquare;
9795         }
9796     } else if (board[fromY][fromX] == king
9797         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9798                && toY == fromY && toX > fromX+1) {
9799         board[fromY][fromX] = EmptySquare;
9800         board[toY][toX] = king;
9801         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9802         board[fromY][BOARD_RGHT-1] = EmptySquare;
9803     } else if (board[fromY][fromX] == king
9804         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9805                && toY == fromY && toX < fromX-1) {
9806         board[fromY][fromX] = EmptySquare;
9807         board[toY][toX] = king;
9808         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9809         board[fromY][BOARD_LEFT] = EmptySquare;
9810     } else if (fromY == 7 && fromX == 3
9811                && board[fromY][fromX] == BlackKing
9812                && toY == 7 && toX == 5) {
9813         board[fromY][fromX] = EmptySquare;
9814         board[toY][toX] = BlackKing;
9815         board[fromY][7] = EmptySquare;
9816         board[toY][4] = BlackRook;
9817     } else if (fromY == 7 && fromX == 3
9818                && board[fromY][fromX] == BlackKing
9819                && toY == 7 && toX == 1) {
9820         board[fromY][fromX] = EmptySquare;
9821         board[toY][toX] = BlackKing;
9822         board[fromY][0] = EmptySquare;
9823         board[toY][2] = BlackRook;
9824     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9825                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9826                && toY < promoRank && promoChar
9827                ) {
9828         /* black pawn promotion */
9829         board[toY][toX] = CharToPiece(ToLower(promoChar));
9830         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9831             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9832         board[fromY][fromX] = EmptySquare;
9833     } else if ((fromY < BOARD_HEIGHT>>1)
9834                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9835                && (toX != fromX)
9836                && gameInfo.variant != VariantXiangqi
9837                && gameInfo.variant != VariantBerolina
9838                && (board[fromY][fromX] == BlackPawn)
9839                && (board[toY][toX] == EmptySquare)) {
9840         board[fromY][fromX] = EmptySquare;
9841         board[toY][toX] = BlackPawn;
9842         captured = board[toY + 1][toX];
9843         board[toY + 1][toX] = EmptySquare;
9844     } else if ((fromY == 3)
9845                && (toX == fromX)
9846                && gameInfo.variant == VariantBerolina
9847                && (board[fromY][fromX] == BlackPawn)
9848                && (board[toY][toX] == EmptySquare)) {
9849         board[fromY][fromX] = EmptySquare;
9850         board[toY][toX] = BlackPawn;
9851         if(oldEP & EP_BEROLIN_A) {
9852                 captured = board[fromY][fromX-1];
9853                 board[fromY][fromX-1] = EmptySquare;
9854         }else{  captured = board[fromY][fromX+1];
9855                 board[fromY][fromX+1] = EmptySquare;
9856         }
9857     } else {
9858         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9859         board[fromY][fromX] = EmptySquare;
9860         board[toY][toX] = piece;
9861     }
9862   }
9863
9864     if (gameInfo.holdingsWidth != 0) {
9865
9866       /* !!A lot more code needs to be written to support holdings  */
9867       /* [HGM] OK, so I have written it. Holdings are stored in the */
9868       /* penultimate board files, so they are automaticlly stored   */
9869       /* in the game history.                                       */
9870       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9871                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9872         /* Delete from holdings, by decreasing count */
9873         /* and erasing image if necessary            */
9874         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9875         if(p < (int) BlackPawn) { /* white drop */
9876              p -= (int)WhitePawn;
9877                  p = PieceToNumber((ChessSquare)p);
9878              if(p >= gameInfo.holdingsSize) p = 0;
9879              if(--board[p][BOARD_WIDTH-2] <= 0)
9880                   board[p][BOARD_WIDTH-1] = EmptySquare;
9881              if((int)board[p][BOARD_WIDTH-2] < 0)
9882                         board[p][BOARD_WIDTH-2] = 0;
9883         } else {                  /* black drop */
9884              p -= (int)BlackPawn;
9885                  p = PieceToNumber((ChessSquare)p);
9886              if(p >= gameInfo.holdingsSize) p = 0;
9887              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9888                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9889              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9890                         board[BOARD_HEIGHT-1-p][1] = 0;
9891         }
9892       }
9893       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9894           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9895         /* [HGM] holdings: Add to holdings, if holdings exist */
9896         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9897                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9898                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9899         }
9900         p = (int) captured;
9901         if (p >= (int) BlackPawn) {
9902           p -= (int)BlackPawn;
9903           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9904                   /* in Shogi restore piece to its original  first */
9905                   captured = (ChessSquare) (DEMOTED captured);
9906                   p = DEMOTED p;
9907           }
9908           p = PieceToNumber((ChessSquare)p);
9909           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9910           board[p][BOARD_WIDTH-2]++;
9911           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9912         } else {
9913           p -= (int)WhitePawn;
9914           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9915                   captured = (ChessSquare) (DEMOTED captured);
9916                   p = DEMOTED p;
9917           }
9918           p = PieceToNumber((ChessSquare)p);
9919           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9920           board[BOARD_HEIGHT-1-p][1]++;
9921           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9922         }
9923       }
9924     } else if (gameInfo.variant == VariantAtomic) {
9925       if (captured != EmptySquare) {
9926         int y, x;
9927         for (y = toY-1; y <= toY+1; y++) {
9928           for (x = toX-1; x <= toX+1; x++) {
9929             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9930                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9931               board[y][x] = EmptySquare;
9932             }
9933           }
9934         }
9935         board[toY][toX] = EmptySquare;
9936       }
9937     }
9938     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9939         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9940     } else
9941     if(promoChar == '+') {
9942         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9943         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9944     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9945         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9946         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9947            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9948         board[toY][toX] = newPiece;
9949     }
9950     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9951                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9952         // [HGM] superchess: take promotion piece out of holdings
9953         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9954         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9955             if(!--board[k][BOARD_WIDTH-2])
9956                 board[k][BOARD_WIDTH-1] = EmptySquare;
9957         } else {
9958             if(!--board[BOARD_HEIGHT-1-k][1])
9959                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9960         }
9961     }
9962 }
9963
9964 /* Updates forwardMostMove */
9965 void
9966 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9967 {
9968     int x = toX, y = toY;
9969     char *s = parseList[forwardMostMove];
9970     ChessSquare p = boards[forwardMostMove][toY][toX];
9971 //    forwardMostMove++; // [HGM] bare: moved downstream
9972
9973     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9974     (void) CoordsToAlgebraic(boards[forwardMostMove],
9975                              PosFlags(forwardMostMove),
9976                              fromY, fromX, y, x, promoChar,
9977                              s);
9978     if(killX >= 0 && killY >= 0)
9979         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9980
9981     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9982         int timeLeft; static int lastLoadFlag=0; int king, piece;
9983         piece = boards[forwardMostMove][fromY][fromX];
9984         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9985         if(gameInfo.variant == VariantKnightmate)
9986             king += (int) WhiteUnicorn - (int) WhiteKing;
9987         if(forwardMostMove == 0) {
9988             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9989                 fprintf(serverMoves, "%s;", UserName());
9990             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9991                 fprintf(serverMoves, "%s;", second.tidy);
9992             fprintf(serverMoves, "%s;", first.tidy);
9993             if(gameMode == MachinePlaysWhite)
9994                 fprintf(serverMoves, "%s;", UserName());
9995             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9996                 fprintf(serverMoves, "%s;", second.tidy);
9997         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9998         lastLoadFlag = loadFlag;
9999         // print base move
10000         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10001         // print castling suffix
10002         if( toY == fromY && piece == king ) {
10003             if(toX-fromX > 1)
10004                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10005             if(fromX-toX >1)
10006                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10007         }
10008         // e.p. suffix
10009         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10010              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10011              boards[forwardMostMove][toY][toX] == EmptySquare
10012              && fromX != toX && fromY != toY)
10013                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10014         // promotion suffix
10015         if(promoChar != NULLCHAR) {
10016             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10017                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10018                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10019             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10020         }
10021         if(!loadFlag) {
10022                 char buf[MOVE_LEN*2], *p; int len;
10023             fprintf(serverMoves, "/%d/%d",
10024                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10025             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10026             else                      timeLeft = blackTimeRemaining/1000;
10027             fprintf(serverMoves, "/%d", timeLeft);
10028                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10029                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10030                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10031                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10032             fprintf(serverMoves, "/%s", buf);
10033         }
10034         fflush(serverMoves);
10035     }
10036
10037     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10038         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10039       return;
10040     }
10041     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10042     if (commentList[forwardMostMove+1] != NULL) {
10043         free(commentList[forwardMostMove+1]);
10044         commentList[forwardMostMove+1] = NULL;
10045     }
10046     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10047     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10048     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10049     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10050     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10051     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10052     adjustedClock = FALSE;
10053     gameInfo.result = GameUnfinished;
10054     if (gameInfo.resultDetails != NULL) {
10055         free(gameInfo.resultDetails);
10056         gameInfo.resultDetails = NULL;
10057     }
10058     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10059                               moveList[forwardMostMove - 1]);
10060     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10061       case MT_NONE:
10062       case MT_STALEMATE:
10063       default:
10064         break;
10065       case MT_CHECK:
10066         if(gameInfo.variant != VariantShogi)
10067             strcat(parseList[forwardMostMove - 1], "+");
10068         break;
10069       case MT_CHECKMATE:
10070       case MT_STAINMATE:
10071         strcat(parseList[forwardMostMove - 1], "#");
10072         break;
10073     }
10074 }
10075
10076 /* Updates currentMove if not pausing */
10077 void
10078 ShowMove (int fromX, int fromY, int toX, int toY)
10079 {
10080     int instant = (gameMode == PlayFromGameFile) ?
10081         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10082     if(appData.noGUI) return;
10083     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10084         if (!instant) {
10085             if (forwardMostMove == currentMove + 1) {
10086                 AnimateMove(boards[forwardMostMove - 1],
10087                             fromX, fromY, toX, toY);
10088             }
10089         }
10090         currentMove = forwardMostMove;
10091     }
10092
10093     killX = killY = -1; // [HGM] lion: used up
10094
10095     if (instant) return;
10096
10097     DisplayMove(currentMove - 1);
10098     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10099             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10100                 SetHighlights(fromX, fromY, toX, toY);
10101             }
10102     }
10103     DrawPosition(FALSE, boards[currentMove]);
10104     DisplayBothClocks();
10105     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10106 }
10107
10108 void
10109 SendEgtPath (ChessProgramState *cps)
10110 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10111         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10112
10113         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10114
10115         while(*p) {
10116             char c, *q = name+1, *r, *s;
10117
10118             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10119             while(*p && *p != ',') *q++ = *p++;
10120             *q++ = ':'; *q = 0;
10121             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10122                 strcmp(name, ",nalimov:") == 0 ) {
10123                 // take nalimov path from the menu-changeable option first, if it is defined
10124               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10125                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10126             } else
10127             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10128                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10129                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10130                 s = r = StrStr(s, ":") + 1; // beginning of path info
10131                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10132                 c = *r; *r = 0;             // temporarily null-terminate path info
10133                     *--q = 0;               // strip of trailig ':' from name
10134                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10135                 *r = c;
10136                 SendToProgram(buf,cps);     // send egtbpath command for this format
10137             }
10138             if(*p == ',') p++; // read away comma to position for next format name
10139         }
10140 }
10141
10142 static int
10143 NonStandardBoardSize ()
10144 {
10145       /* [HGM] Awkward testing. Should really be a table */
10146       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10147       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10148       if( gameInfo.variant == VariantXiangqi )
10149            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10150       if( gameInfo.variant == VariantShogi )
10151            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10152       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10153            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10154       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10155           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10156            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10157       if( gameInfo.variant == VariantCourier )
10158            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10159       if( gameInfo.variant == VariantSuper )
10160            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10161       if( gameInfo.variant == VariantGreat )
10162            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10163       if( gameInfo.variant == VariantSChess )
10164            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10165       if( gameInfo.variant == VariantGrand )
10166            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10167       if( gameInfo.variant == VariantChu )
10168            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10169       return overruled;
10170 }
10171
10172 void
10173 InitChessProgram (ChessProgramState *cps, int setup)
10174 /* setup needed to setup FRC opening position */
10175 {
10176     char buf[MSG_SIZ], b[MSG_SIZ];
10177     if (appData.noChessProgram) return;
10178     hintRequested = FALSE;
10179     bookRequested = FALSE;
10180
10181     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10182     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10183     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10184     if(cps->memSize) { /* [HGM] memory */
10185       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10186         SendToProgram(buf, cps);
10187     }
10188     SendEgtPath(cps); /* [HGM] EGT */
10189     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10190       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10191         SendToProgram(buf, cps);
10192     }
10193
10194     SendToProgram(cps->initString, cps);
10195     if (gameInfo.variant != VariantNormal &&
10196         gameInfo.variant != VariantLoadable
10197         /* [HGM] also send variant if board size non-standard */
10198         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10199                                             ) {
10200       char *v = VariantName(gameInfo.variant);
10201       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10202         /* [HGM] in protocol 1 we have to assume all variants valid */
10203         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10204         DisplayFatalError(buf, 0, 1);
10205         return;
10206       }
10207
10208       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10209         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10210                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10211            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10212            if(StrStr(cps->variants, b) == NULL) {
10213                // specific sized variant not known, check if general sizing allowed
10214                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10215                    if(StrStr(cps->variants, "boardsize") == NULL) {
10216                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10217                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10218                        DisplayFatalError(buf, 0, 1);
10219                        return;
10220                    }
10221                    /* [HGM] here we really should compare with the maximum supported board size */
10222                }
10223            }
10224       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10225       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10226       SendToProgram(buf, cps);
10227     }
10228     currentlyInitializedVariant = gameInfo.variant;
10229
10230     /* [HGM] send opening position in FRC to first engine */
10231     if(setup) {
10232           SendToProgram("force\n", cps);
10233           SendBoard(cps, 0);
10234           /* engine is now in force mode! Set flag to wake it up after first move. */
10235           setboardSpoiledMachineBlack = 1;
10236     }
10237
10238     if (cps->sendICS) {
10239       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10240       SendToProgram(buf, cps);
10241     }
10242     cps->maybeThinking = FALSE;
10243     cps->offeredDraw = 0;
10244     if (!appData.icsActive) {
10245         SendTimeControl(cps, movesPerSession, timeControl,
10246                         timeIncrement, appData.searchDepth,
10247                         searchTime);
10248     }
10249     if (appData.showThinking
10250         // [HGM] thinking: four options require thinking output to be sent
10251         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10252                                 ) {
10253         SendToProgram("post\n", cps);
10254     }
10255     SendToProgram("hard\n", cps);
10256     if (!appData.ponderNextMove) {
10257         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10258            it without being sure what state we are in first.  "hard"
10259            is not a toggle, so that one is OK.
10260          */
10261         SendToProgram("easy\n", cps);
10262     }
10263     if (cps->usePing) {
10264       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10265       SendToProgram(buf, cps);
10266     }
10267     cps->initDone = TRUE;
10268     ClearEngineOutputPane(cps == &second);
10269 }
10270
10271
10272 void
10273 ResendOptions (ChessProgramState *cps)
10274 { // send the stored value of the options
10275   int i;
10276   char buf[MSG_SIZ];
10277   Option *opt = cps->option;
10278   for(i=0; i<cps->nrOptions; i++, opt++) {
10279       switch(opt->type) {
10280         case Spin:
10281         case Slider:
10282         case CheckBox:
10283             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10284           break;
10285         case ComboBox:
10286           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10287           break;
10288         default:
10289             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10290           break;
10291         case Button:
10292         case SaveButton:
10293           continue;
10294       }
10295       SendToProgram(buf, cps);
10296   }
10297 }
10298
10299 void
10300 StartChessProgram (ChessProgramState *cps)
10301 {
10302     char buf[MSG_SIZ];
10303     int err;
10304
10305     if (appData.noChessProgram) return;
10306     cps->initDone = FALSE;
10307
10308     if (strcmp(cps->host, "localhost") == 0) {
10309         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10310     } else if (*appData.remoteShell == NULLCHAR) {
10311         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10312     } else {
10313         if (*appData.remoteUser == NULLCHAR) {
10314           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10315                     cps->program);
10316         } else {
10317           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10318                     cps->host, appData.remoteUser, cps->program);
10319         }
10320         err = StartChildProcess(buf, "", &cps->pr);
10321     }
10322
10323     if (err != 0) {
10324       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10325         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10326         if(cps != &first) return;
10327         appData.noChessProgram = TRUE;
10328         ThawUI();
10329         SetNCPMode();
10330 //      DisplayFatalError(buf, err, 1);
10331 //      cps->pr = NoProc;
10332 //      cps->isr = NULL;
10333         return;
10334     }
10335
10336     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10337     if (cps->protocolVersion > 1) {
10338       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10339       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10340         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10341         cps->comboCnt = 0;  //                and values of combo boxes
10342       }
10343       SendToProgram(buf, cps);
10344       if(cps->reload) ResendOptions(cps);
10345     } else {
10346       SendToProgram("xboard\n", cps);
10347     }
10348 }
10349
10350 void
10351 TwoMachinesEventIfReady P((void))
10352 {
10353   static int curMess = 0;
10354   if (first.lastPing != first.lastPong) {
10355     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10356     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10357     return;
10358   }
10359   if (second.lastPing != second.lastPong) {
10360     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10361     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10362     return;
10363   }
10364   DisplayMessage("", ""); curMess = 0;
10365   TwoMachinesEvent();
10366 }
10367
10368 char *
10369 MakeName (char *template)
10370 {
10371     time_t clock;
10372     struct tm *tm;
10373     static char buf[MSG_SIZ];
10374     char *p = buf;
10375     int i;
10376
10377     clock = time((time_t *)NULL);
10378     tm = localtime(&clock);
10379
10380     while(*p++ = *template++) if(p[-1] == '%') {
10381         switch(*template++) {
10382           case 0:   *p = 0; return buf;
10383           case 'Y': i = tm->tm_year+1900; break;
10384           case 'y': i = tm->tm_year-100; break;
10385           case 'M': i = tm->tm_mon+1; break;
10386           case 'd': i = tm->tm_mday; break;
10387           case 'h': i = tm->tm_hour; break;
10388           case 'm': i = tm->tm_min; break;
10389           case 's': i = tm->tm_sec; break;
10390           default:  i = 0;
10391         }
10392         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10393     }
10394     return buf;
10395 }
10396
10397 int
10398 CountPlayers (char *p)
10399 {
10400     int n = 0;
10401     while(p = strchr(p, '\n')) p++, n++; // count participants
10402     return n;
10403 }
10404
10405 FILE *
10406 WriteTourneyFile (char *results, FILE *f)
10407 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10408     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10409     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10410         // create a file with tournament description
10411         fprintf(f, "-participants {%s}\n", appData.participants);
10412         fprintf(f, "-seedBase %d\n", appData.seedBase);
10413         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10414         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10415         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10416         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10417         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10418         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10419         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10420         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10421         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10422         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10423         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10424         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10425         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10426         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10427         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10428         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10429         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10430         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10431         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10432         fprintf(f, "-smpCores %d\n", appData.smpCores);
10433         if(searchTime > 0)
10434                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10435         else {
10436                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10437                 fprintf(f, "-tc %s\n", appData.timeControl);
10438                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10439         }
10440         fprintf(f, "-results \"%s\"\n", results);
10441     }
10442     return f;
10443 }
10444
10445 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10446
10447 void
10448 Substitute (char *participants, int expunge)
10449 {
10450     int i, changed, changes=0, nPlayers=0;
10451     char *p, *q, *r, buf[MSG_SIZ];
10452     if(participants == NULL) return;
10453     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10454     r = p = participants; q = appData.participants;
10455     while(*p && *p == *q) {
10456         if(*p == '\n') r = p+1, nPlayers++;
10457         p++; q++;
10458     }
10459     if(*p) { // difference
10460         while(*p && *p++ != '\n');
10461         while(*q && *q++ != '\n');
10462       changed = nPlayers;
10463         changes = 1 + (strcmp(p, q) != 0);
10464     }
10465     if(changes == 1) { // a single engine mnemonic was changed
10466         q = r; while(*q) nPlayers += (*q++ == '\n');
10467         p = buf; while(*r && (*p = *r++) != '\n') p++;
10468         *p = NULLCHAR;
10469         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10470         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10471         if(mnemonic[i]) { // The substitute is valid
10472             FILE *f;
10473             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10474                 flock(fileno(f), LOCK_EX);
10475                 ParseArgsFromFile(f);
10476                 fseek(f, 0, SEEK_SET);
10477                 FREE(appData.participants); appData.participants = participants;
10478                 if(expunge) { // erase results of replaced engine
10479                     int len = strlen(appData.results), w, b, dummy;
10480                     for(i=0; i<len; i++) {
10481                         Pairing(i, nPlayers, &w, &b, &dummy);
10482                         if((w == changed || b == changed) && appData.results[i] == '*') {
10483                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10484                             fclose(f);
10485                             return;
10486                         }
10487                     }
10488                     for(i=0; i<len; i++) {
10489                         Pairing(i, nPlayers, &w, &b, &dummy);
10490                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10491                     }
10492                 }
10493                 WriteTourneyFile(appData.results, f);
10494                 fclose(f); // release lock
10495                 return;
10496             }
10497         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10498     }
10499     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10500     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10501     free(participants);
10502     return;
10503 }
10504
10505 int
10506 CheckPlayers (char *participants)
10507 {
10508         int i;
10509         char buf[MSG_SIZ], *p;
10510         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10511         while(p = strchr(participants, '\n')) {
10512             *p = NULLCHAR;
10513             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10514             if(!mnemonic[i]) {
10515                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10516                 *p = '\n';
10517                 DisplayError(buf, 0);
10518                 return 1;
10519             }
10520             *p = '\n';
10521             participants = p + 1;
10522         }
10523         return 0;
10524 }
10525
10526 int
10527 CreateTourney (char *name)
10528 {
10529         FILE *f;
10530         if(matchMode && strcmp(name, appData.tourneyFile)) {
10531              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10532         }
10533         if(name[0] == NULLCHAR) {
10534             if(appData.participants[0])
10535                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10536             return 0;
10537         }
10538         f = fopen(name, "r");
10539         if(f) { // file exists
10540             ASSIGN(appData.tourneyFile, name);
10541             ParseArgsFromFile(f); // parse it
10542         } else {
10543             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10544             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10545                 DisplayError(_("Not enough participants"), 0);
10546                 return 0;
10547             }
10548             if(CheckPlayers(appData.participants)) return 0;
10549             ASSIGN(appData.tourneyFile, name);
10550             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10551             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10552         }
10553         fclose(f);
10554         appData.noChessProgram = FALSE;
10555         appData.clockMode = TRUE;
10556         SetGNUMode();
10557         return 1;
10558 }
10559
10560 int
10561 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10562 {
10563     char buf[MSG_SIZ], *p, *q;
10564     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10565     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10566     skip = !all && group[0]; // if group requested, we start in skip mode
10567     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10568         p = names; q = buf; header = 0;
10569         while(*p && *p != '\n') *q++ = *p++;
10570         *q = 0;
10571         if(*p == '\n') p++;
10572         if(buf[0] == '#') {
10573             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10574             depth++; // we must be entering a new group
10575             if(all) continue; // suppress printing group headers when complete list requested
10576             header = 1;
10577             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10578         }
10579         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10580         if(engineList[i]) free(engineList[i]);
10581         engineList[i] = strdup(buf);
10582         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10583         if(engineMnemonic[i]) free(engineMnemonic[i]);
10584         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10585             strcat(buf, " (");
10586             sscanf(q + 8, "%s", buf + strlen(buf));
10587             strcat(buf, ")");
10588         }
10589         engineMnemonic[i] = strdup(buf);
10590         i++;
10591     }
10592     engineList[i] = engineMnemonic[i] = NULL;
10593     return i;
10594 }
10595
10596 // following implemented as macro to avoid type limitations
10597 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10598
10599 void
10600 SwapEngines (int n)
10601 {   // swap settings for first engine and other engine (so far only some selected options)
10602     int h;
10603     char *p;
10604     if(n == 0) return;
10605     SWAP(directory, p)
10606     SWAP(chessProgram, p)
10607     SWAP(isUCI, h)
10608     SWAP(hasOwnBookUCI, h)
10609     SWAP(protocolVersion, h)
10610     SWAP(reuse, h)
10611     SWAP(scoreIsAbsolute, h)
10612     SWAP(timeOdds, h)
10613     SWAP(logo, p)
10614     SWAP(pgnName, p)
10615     SWAP(pvSAN, h)
10616     SWAP(engOptions, p)
10617     SWAP(engInitString, p)
10618     SWAP(computerString, p)
10619     SWAP(features, p)
10620     SWAP(fenOverride, p)
10621     SWAP(NPS, h)
10622     SWAP(accumulateTC, h)
10623     SWAP(host, p)
10624 }
10625
10626 int
10627 GetEngineLine (char *s, int n)
10628 {
10629     int i;
10630     char buf[MSG_SIZ];
10631     extern char *icsNames;
10632     if(!s || !*s) return 0;
10633     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10634     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10635     if(!mnemonic[i]) return 0;
10636     if(n == 11) return 1; // just testing if there was a match
10637     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10638     if(n == 1) SwapEngines(n);
10639     ParseArgsFromString(buf);
10640     if(n == 1) SwapEngines(n);
10641     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10642         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10643         ParseArgsFromString(buf);
10644     }
10645     return 1;
10646 }
10647
10648 int
10649 SetPlayer (int player, char *p)
10650 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10651     int i;
10652     char buf[MSG_SIZ], *engineName;
10653     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10654     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10655     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10656     if(mnemonic[i]) {
10657         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10658         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10659         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10660         ParseArgsFromString(buf);
10661     } else { // no engine with this nickname is installed!
10662         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10663         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10664         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10665         ModeHighlight();
10666         DisplayError(buf, 0);
10667         return 0;
10668     }
10669     free(engineName);
10670     return i;
10671 }
10672
10673 char *recentEngines;
10674
10675 void
10676 RecentEngineEvent (int nr)
10677 {
10678     int n;
10679 //    SwapEngines(1); // bump first to second
10680 //    ReplaceEngine(&second, 1); // and load it there
10681     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10682     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10683     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10684         ReplaceEngine(&first, 0);
10685         FloatToFront(&appData.recentEngineList, command[n]);
10686     }
10687 }
10688
10689 int
10690 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10691 {   // determine players from game number
10692     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10693
10694     if(appData.tourneyType == 0) {
10695         roundsPerCycle = (nPlayers - 1) | 1;
10696         pairingsPerRound = nPlayers / 2;
10697     } else if(appData.tourneyType > 0) {
10698         roundsPerCycle = nPlayers - appData.tourneyType;
10699         pairingsPerRound = appData.tourneyType;
10700     }
10701     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10702     gamesPerCycle = gamesPerRound * roundsPerCycle;
10703     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10704     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10705     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10706     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10707     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10708     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10709
10710     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10711     if(appData.roundSync) *syncInterval = gamesPerRound;
10712
10713     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10714
10715     if(appData.tourneyType == 0) {
10716         if(curPairing == (nPlayers-1)/2 ) {
10717             *whitePlayer = curRound;
10718             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10719         } else {
10720             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10721             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10722             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10723             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10724         }
10725     } else if(appData.tourneyType > 1) {
10726         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10727         *whitePlayer = curRound + appData.tourneyType;
10728     } else if(appData.tourneyType > 0) {
10729         *whitePlayer = curPairing;
10730         *blackPlayer = curRound + appData.tourneyType;
10731     }
10732
10733     // take care of white/black alternation per round.
10734     // For cycles and games this is already taken care of by default, derived from matchGame!
10735     return curRound & 1;
10736 }
10737
10738 int
10739 NextTourneyGame (int nr, int *swapColors)
10740 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10741     char *p, *q;
10742     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10743     FILE *tf;
10744     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10745     tf = fopen(appData.tourneyFile, "r");
10746     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10747     ParseArgsFromFile(tf); fclose(tf);
10748     InitTimeControls(); // TC might be altered from tourney file
10749
10750     nPlayers = CountPlayers(appData.participants); // count participants
10751     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10752     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10753
10754     if(syncInterval) {
10755         p = q = appData.results;
10756         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10757         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10758             DisplayMessage(_("Waiting for other game(s)"),"");
10759             waitingForGame = TRUE;
10760             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10761             return 0;
10762         }
10763         waitingForGame = FALSE;
10764     }
10765
10766     if(appData.tourneyType < 0) {
10767         if(nr>=0 && !pairingReceived) {
10768             char buf[1<<16];
10769             if(pairing.pr == NoProc) {
10770                 if(!appData.pairingEngine[0]) {
10771                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10772                     return 0;
10773                 }
10774                 StartChessProgram(&pairing); // starts the pairing engine
10775             }
10776             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10777             SendToProgram(buf, &pairing);
10778             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10779             SendToProgram(buf, &pairing);
10780             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10781         }
10782         pairingReceived = 0;                              // ... so we continue here
10783         *swapColors = 0;
10784         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10785         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10786         matchGame = 1; roundNr = nr / syncInterval + 1;
10787     }
10788
10789     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10790
10791     // redefine engines, engine dir, etc.
10792     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10793     if(first.pr == NoProc) {
10794       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10795       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10796     }
10797     if(second.pr == NoProc) {
10798       SwapEngines(1);
10799       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10800       SwapEngines(1);         // and make that valid for second engine by swapping
10801       InitEngine(&second, 1);
10802     }
10803     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10804     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10805     return OK;
10806 }
10807
10808 void
10809 NextMatchGame ()
10810 {   // performs game initialization that does not invoke engines, and then tries to start the game
10811     int res, firstWhite, swapColors = 0;
10812     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10813     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
10814         char buf[MSG_SIZ];
10815         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10816         if(strcmp(buf, currentDebugFile)) { // name has changed
10817             FILE *f = fopen(buf, "w");
10818             if(f) { // if opening the new file failed, just keep using the old one
10819                 ASSIGN(currentDebugFile, buf);
10820                 fclose(debugFP);
10821                 debugFP = f;
10822             }
10823             if(appData.serverFileName) {
10824                 if(serverFP) fclose(serverFP);
10825                 serverFP = fopen(appData.serverFileName, "w");
10826                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10827                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10828             }
10829         }
10830     }
10831     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10832     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10833     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10834     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10835     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10836     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10837     Reset(FALSE, first.pr != NoProc);
10838     res = LoadGameOrPosition(matchGame); // setup game
10839     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10840     if(!res) return; // abort when bad game/pos file
10841     TwoMachinesEvent();
10842 }
10843
10844 void
10845 UserAdjudicationEvent (int result)
10846 {
10847     ChessMove gameResult = GameIsDrawn;
10848
10849     if( result > 0 ) {
10850         gameResult = WhiteWins;
10851     }
10852     else if( result < 0 ) {
10853         gameResult = BlackWins;
10854     }
10855
10856     if( gameMode == TwoMachinesPlay ) {
10857         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10858     }
10859 }
10860
10861
10862 // [HGM] save: calculate checksum of game to make games easily identifiable
10863 int
10864 StringCheckSum (char *s)
10865 {
10866         int i = 0;
10867         if(s==NULL) return 0;
10868         while(*s) i = i*259 + *s++;
10869         return i;
10870 }
10871
10872 int
10873 GameCheckSum ()
10874 {
10875         int i, sum=0;
10876         for(i=backwardMostMove; i<forwardMostMove; i++) {
10877                 sum += pvInfoList[i].depth;
10878                 sum += StringCheckSum(parseList[i]);
10879                 sum += StringCheckSum(commentList[i]);
10880                 sum *= 261;
10881         }
10882         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10883         return sum + StringCheckSum(commentList[i]);
10884 } // end of save patch
10885
10886 void
10887 GameEnds (ChessMove result, char *resultDetails, int whosays)
10888 {
10889     GameMode nextGameMode;
10890     int isIcsGame;
10891     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10892
10893     if(endingGame) return; /* [HGM] crash: forbid recursion */
10894     endingGame = 1;
10895     if(twoBoards) { // [HGM] dual: switch back to one board
10896         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10897         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10898     }
10899     if (appData.debugMode) {
10900       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10901               result, resultDetails ? resultDetails : "(null)", whosays);
10902     }
10903
10904     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10905
10906     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10907
10908     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10909         /* If we are playing on ICS, the server decides when the
10910            game is over, but the engine can offer to draw, claim
10911            a draw, or resign.
10912          */
10913 #if ZIPPY
10914         if (appData.zippyPlay && first.initDone) {
10915             if (result == GameIsDrawn) {
10916                 /* In case draw still needs to be claimed */
10917                 SendToICS(ics_prefix);
10918                 SendToICS("draw\n");
10919             } else if (StrCaseStr(resultDetails, "resign")) {
10920                 SendToICS(ics_prefix);
10921                 SendToICS("resign\n");
10922             }
10923         }
10924 #endif
10925         endingGame = 0; /* [HGM] crash */
10926         return;
10927     }
10928
10929     /* If we're loading the game from a file, stop */
10930     if (whosays == GE_FILE) {
10931       (void) StopLoadGameTimer();
10932       gameFileFP = NULL;
10933     }
10934
10935     /* Cancel draw offers */
10936     first.offeredDraw = second.offeredDraw = 0;
10937
10938     /* If this is an ICS game, only ICS can really say it's done;
10939        if not, anyone can. */
10940     isIcsGame = (gameMode == IcsPlayingWhite ||
10941                  gameMode == IcsPlayingBlack ||
10942                  gameMode == IcsObserving    ||
10943                  gameMode == IcsExamining);
10944
10945     if (!isIcsGame || whosays == GE_ICS) {
10946         /* OK -- not an ICS game, or ICS said it was done */
10947         StopClocks();
10948         if (!isIcsGame && !appData.noChessProgram)
10949           SetUserThinkingEnables();
10950
10951         /* [HGM] if a machine claims the game end we verify this claim */
10952         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10953             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10954                 char claimer;
10955                 ChessMove trueResult = (ChessMove) -1;
10956
10957                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10958                                             first.twoMachinesColor[0] :
10959                                             second.twoMachinesColor[0] ;
10960
10961                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10962                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10963                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10964                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10965                 } else
10966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10967                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10968                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10969                 } else
10970                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10971                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10972                 }
10973
10974                 // now verify win claims, but not in drop games, as we don't understand those yet
10975                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10976                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10977                     (result == WhiteWins && claimer == 'w' ||
10978                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10979                       if (appData.debugMode) {
10980                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10981                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10982                       }
10983                       if(result != trueResult) {
10984                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10985                               result = claimer == 'w' ? BlackWins : WhiteWins;
10986                               resultDetails = buf;
10987                       }
10988                 } else
10989                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10990                     && (forwardMostMove <= backwardMostMove ||
10991                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10992                         (claimer=='b')==(forwardMostMove&1))
10993                                                                                   ) {
10994                       /* [HGM] verify: draws that were not flagged are false claims */
10995                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10996                       result = claimer == 'w' ? BlackWins : WhiteWins;
10997                       resultDetails = buf;
10998                 }
10999                 /* (Claiming a loss is accepted no questions asked!) */
11000             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11001                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11002                 result = GameUnfinished;
11003                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11004             }
11005             /* [HGM] bare: don't allow bare King to win */
11006             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11007                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11008                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11009                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11010                && result != GameIsDrawn)
11011             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11012                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11013                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11014                         if(p >= 0 && p <= (int)WhiteKing) k++;
11015                 }
11016                 if (appData.debugMode) {
11017                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11018                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11019                 }
11020                 if(k <= 1) {
11021                         result = GameIsDrawn;
11022                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11023                         resultDetails = buf;
11024                 }
11025             }
11026         }
11027
11028
11029         if(serverMoves != NULL && !loadFlag) { char c = '=';
11030             if(result==WhiteWins) c = '+';
11031             if(result==BlackWins) c = '-';
11032             if(resultDetails != NULL)
11033                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11034         }
11035         if (resultDetails != NULL) {
11036             gameInfo.result = result;
11037             gameInfo.resultDetails = StrSave(resultDetails);
11038
11039             /* display last move only if game was not loaded from file */
11040             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11041                 DisplayMove(currentMove - 1);
11042
11043             if (forwardMostMove != 0) {
11044                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11045                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11046                                                                 ) {
11047                     if (*appData.saveGameFile != NULLCHAR) {
11048                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11049                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11050                         else
11051                         SaveGameToFile(appData.saveGameFile, TRUE);
11052                     } else if (appData.autoSaveGames) {
11053                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11054                     }
11055                     if (*appData.savePositionFile != NULLCHAR) {
11056                         SavePositionToFile(appData.savePositionFile);
11057                     }
11058                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11059                 }
11060             }
11061
11062             /* Tell program how game ended in case it is learning */
11063             /* [HGM] Moved this to after saving the PGN, just in case */
11064             /* engine died and we got here through time loss. In that */
11065             /* case we will get a fatal error writing the pipe, which */
11066             /* would otherwise lose us the PGN.                       */
11067             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11068             /* output during GameEnds should never be fatal anymore   */
11069             if (gameMode == MachinePlaysWhite ||
11070                 gameMode == MachinePlaysBlack ||
11071                 gameMode == TwoMachinesPlay ||
11072                 gameMode == IcsPlayingWhite ||
11073                 gameMode == IcsPlayingBlack ||
11074                 gameMode == BeginningOfGame) {
11075                 char buf[MSG_SIZ];
11076                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11077                         resultDetails);
11078                 if (first.pr != NoProc) {
11079                     SendToProgram(buf, &first);
11080                 }
11081                 if (second.pr != NoProc &&
11082                     gameMode == TwoMachinesPlay) {
11083                     SendToProgram(buf, &second);
11084                 }
11085             }
11086         }
11087
11088         if (appData.icsActive) {
11089             if (appData.quietPlay &&
11090                 (gameMode == IcsPlayingWhite ||
11091                  gameMode == IcsPlayingBlack)) {
11092                 SendToICS(ics_prefix);
11093                 SendToICS("set shout 1\n");
11094             }
11095             nextGameMode = IcsIdle;
11096             ics_user_moved = FALSE;
11097             /* clean up premove.  It's ugly when the game has ended and the
11098              * premove highlights are still on the board.
11099              */
11100             if (gotPremove) {
11101               gotPremove = FALSE;
11102               ClearPremoveHighlights();
11103               DrawPosition(FALSE, boards[currentMove]);
11104             }
11105             if (whosays == GE_ICS) {
11106                 switch (result) {
11107                 case WhiteWins:
11108                     if (gameMode == IcsPlayingWhite)
11109                         PlayIcsWinSound();
11110                     else if(gameMode == IcsPlayingBlack)
11111                         PlayIcsLossSound();
11112                     break;
11113                 case BlackWins:
11114                     if (gameMode == IcsPlayingBlack)
11115                         PlayIcsWinSound();
11116                     else if(gameMode == IcsPlayingWhite)
11117                         PlayIcsLossSound();
11118                     break;
11119                 case GameIsDrawn:
11120                     PlayIcsDrawSound();
11121                     break;
11122                 default:
11123                     PlayIcsUnfinishedSound();
11124                 }
11125             }
11126             if(appData.quitNext) { ExitEvent(0); return; }
11127         } else if (gameMode == EditGame ||
11128                    gameMode == PlayFromGameFile ||
11129                    gameMode == AnalyzeMode ||
11130                    gameMode == AnalyzeFile) {
11131             nextGameMode = gameMode;
11132         } else {
11133             nextGameMode = EndOfGame;
11134         }
11135         pausing = FALSE;
11136         ModeHighlight();
11137     } else {
11138         nextGameMode = gameMode;
11139     }
11140
11141     if (appData.noChessProgram) {
11142         gameMode = nextGameMode;
11143         ModeHighlight();
11144         endingGame = 0; /* [HGM] crash */
11145         return;
11146     }
11147
11148     if (first.reuse) {
11149         /* Put first chess program into idle state */
11150         if (first.pr != NoProc &&
11151             (gameMode == MachinePlaysWhite ||
11152              gameMode == MachinePlaysBlack ||
11153              gameMode == TwoMachinesPlay ||
11154              gameMode == IcsPlayingWhite ||
11155              gameMode == IcsPlayingBlack ||
11156              gameMode == BeginningOfGame)) {
11157             SendToProgram("force\n", &first);
11158             if (first.usePing) {
11159               char buf[MSG_SIZ];
11160               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11161               SendToProgram(buf, &first);
11162             }
11163         }
11164     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11165         /* Kill off first chess program */
11166         if (first.isr != NULL)
11167           RemoveInputSource(first.isr);
11168         first.isr = NULL;
11169
11170         if (first.pr != NoProc) {
11171             ExitAnalyzeMode();
11172             DoSleep( appData.delayBeforeQuit );
11173             SendToProgram("quit\n", &first);
11174             DoSleep( appData.delayAfterQuit );
11175             DestroyChildProcess(first.pr, first.useSigterm);
11176             first.reload = TRUE;
11177         }
11178         first.pr = NoProc;
11179     }
11180     if (second.reuse) {
11181         /* Put second chess program into idle state */
11182         if (second.pr != NoProc &&
11183             gameMode == TwoMachinesPlay) {
11184             SendToProgram("force\n", &second);
11185             if (second.usePing) {
11186               char buf[MSG_SIZ];
11187               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11188               SendToProgram(buf, &second);
11189             }
11190         }
11191     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11192         /* Kill off second chess program */
11193         if (second.isr != NULL)
11194           RemoveInputSource(second.isr);
11195         second.isr = NULL;
11196
11197         if (second.pr != NoProc) {
11198             DoSleep( appData.delayBeforeQuit );
11199             SendToProgram("quit\n", &second);
11200             DoSleep( appData.delayAfterQuit );
11201             DestroyChildProcess(second.pr, second.useSigterm);
11202             second.reload = TRUE;
11203         }
11204         second.pr = NoProc;
11205     }
11206
11207     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11208         char resChar = '=';
11209         switch (result) {
11210         case WhiteWins:
11211           resChar = '+';
11212           if (first.twoMachinesColor[0] == 'w') {
11213             first.matchWins++;
11214           } else {
11215             second.matchWins++;
11216           }
11217           break;
11218         case BlackWins:
11219           resChar = '-';
11220           if (first.twoMachinesColor[0] == 'b') {
11221             first.matchWins++;
11222           } else {
11223             second.matchWins++;
11224           }
11225           break;
11226         case GameUnfinished:
11227           resChar = ' ';
11228         default:
11229           break;
11230         }
11231
11232         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11233         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11234             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11235             ReserveGame(nextGame, resChar); // sets nextGame
11236             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11237             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11238         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11239
11240         if (nextGame <= appData.matchGames && !abortMatch) {
11241             gameMode = nextGameMode;
11242             matchGame = nextGame; // this will be overruled in tourney mode!
11243             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11244             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11245             endingGame = 0; /* [HGM] crash */
11246             return;
11247         } else {
11248             gameMode = nextGameMode;
11249             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11250                      first.tidy, second.tidy,
11251                      first.matchWins, second.matchWins,
11252                      appData.matchGames - (first.matchWins + second.matchWins));
11253             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11254             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11255             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11256             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11257                 first.twoMachinesColor = "black\n";
11258                 second.twoMachinesColor = "white\n";
11259             } else {
11260                 first.twoMachinesColor = "white\n";
11261                 second.twoMachinesColor = "black\n";
11262             }
11263         }
11264     }
11265     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11266         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11267       ExitAnalyzeMode();
11268     gameMode = nextGameMode;
11269     ModeHighlight();
11270     endingGame = 0;  /* [HGM] crash */
11271     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11272         if(matchMode == TRUE) { // match through command line: exit with or without popup
11273             if(ranking) {
11274                 ToNrEvent(forwardMostMove);
11275                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11276                 else ExitEvent(0);
11277             } else DisplayFatalError(buf, 0, 0);
11278         } else { // match through menu; just stop, with or without popup
11279             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11280             ModeHighlight();
11281             if(ranking){
11282                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11283             } else DisplayNote(buf);
11284       }
11285       if(ranking) free(ranking);
11286     }
11287 }
11288
11289 /* Assumes program was just initialized (initString sent).
11290    Leaves program in force mode. */
11291 void
11292 FeedMovesToProgram (ChessProgramState *cps, int upto)
11293 {
11294     int i;
11295
11296     if (appData.debugMode)
11297       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11298               startedFromSetupPosition ? "position and " : "",
11299               backwardMostMove, upto, cps->which);
11300     if(currentlyInitializedVariant != gameInfo.variant) {
11301       char buf[MSG_SIZ];
11302         // [HGM] variantswitch: make engine aware of new variant
11303         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11304                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11305         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11306         SendToProgram(buf, cps);
11307         currentlyInitializedVariant = gameInfo.variant;
11308     }
11309     SendToProgram("force\n", cps);
11310     if (startedFromSetupPosition) {
11311         SendBoard(cps, backwardMostMove);
11312     if (appData.debugMode) {
11313         fprintf(debugFP, "feedMoves\n");
11314     }
11315     }
11316     for (i = backwardMostMove; i < upto; i++) {
11317         SendMoveToProgram(i, cps);
11318     }
11319 }
11320
11321
11322 int
11323 ResurrectChessProgram ()
11324 {
11325      /* The chess program may have exited.
11326         If so, restart it and feed it all the moves made so far. */
11327     static int doInit = 0;
11328
11329     if (appData.noChessProgram) return 1;
11330
11331     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11332         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11333         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11334         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11335     } else {
11336         if (first.pr != NoProc) return 1;
11337         StartChessProgram(&first);
11338     }
11339     InitChessProgram(&first, FALSE);
11340     FeedMovesToProgram(&first, currentMove);
11341
11342     if (!first.sendTime) {
11343         /* can't tell gnuchess what its clock should read,
11344            so we bow to its notion. */
11345         ResetClocks();
11346         timeRemaining[0][currentMove] = whiteTimeRemaining;
11347         timeRemaining[1][currentMove] = blackTimeRemaining;
11348     }
11349
11350     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11351                 appData.icsEngineAnalyze) && first.analysisSupport) {
11352       SendToProgram("analyze\n", &first);
11353       first.analyzing = TRUE;
11354     }
11355     return 1;
11356 }
11357
11358 /*
11359  * Button procedures
11360  */
11361 void
11362 Reset (int redraw, int init)
11363 {
11364     int i;
11365
11366     if (appData.debugMode) {
11367         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11368                 redraw, init, gameMode);
11369     }
11370     CleanupTail(); // [HGM] vari: delete any stored variations
11371     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11372     pausing = pauseExamInvalid = FALSE;
11373     startedFromSetupPosition = blackPlaysFirst = FALSE;
11374     firstMove = TRUE;
11375     whiteFlag = blackFlag = FALSE;
11376     userOfferedDraw = FALSE;
11377     hintRequested = bookRequested = FALSE;
11378     first.maybeThinking = FALSE;
11379     second.maybeThinking = FALSE;
11380     first.bookSuspend = FALSE; // [HGM] book
11381     second.bookSuspend = FALSE;
11382     thinkOutput[0] = NULLCHAR;
11383     lastHint[0] = NULLCHAR;
11384     ClearGameInfo(&gameInfo);
11385     gameInfo.variant = StringToVariant(appData.variant);
11386     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11387     ics_user_moved = ics_clock_paused = FALSE;
11388     ics_getting_history = H_FALSE;
11389     ics_gamenum = -1;
11390     white_holding[0] = black_holding[0] = NULLCHAR;
11391     ClearProgramStats();
11392     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11393
11394     ResetFrontEnd();
11395     ClearHighlights();
11396     flipView = appData.flipView;
11397     ClearPremoveHighlights();
11398     gotPremove = FALSE;
11399     alarmSounded = FALSE;
11400     killX = killY = -1; // [HGM] lion
11401
11402     GameEnds(EndOfFile, NULL, GE_PLAYER);
11403     if(appData.serverMovesName != NULL) {
11404         /* [HGM] prepare to make moves file for broadcasting */
11405         clock_t t = clock();
11406         if(serverMoves != NULL) fclose(serverMoves);
11407         serverMoves = fopen(appData.serverMovesName, "r");
11408         if(serverMoves != NULL) {
11409             fclose(serverMoves);
11410             /* delay 15 sec before overwriting, so all clients can see end */
11411             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11412         }
11413         serverMoves = fopen(appData.serverMovesName, "w");
11414     }
11415
11416     ExitAnalyzeMode();
11417     gameMode = BeginningOfGame;
11418     ModeHighlight();
11419     if(appData.icsActive) gameInfo.variant = VariantNormal;
11420     currentMove = forwardMostMove = backwardMostMove = 0;
11421     MarkTargetSquares(1);
11422     InitPosition(redraw);
11423     for (i = 0; i < MAX_MOVES; i++) {
11424         if (commentList[i] != NULL) {
11425             free(commentList[i]);
11426             commentList[i] = NULL;
11427         }
11428     }
11429     ResetClocks();
11430     timeRemaining[0][0] = whiteTimeRemaining;
11431     timeRemaining[1][0] = blackTimeRemaining;
11432
11433     if (first.pr == NoProc) {
11434         StartChessProgram(&first);
11435     }
11436     if (init) {
11437             InitChessProgram(&first, startedFromSetupPosition);
11438     }
11439     DisplayTitle("");
11440     DisplayMessage("", "");
11441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11442     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11443     ClearMap();        // [HGM] exclude: invalidate map
11444 }
11445
11446 void
11447 AutoPlayGameLoop ()
11448 {
11449     for (;;) {
11450         if (!AutoPlayOneMove())
11451           return;
11452         if (matchMode || appData.timeDelay == 0)
11453           continue;
11454         if (appData.timeDelay < 0)
11455           return;
11456         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11457         break;
11458     }
11459 }
11460
11461 void
11462 AnalyzeNextGame()
11463 {
11464     ReloadGame(1); // next game
11465 }
11466
11467 int
11468 AutoPlayOneMove ()
11469 {
11470     int fromX, fromY, toX, toY;
11471
11472     if (appData.debugMode) {
11473       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11474     }
11475
11476     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11477       return FALSE;
11478
11479     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11480       pvInfoList[currentMove].depth = programStats.depth;
11481       pvInfoList[currentMove].score = programStats.score;
11482       pvInfoList[currentMove].time  = 0;
11483       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11484       else { // append analysis of final position as comment
11485         char buf[MSG_SIZ];
11486         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11487         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11488       }
11489       programStats.depth = 0;
11490     }
11491
11492     if (currentMove >= forwardMostMove) {
11493       if(gameMode == AnalyzeFile) {
11494           if(appData.loadGameIndex == -1) {
11495             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11496           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11497           } else {
11498           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11499         }
11500       }
11501 //      gameMode = EndOfGame;
11502 //      ModeHighlight();
11503
11504       /* [AS] Clear current move marker at the end of a game */
11505       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11506
11507       return FALSE;
11508     }
11509
11510     toX = moveList[currentMove][2] - AAA;
11511     toY = moveList[currentMove][3] - ONE;
11512
11513     if (moveList[currentMove][1] == '@') {
11514         if (appData.highlightLastMove) {
11515             SetHighlights(-1, -1, toX, toY);
11516         }
11517     } else {
11518         fromX = moveList[currentMove][0] - AAA;
11519         fromY = moveList[currentMove][1] - ONE;
11520
11521         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11522
11523         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11524
11525         if (appData.highlightLastMove) {
11526             SetHighlights(fromX, fromY, toX, toY);
11527         }
11528     }
11529     DisplayMove(currentMove);
11530     SendMoveToProgram(currentMove++, &first);
11531     DisplayBothClocks();
11532     DrawPosition(FALSE, boards[currentMove]);
11533     // [HGM] PV info: always display, routine tests if empty
11534     DisplayComment(currentMove - 1, commentList[currentMove]);
11535     return TRUE;
11536 }
11537
11538
11539 int
11540 LoadGameOneMove (ChessMove readAhead)
11541 {
11542     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11543     char promoChar = NULLCHAR;
11544     ChessMove moveType;
11545     char move[MSG_SIZ];
11546     char *p, *q;
11547
11548     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11549         gameMode != AnalyzeMode && gameMode != Training) {
11550         gameFileFP = NULL;
11551         return FALSE;
11552     }
11553
11554     yyboardindex = forwardMostMove;
11555     if (readAhead != EndOfFile) {
11556       moveType = readAhead;
11557     } else {
11558       if (gameFileFP == NULL)
11559           return FALSE;
11560       moveType = (ChessMove) Myylex();
11561     }
11562
11563     done = FALSE;
11564     switch (moveType) {
11565       case Comment:
11566         if (appData.debugMode)
11567           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11568         p = yy_text;
11569
11570         /* append the comment but don't display it */
11571         AppendComment(currentMove, p, FALSE);
11572         return TRUE;
11573
11574       case WhiteCapturesEnPassant:
11575       case BlackCapturesEnPassant:
11576       case WhitePromotion:
11577       case BlackPromotion:
11578       case WhiteNonPromotion:
11579       case BlackNonPromotion:
11580       case NormalMove:
11581       case FirstLeg:
11582       case WhiteKingSideCastle:
11583       case WhiteQueenSideCastle:
11584       case BlackKingSideCastle:
11585       case BlackQueenSideCastle:
11586       case WhiteKingSideCastleWild:
11587       case WhiteQueenSideCastleWild:
11588       case BlackKingSideCastleWild:
11589       case BlackQueenSideCastleWild:
11590       /* PUSH Fabien */
11591       case WhiteHSideCastleFR:
11592       case WhiteASideCastleFR:
11593       case BlackHSideCastleFR:
11594       case BlackASideCastleFR:
11595       /* POP Fabien */
11596         if (appData.debugMode)
11597           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11598         fromX = currentMoveString[0] - AAA;
11599         fromY = currentMoveString[1] - ONE;
11600         toX = currentMoveString[2] - AAA;
11601         toY = currentMoveString[3] - ONE;
11602         promoChar = currentMoveString[4];
11603         if(promoChar == ';') promoChar = NULLCHAR;
11604         break;
11605
11606       case WhiteDrop:
11607       case BlackDrop:
11608         if (appData.debugMode)
11609           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11610         fromX = moveType == WhiteDrop ?
11611           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11612         (int) CharToPiece(ToLower(currentMoveString[0]));
11613         fromY = DROP_RANK;
11614         toX = currentMoveString[2] - AAA;
11615         toY = currentMoveString[3] - ONE;
11616         break;
11617
11618       case WhiteWins:
11619       case BlackWins:
11620       case GameIsDrawn:
11621       case GameUnfinished:
11622         if (appData.debugMode)
11623           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11624         p = strchr(yy_text, '{');
11625         if (p == NULL) p = strchr(yy_text, '(');
11626         if (p == NULL) {
11627             p = yy_text;
11628             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11629         } else {
11630             q = strchr(p, *p == '{' ? '}' : ')');
11631             if (q != NULL) *q = NULLCHAR;
11632             p++;
11633         }
11634         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11635         GameEnds(moveType, p, GE_FILE);
11636         done = TRUE;
11637         if (cmailMsgLoaded) {
11638             ClearHighlights();
11639             flipView = WhiteOnMove(currentMove);
11640             if (moveType == GameUnfinished) flipView = !flipView;
11641             if (appData.debugMode)
11642               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11643         }
11644         break;
11645
11646       case EndOfFile:
11647         if (appData.debugMode)
11648           fprintf(debugFP, "Parser hit end of file\n");
11649         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11650           case MT_NONE:
11651           case MT_CHECK:
11652             break;
11653           case MT_CHECKMATE:
11654           case MT_STAINMATE:
11655             if (WhiteOnMove(currentMove)) {
11656                 GameEnds(BlackWins, "Black mates", GE_FILE);
11657             } else {
11658                 GameEnds(WhiteWins, "White mates", GE_FILE);
11659             }
11660             break;
11661           case MT_STALEMATE:
11662             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11663             break;
11664         }
11665         done = TRUE;
11666         break;
11667
11668       case MoveNumberOne:
11669         if (lastLoadGameStart == GNUChessGame) {
11670             /* GNUChessGames have numbers, but they aren't move numbers */
11671             if (appData.debugMode)
11672               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11673                       yy_text, (int) moveType);
11674             return LoadGameOneMove(EndOfFile); /* tail recursion */
11675         }
11676         /* else fall thru */
11677
11678       case XBoardGame:
11679       case GNUChessGame:
11680       case PGNTag:
11681         /* Reached start of next game in file */
11682         if (appData.debugMode)
11683           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11684         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11685           case MT_NONE:
11686           case MT_CHECK:
11687             break;
11688           case MT_CHECKMATE:
11689           case MT_STAINMATE:
11690             if (WhiteOnMove(currentMove)) {
11691                 GameEnds(BlackWins, "Black mates", GE_FILE);
11692             } else {
11693                 GameEnds(WhiteWins, "White mates", GE_FILE);
11694             }
11695             break;
11696           case MT_STALEMATE:
11697             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11698             break;
11699         }
11700         done = TRUE;
11701         break;
11702
11703       case PositionDiagram:     /* should not happen; ignore */
11704       case ElapsedTime:         /* ignore */
11705       case NAG:                 /* ignore */
11706         if (appData.debugMode)
11707           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11708                   yy_text, (int) moveType);
11709         return LoadGameOneMove(EndOfFile); /* tail recursion */
11710
11711       case IllegalMove:
11712         if (appData.testLegality) {
11713             if (appData.debugMode)
11714               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11715             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11716                     (forwardMostMove / 2) + 1,
11717                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11718             DisplayError(move, 0);
11719             done = TRUE;
11720         } else {
11721             if (appData.debugMode)
11722               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11723                       yy_text, currentMoveString);
11724             fromX = currentMoveString[0] - AAA;
11725             fromY = currentMoveString[1] - ONE;
11726             toX = currentMoveString[2] - AAA;
11727             toY = currentMoveString[3] - ONE;
11728             promoChar = currentMoveString[4];
11729         }
11730         break;
11731
11732       case AmbiguousMove:
11733         if (appData.debugMode)
11734           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11735         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11736                 (forwardMostMove / 2) + 1,
11737                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11738         DisplayError(move, 0);
11739         done = TRUE;
11740         break;
11741
11742       default:
11743       case ImpossibleMove:
11744         if (appData.debugMode)
11745           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11746         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11747                 (forwardMostMove / 2) + 1,
11748                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11749         DisplayError(move, 0);
11750         done = TRUE;
11751         break;
11752     }
11753
11754     if (done) {
11755         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11756             DrawPosition(FALSE, boards[currentMove]);
11757             DisplayBothClocks();
11758             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11759               DisplayComment(currentMove - 1, commentList[currentMove]);
11760         }
11761         (void) StopLoadGameTimer();
11762         gameFileFP = NULL;
11763         cmailOldMove = forwardMostMove;
11764         return FALSE;
11765     } else {
11766         /* currentMoveString is set as a side-effect of yylex */
11767
11768         thinkOutput[0] = NULLCHAR;
11769         MakeMove(fromX, fromY, toX, toY, promoChar);
11770         killX = killY = -1; // [HGM] lion: used up
11771         currentMove = forwardMostMove;
11772         return TRUE;
11773     }
11774 }
11775
11776 /* Load the nth game from the given file */
11777 int
11778 LoadGameFromFile (char *filename, int n, char *title, int useList)
11779 {
11780     FILE *f;
11781     char buf[MSG_SIZ];
11782
11783     if (strcmp(filename, "-") == 0) {
11784         f = stdin;
11785         title = "stdin";
11786     } else {
11787         f = fopen(filename, "rb");
11788         if (f == NULL) {
11789           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11790             DisplayError(buf, errno);
11791             return FALSE;
11792         }
11793     }
11794     if (fseek(f, 0, 0) == -1) {
11795         /* f is not seekable; probably a pipe */
11796         useList = FALSE;
11797     }
11798     if (useList && n == 0) {
11799         int error = GameListBuild(f);
11800         if (error) {
11801             DisplayError(_("Cannot build game list"), error);
11802         } else if (!ListEmpty(&gameList) &&
11803                    ((ListGame *) gameList.tailPred)->number > 1) {
11804             GameListPopUp(f, title);
11805             return TRUE;
11806         }
11807         GameListDestroy();
11808         n = 1;
11809     }
11810     if (n == 0) n = 1;
11811     return LoadGame(f, n, title, FALSE);
11812 }
11813
11814
11815 void
11816 MakeRegisteredMove ()
11817 {
11818     int fromX, fromY, toX, toY;
11819     char promoChar;
11820     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11821         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11822           case CMAIL_MOVE:
11823           case CMAIL_DRAW:
11824             if (appData.debugMode)
11825               fprintf(debugFP, "Restoring %s for game %d\n",
11826                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11827
11828             thinkOutput[0] = NULLCHAR;
11829             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11830             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11831             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11832             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11833             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11834             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11835             MakeMove(fromX, fromY, toX, toY, promoChar);
11836             ShowMove(fromX, fromY, toX, toY);
11837
11838             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11839               case MT_NONE:
11840               case MT_CHECK:
11841                 break;
11842
11843               case MT_CHECKMATE:
11844               case MT_STAINMATE:
11845                 if (WhiteOnMove(currentMove)) {
11846                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11847                 } else {
11848                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11849                 }
11850                 break;
11851
11852               case MT_STALEMATE:
11853                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11854                 break;
11855             }
11856
11857             break;
11858
11859           case CMAIL_RESIGN:
11860             if (WhiteOnMove(currentMove)) {
11861                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11862             } else {
11863                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11864             }
11865             break;
11866
11867           case CMAIL_ACCEPT:
11868             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11869             break;
11870
11871           default:
11872             break;
11873         }
11874     }
11875
11876     return;
11877 }
11878
11879 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11880 int
11881 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11882 {
11883     int retVal;
11884
11885     if (gameNumber > nCmailGames) {
11886         DisplayError(_("No more games in this message"), 0);
11887         return FALSE;
11888     }
11889     if (f == lastLoadGameFP) {
11890         int offset = gameNumber - lastLoadGameNumber;
11891         if (offset == 0) {
11892             cmailMsg[0] = NULLCHAR;
11893             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11894                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11895                 nCmailMovesRegistered--;
11896             }
11897             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11898             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11899                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11900             }
11901         } else {
11902             if (! RegisterMove()) return FALSE;
11903         }
11904     }
11905
11906     retVal = LoadGame(f, gameNumber, title, useList);
11907
11908     /* Make move registered during previous look at this game, if any */
11909     MakeRegisteredMove();
11910
11911     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11912         commentList[currentMove]
11913           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11914         DisplayComment(currentMove - 1, commentList[currentMove]);
11915     }
11916
11917     return retVal;
11918 }
11919
11920 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11921 int
11922 ReloadGame (int offset)
11923 {
11924     int gameNumber = lastLoadGameNumber + offset;
11925     if (lastLoadGameFP == NULL) {
11926         DisplayError(_("No game has been loaded yet"), 0);
11927         return FALSE;
11928     }
11929     if (gameNumber <= 0) {
11930         DisplayError(_("Can't back up any further"), 0);
11931         return FALSE;
11932     }
11933     if (cmailMsgLoaded) {
11934         return CmailLoadGame(lastLoadGameFP, gameNumber,
11935                              lastLoadGameTitle, lastLoadGameUseList);
11936     } else {
11937         return LoadGame(lastLoadGameFP, gameNumber,
11938                         lastLoadGameTitle, lastLoadGameUseList);
11939     }
11940 }
11941
11942 int keys[EmptySquare+1];
11943
11944 int
11945 PositionMatches (Board b1, Board b2)
11946 {
11947     int r, f, sum=0;
11948     switch(appData.searchMode) {
11949         case 1: return CompareWithRights(b1, b2);
11950         case 2:
11951             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11952                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11953             }
11954             return TRUE;
11955         case 3:
11956             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11957               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11958                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11959             }
11960             return sum==0;
11961         case 4:
11962             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11963                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11964             }
11965             return sum==0;
11966     }
11967     return TRUE;
11968 }
11969
11970 #define Q_PROMO  4
11971 #define Q_EP     3
11972 #define Q_BCASTL 2
11973 #define Q_WCASTL 1
11974
11975 int pieceList[256], quickBoard[256];
11976 ChessSquare pieceType[256] = { EmptySquare };
11977 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11978 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11979 int soughtTotal, turn;
11980 Boolean epOK, flipSearch;
11981
11982 typedef struct {
11983     unsigned char piece, to;
11984 } Move;
11985
11986 #define DSIZE (250000)
11987
11988 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11989 Move *moveDatabase = initialSpace;
11990 unsigned int movePtr, dataSize = DSIZE;
11991
11992 int
11993 MakePieceList (Board board, int *counts)
11994 {
11995     int r, f, n=Q_PROMO, total=0;
11996     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11997     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11998         int sq = f + (r<<4);
11999         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12000             quickBoard[sq] = ++n;
12001             pieceList[n] = sq;
12002             pieceType[n] = board[r][f];
12003             counts[board[r][f]]++;
12004             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12005             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12006             total++;
12007         }
12008     }
12009     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12010     return total;
12011 }
12012
12013 void
12014 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12015 {
12016     int sq = fromX + (fromY<<4);
12017     int piece = quickBoard[sq];
12018     quickBoard[sq] = 0;
12019     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12020     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12021         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12022         moveDatabase[movePtr++].piece = Q_WCASTL;
12023         quickBoard[sq] = piece;
12024         piece = quickBoard[from]; quickBoard[from] = 0;
12025         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12026     } else
12027     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12028         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12029         moveDatabase[movePtr++].piece = Q_BCASTL;
12030         quickBoard[sq] = piece;
12031         piece = quickBoard[from]; quickBoard[from] = 0;
12032         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12033     } else
12034     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12035         quickBoard[(fromY<<4)+toX] = 0;
12036         moveDatabase[movePtr].piece = Q_EP;
12037         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12038         moveDatabase[movePtr].to = sq;
12039     } else
12040     if(promoPiece != pieceType[piece]) {
12041         moveDatabase[movePtr++].piece = Q_PROMO;
12042         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12043     }
12044     moveDatabase[movePtr].piece = piece;
12045     quickBoard[sq] = piece;
12046     movePtr++;
12047 }
12048
12049 int
12050 PackGame (Board board)
12051 {
12052     Move *newSpace = NULL;
12053     moveDatabase[movePtr].piece = 0; // terminate previous game
12054     if(movePtr > dataSize) {
12055         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12056         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12057         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12058         if(newSpace) {
12059             int i;
12060             Move *p = moveDatabase, *q = newSpace;
12061             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12062             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12063             moveDatabase = newSpace;
12064         } else { // calloc failed, we must be out of memory. Too bad...
12065             dataSize = 0; // prevent calloc events for all subsequent games
12066             return 0;     // and signal this one isn't cached
12067         }
12068     }
12069     movePtr++;
12070     MakePieceList(board, counts);
12071     return movePtr;
12072 }
12073
12074 int
12075 QuickCompare (Board board, int *minCounts, int *maxCounts)
12076 {   // compare according to search mode
12077     int r, f;
12078     switch(appData.searchMode)
12079     {
12080       case 1: // exact position match
12081         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12082         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12083             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12084         }
12085         break;
12086       case 2: // can have extra material on empty squares
12087         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12088             if(board[r][f] == EmptySquare) continue;
12089             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12090         }
12091         break;
12092       case 3: // material with exact Pawn structure
12093         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12094             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12095             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12096         } // fall through to material comparison
12097       case 4: // exact material
12098         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12099         break;
12100       case 6: // material range with given imbalance
12101         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12102         // fall through to range comparison
12103       case 5: // material range
12104         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12105     }
12106     return TRUE;
12107 }
12108
12109 int
12110 QuickScan (Board board, Move *move)
12111 {   // reconstruct game,and compare all positions in it
12112     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12113     do {
12114         int piece = move->piece;
12115         int to = move->to, from = pieceList[piece];
12116         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12117           if(!piece) return -1;
12118           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12119             piece = (++move)->piece;
12120             from = pieceList[piece];
12121             counts[pieceType[piece]]--;
12122             pieceType[piece] = (ChessSquare) move->to;
12123             counts[move->to]++;
12124           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12125             counts[pieceType[quickBoard[to]]]--;
12126             quickBoard[to] = 0; total--;
12127             move++;
12128             continue;
12129           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12130             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12131             from  = pieceList[piece]; // so this must be King
12132             quickBoard[from] = 0;
12133             pieceList[piece] = to;
12134             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12135             quickBoard[from] = 0; // rook
12136             quickBoard[to] = piece;
12137             to = move->to; piece = move->piece;
12138             goto aftercastle;
12139           }
12140         }
12141         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12142         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12143         quickBoard[from] = 0;
12144       aftercastle:
12145         quickBoard[to] = piece;
12146         pieceList[piece] = to;
12147         cnt++; turn ^= 3;
12148         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12149            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12150            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12151                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12152           ) {
12153             static int lastCounts[EmptySquare+1];
12154             int i;
12155             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12156             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12157         } else stretch = 0;
12158         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12159         move++;
12160     } while(1);
12161 }
12162
12163 void
12164 InitSearch ()
12165 {
12166     int r, f;
12167     flipSearch = FALSE;
12168     CopyBoard(soughtBoard, boards[currentMove]);
12169     soughtTotal = MakePieceList(soughtBoard, maxSought);
12170     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12171     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12172     CopyBoard(reverseBoard, boards[currentMove]);
12173     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12174         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12175         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12176         reverseBoard[r][f] = piece;
12177     }
12178     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12179     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12180     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12181                  || (boards[currentMove][CASTLING][2] == NoRights ||
12182                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12183                  && (boards[currentMove][CASTLING][5] == NoRights ||
12184                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12185       ) {
12186         flipSearch = TRUE;
12187         CopyBoard(flipBoard, soughtBoard);
12188         CopyBoard(rotateBoard, reverseBoard);
12189         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12190             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12191             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12192         }
12193     }
12194     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12195     if(appData.searchMode >= 5) {
12196         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12197         MakePieceList(soughtBoard, minSought);
12198         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12199     }
12200     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12201         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12202 }
12203
12204 GameInfo dummyInfo;
12205 static int creatingBook;
12206
12207 int
12208 GameContainsPosition (FILE *f, ListGame *lg)
12209 {
12210     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12211     int fromX, fromY, toX, toY;
12212     char promoChar;
12213     static int initDone=FALSE;
12214
12215     // weed out games based on numerical tag comparison
12216     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12217     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12218     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12219     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12220     if(!initDone) {
12221         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12222         initDone = TRUE;
12223     }
12224     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12225     else CopyBoard(boards[scratch], initialPosition); // default start position
12226     if(lg->moves) {
12227         turn = btm + 1;
12228         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12229         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12230     }
12231     if(btm) plyNr++;
12232     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12233     fseek(f, lg->offset, 0);
12234     yynewfile(f);
12235     while(1) {
12236         yyboardindex = scratch;
12237         quickFlag = plyNr+1;
12238         next = Myylex();
12239         quickFlag = 0;
12240         switch(next) {
12241             case PGNTag:
12242                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12243             default:
12244                 continue;
12245
12246             case XBoardGame:
12247             case GNUChessGame:
12248                 if(plyNr) return -1; // after we have seen moves, this is for new game
12249               continue;
12250
12251             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12252             case ImpossibleMove:
12253             case WhiteWins: // game ends here with these four
12254             case BlackWins:
12255             case GameIsDrawn:
12256             case GameUnfinished:
12257                 return -1;
12258
12259             case IllegalMove:
12260                 if(appData.testLegality) return -1;
12261             case WhiteCapturesEnPassant:
12262             case BlackCapturesEnPassant:
12263             case WhitePromotion:
12264             case BlackPromotion:
12265             case WhiteNonPromotion:
12266             case BlackNonPromotion:
12267             case NormalMove:
12268             case FirstLeg:
12269             case WhiteKingSideCastle:
12270             case WhiteQueenSideCastle:
12271             case BlackKingSideCastle:
12272             case BlackQueenSideCastle:
12273             case WhiteKingSideCastleWild:
12274             case WhiteQueenSideCastleWild:
12275             case BlackKingSideCastleWild:
12276             case BlackQueenSideCastleWild:
12277             case WhiteHSideCastleFR:
12278             case WhiteASideCastleFR:
12279             case BlackHSideCastleFR:
12280             case BlackASideCastleFR:
12281                 fromX = currentMoveString[0] - AAA;
12282                 fromY = currentMoveString[1] - ONE;
12283                 toX = currentMoveString[2] - AAA;
12284                 toY = currentMoveString[3] - ONE;
12285                 promoChar = currentMoveString[4];
12286                 break;
12287             case WhiteDrop:
12288             case BlackDrop:
12289                 fromX = next == WhiteDrop ?
12290                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12291                   (int) CharToPiece(ToLower(currentMoveString[0]));
12292                 fromY = DROP_RANK;
12293                 toX = currentMoveString[2] - AAA;
12294                 toY = currentMoveString[3] - ONE;
12295                 promoChar = 0;
12296                 break;
12297         }
12298         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12299         plyNr++;
12300         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12301         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12302         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12303         if(appData.findMirror) {
12304             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12305             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12306         }
12307     }
12308 }
12309
12310 /* Load the nth game from open file f */
12311 int
12312 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12313 {
12314     ChessMove cm;
12315     char buf[MSG_SIZ];
12316     int gn = gameNumber;
12317     ListGame *lg = NULL;
12318     int numPGNTags = 0;
12319     int err, pos = -1;
12320     GameMode oldGameMode;
12321     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12322
12323     if (appData.debugMode)
12324         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12325
12326     if (gameMode == Training )
12327         SetTrainingModeOff();
12328
12329     oldGameMode = gameMode;
12330     if (gameMode != BeginningOfGame) {
12331       Reset(FALSE, TRUE);
12332     }
12333     killX = killY = -1; // [HGM] lion: in case we did not Reset
12334
12335     gameFileFP = f;
12336     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12337         fclose(lastLoadGameFP);
12338     }
12339
12340     if (useList) {
12341         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12342
12343         if (lg) {
12344             fseek(f, lg->offset, 0);
12345             GameListHighlight(gameNumber);
12346             pos = lg->position;
12347             gn = 1;
12348         }
12349         else {
12350             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12351               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12352             else
12353             DisplayError(_("Game number out of range"), 0);
12354             return FALSE;
12355         }
12356     } else {
12357         GameListDestroy();
12358         if (fseek(f, 0, 0) == -1) {
12359             if (f == lastLoadGameFP ?
12360                 gameNumber == lastLoadGameNumber + 1 :
12361                 gameNumber == 1) {
12362                 gn = 1;
12363             } else {
12364                 DisplayError(_("Can't seek on game file"), 0);
12365                 return FALSE;
12366             }
12367         }
12368     }
12369     lastLoadGameFP = f;
12370     lastLoadGameNumber = gameNumber;
12371     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12372     lastLoadGameUseList = useList;
12373
12374     yynewfile(f);
12375
12376     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12377       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12378                 lg->gameInfo.black);
12379             DisplayTitle(buf);
12380     } else if (*title != NULLCHAR) {
12381         if (gameNumber > 1) {
12382           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12383             DisplayTitle(buf);
12384         } else {
12385             DisplayTitle(title);
12386         }
12387     }
12388
12389     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12390         gameMode = PlayFromGameFile;
12391         ModeHighlight();
12392     }
12393
12394     currentMove = forwardMostMove = backwardMostMove = 0;
12395     CopyBoard(boards[0], initialPosition);
12396     StopClocks();
12397
12398     /*
12399      * Skip the first gn-1 games in the file.
12400      * Also skip over anything that precedes an identifiable
12401      * start of game marker, to avoid being confused by
12402      * garbage at the start of the file.  Currently
12403      * recognized start of game markers are the move number "1",
12404      * the pattern "gnuchess .* game", the pattern
12405      * "^[#;%] [^ ]* game file", and a PGN tag block.
12406      * A game that starts with one of the latter two patterns
12407      * will also have a move number 1, possibly
12408      * following a position diagram.
12409      * 5-4-02: Let's try being more lenient and allowing a game to
12410      * start with an unnumbered move.  Does that break anything?
12411      */
12412     cm = lastLoadGameStart = EndOfFile;
12413     while (gn > 0) {
12414         yyboardindex = forwardMostMove;
12415         cm = (ChessMove) Myylex();
12416         switch (cm) {
12417           case EndOfFile:
12418             if (cmailMsgLoaded) {
12419                 nCmailGames = CMAIL_MAX_GAMES - gn;
12420             } else {
12421                 Reset(TRUE, TRUE);
12422                 DisplayError(_("Game not found in file"), 0);
12423             }
12424             return FALSE;
12425
12426           case GNUChessGame:
12427           case XBoardGame:
12428             gn--;
12429             lastLoadGameStart = cm;
12430             break;
12431
12432           case MoveNumberOne:
12433             switch (lastLoadGameStart) {
12434               case GNUChessGame:
12435               case XBoardGame:
12436               case PGNTag:
12437                 break;
12438               case MoveNumberOne:
12439               case EndOfFile:
12440                 gn--;           /* count this game */
12441                 lastLoadGameStart = cm;
12442                 break;
12443               default:
12444                 /* impossible */
12445                 break;
12446             }
12447             break;
12448
12449           case PGNTag:
12450             switch (lastLoadGameStart) {
12451               case GNUChessGame:
12452               case PGNTag:
12453               case MoveNumberOne:
12454               case EndOfFile:
12455                 gn--;           /* count this game */
12456                 lastLoadGameStart = cm;
12457                 break;
12458               case XBoardGame:
12459                 lastLoadGameStart = cm; /* game counted already */
12460                 break;
12461               default:
12462                 /* impossible */
12463                 break;
12464             }
12465             if (gn > 0) {
12466                 do {
12467                     yyboardindex = forwardMostMove;
12468                     cm = (ChessMove) Myylex();
12469                 } while (cm == PGNTag || cm == Comment);
12470             }
12471             break;
12472
12473           case WhiteWins:
12474           case BlackWins:
12475           case GameIsDrawn:
12476             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12477                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12478                     != CMAIL_OLD_RESULT) {
12479                     nCmailResults ++ ;
12480                     cmailResult[  CMAIL_MAX_GAMES
12481                                 - gn - 1] = CMAIL_OLD_RESULT;
12482                 }
12483             }
12484             break;
12485
12486           case NormalMove:
12487           case FirstLeg:
12488             /* Only a NormalMove can be at the start of a game
12489              * without a position diagram. */
12490             if (lastLoadGameStart == EndOfFile ) {
12491               gn--;
12492               lastLoadGameStart = MoveNumberOne;
12493             }
12494             break;
12495
12496           default:
12497             break;
12498         }
12499     }
12500
12501     if (appData.debugMode)
12502       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12503
12504     if (cm == XBoardGame) {
12505         /* Skip any header junk before position diagram and/or move 1 */
12506         for (;;) {
12507             yyboardindex = forwardMostMove;
12508             cm = (ChessMove) Myylex();
12509
12510             if (cm == EndOfFile ||
12511                 cm == GNUChessGame || cm == XBoardGame) {
12512                 /* Empty game; pretend end-of-file and handle later */
12513                 cm = EndOfFile;
12514                 break;
12515             }
12516
12517             if (cm == MoveNumberOne || cm == PositionDiagram ||
12518                 cm == PGNTag || cm == Comment)
12519               break;
12520         }
12521     } else if (cm == GNUChessGame) {
12522         if (gameInfo.event != NULL) {
12523             free(gameInfo.event);
12524         }
12525         gameInfo.event = StrSave(yy_text);
12526     }
12527
12528     startedFromSetupPosition = FALSE;
12529     while (cm == PGNTag) {
12530         if (appData.debugMode)
12531           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12532         err = ParsePGNTag(yy_text, &gameInfo);
12533         if (!err) numPGNTags++;
12534
12535         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12536         if(gameInfo.variant != oldVariant) {
12537             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12538             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12539             InitPosition(TRUE);
12540             oldVariant = gameInfo.variant;
12541             if (appData.debugMode)
12542               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12543         }
12544
12545
12546         if (gameInfo.fen != NULL) {
12547           Board initial_position;
12548           startedFromSetupPosition = TRUE;
12549           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12550             Reset(TRUE, TRUE);
12551             DisplayError(_("Bad FEN position in file"), 0);
12552             return FALSE;
12553           }
12554           CopyBoard(boards[0], initial_position);
12555           if (blackPlaysFirst) {
12556             currentMove = forwardMostMove = backwardMostMove = 1;
12557             CopyBoard(boards[1], initial_position);
12558             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12559             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12560             timeRemaining[0][1] = whiteTimeRemaining;
12561             timeRemaining[1][1] = blackTimeRemaining;
12562             if (commentList[0] != NULL) {
12563               commentList[1] = commentList[0];
12564               commentList[0] = NULL;
12565             }
12566           } else {
12567             currentMove = forwardMostMove = backwardMostMove = 0;
12568           }
12569           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12570           {   int i;
12571               initialRulePlies = FENrulePlies;
12572               for( i=0; i< nrCastlingRights; i++ )
12573                   initialRights[i] = initial_position[CASTLING][i];
12574           }
12575           yyboardindex = forwardMostMove;
12576           free(gameInfo.fen);
12577           gameInfo.fen = NULL;
12578         }
12579
12580         yyboardindex = forwardMostMove;
12581         cm = (ChessMove) Myylex();
12582
12583         /* Handle comments interspersed among the tags */
12584         while (cm == Comment) {
12585             char *p;
12586             if (appData.debugMode)
12587               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12588             p = yy_text;
12589             AppendComment(currentMove, p, FALSE);
12590             yyboardindex = forwardMostMove;
12591             cm = (ChessMove) Myylex();
12592         }
12593     }
12594
12595     /* don't rely on existence of Event tag since if game was
12596      * pasted from clipboard the Event tag may not exist
12597      */
12598     if (numPGNTags > 0){
12599         char *tags;
12600         if (gameInfo.variant == VariantNormal) {
12601           VariantClass v = StringToVariant(gameInfo.event);
12602           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12603           if(v < VariantShogi) gameInfo.variant = v;
12604         }
12605         if (!matchMode) {
12606           if( appData.autoDisplayTags ) {
12607             tags = PGNTags(&gameInfo);
12608             TagsPopUp(tags, CmailMsg());
12609             free(tags);
12610           }
12611         }
12612     } else {
12613         /* Make something up, but don't display it now */
12614         SetGameInfo();
12615         TagsPopDown();
12616     }
12617
12618     if (cm == PositionDiagram) {
12619         int i, j;
12620         char *p;
12621         Board initial_position;
12622
12623         if (appData.debugMode)
12624           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12625
12626         if (!startedFromSetupPosition) {
12627             p = yy_text;
12628             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12629               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12630                 switch (*p) {
12631                   case '{':
12632                   case '[':
12633                   case '-':
12634                   case ' ':
12635                   case '\t':
12636                   case '\n':
12637                   case '\r':
12638                     break;
12639                   default:
12640                     initial_position[i][j++] = CharToPiece(*p);
12641                     break;
12642                 }
12643             while (*p == ' ' || *p == '\t' ||
12644                    *p == '\n' || *p == '\r') p++;
12645
12646             if (strncmp(p, "black", strlen("black"))==0)
12647               blackPlaysFirst = TRUE;
12648             else
12649               blackPlaysFirst = FALSE;
12650             startedFromSetupPosition = TRUE;
12651
12652             CopyBoard(boards[0], initial_position);
12653             if (blackPlaysFirst) {
12654                 currentMove = forwardMostMove = backwardMostMove = 1;
12655                 CopyBoard(boards[1], initial_position);
12656                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12657                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12658                 timeRemaining[0][1] = whiteTimeRemaining;
12659                 timeRemaining[1][1] = blackTimeRemaining;
12660                 if (commentList[0] != NULL) {
12661                     commentList[1] = commentList[0];
12662                     commentList[0] = NULL;
12663                 }
12664             } else {
12665                 currentMove = forwardMostMove = backwardMostMove = 0;
12666             }
12667         }
12668         yyboardindex = forwardMostMove;
12669         cm = (ChessMove) Myylex();
12670     }
12671
12672   if(!creatingBook) {
12673     if (first.pr == NoProc) {
12674         StartChessProgram(&first);
12675     }
12676     InitChessProgram(&first, FALSE);
12677     SendToProgram("force\n", &first);
12678     if (startedFromSetupPosition) {
12679         SendBoard(&first, forwardMostMove);
12680     if (appData.debugMode) {
12681         fprintf(debugFP, "Load Game\n");
12682     }
12683         DisplayBothClocks();
12684     }
12685   }
12686
12687     /* [HGM] server: flag to write setup moves in broadcast file as one */
12688     loadFlag = appData.suppressLoadMoves;
12689
12690     while (cm == Comment) {
12691         char *p;
12692         if (appData.debugMode)
12693           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12694         p = yy_text;
12695         AppendComment(currentMove, p, FALSE);
12696         yyboardindex = forwardMostMove;
12697         cm = (ChessMove) Myylex();
12698     }
12699
12700     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12701         cm == WhiteWins || cm == BlackWins ||
12702         cm == GameIsDrawn || cm == GameUnfinished) {
12703         DisplayMessage("", _("No moves in game"));
12704         if (cmailMsgLoaded) {
12705             if (appData.debugMode)
12706               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12707             ClearHighlights();
12708             flipView = FALSE;
12709         }
12710         DrawPosition(FALSE, boards[currentMove]);
12711         DisplayBothClocks();
12712         gameMode = EditGame;
12713         ModeHighlight();
12714         gameFileFP = NULL;
12715         cmailOldMove = 0;
12716         return TRUE;
12717     }
12718
12719     // [HGM] PV info: routine tests if comment empty
12720     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12721         DisplayComment(currentMove - 1, commentList[currentMove]);
12722     }
12723     if (!matchMode && appData.timeDelay != 0)
12724       DrawPosition(FALSE, boards[currentMove]);
12725
12726     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12727       programStats.ok_to_send = 1;
12728     }
12729
12730     /* if the first token after the PGN tags is a move
12731      * and not move number 1, retrieve it from the parser
12732      */
12733     if (cm != MoveNumberOne)
12734         LoadGameOneMove(cm);
12735
12736     /* load the remaining moves from the file */
12737     while (LoadGameOneMove(EndOfFile)) {
12738       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12739       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12740     }
12741
12742     /* rewind to the start of the game */
12743     currentMove = backwardMostMove;
12744
12745     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12746
12747     if (oldGameMode == AnalyzeFile) {
12748       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12749       AnalyzeFileEvent();
12750     } else
12751     if (oldGameMode == AnalyzeMode) {
12752       AnalyzeFileEvent();
12753     }
12754
12755     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12756         long int w, b; // [HGM] adjourn: restore saved clock times
12757         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12758         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12759             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12760             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12761         }
12762     }
12763
12764     if(creatingBook) return TRUE;
12765     if (!matchMode && pos > 0) {
12766         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12767     } else
12768     if (matchMode || appData.timeDelay == 0) {
12769       ToEndEvent();
12770     } else if (appData.timeDelay > 0) {
12771       AutoPlayGameLoop();
12772     }
12773
12774     if (appData.debugMode)
12775         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12776
12777     loadFlag = 0; /* [HGM] true game starts */
12778     return TRUE;
12779 }
12780
12781 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12782 int
12783 ReloadPosition (int offset)
12784 {
12785     int positionNumber = lastLoadPositionNumber + offset;
12786     if (lastLoadPositionFP == NULL) {
12787         DisplayError(_("No position has been loaded yet"), 0);
12788         return FALSE;
12789     }
12790     if (positionNumber <= 0) {
12791         DisplayError(_("Can't back up any further"), 0);
12792         return FALSE;
12793     }
12794     return LoadPosition(lastLoadPositionFP, positionNumber,
12795                         lastLoadPositionTitle);
12796 }
12797
12798 /* Load the nth position from the given file */
12799 int
12800 LoadPositionFromFile (char *filename, int n, char *title)
12801 {
12802     FILE *f;
12803     char buf[MSG_SIZ];
12804
12805     if (strcmp(filename, "-") == 0) {
12806         return LoadPosition(stdin, n, "stdin");
12807     } else {
12808         f = fopen(filename, "rb");
12809         if (f == NULL) {
12810             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12811             DisplayError(buf, errno);
12812             return FALSE;
12813         } else {
12814             return LoadPosition(f, n, title);
12815         }
12816     }
12817 }
12818
12819 /* Load the nth position from the given open file, and close it */
12820 int
12821 LoadPosition (FILE *f, int positionNumber, char *title)
12822 {
12823     char *p, line[MSG_SIZ];
12824     Board initial_position;
12825     int i, j, fenMode, pn;
12826
12827     if (gameMode == Training )
12828         SetTrainingModeOff();
12829
12830     if (gameMode != BeginningOfGame) {
12831         Reset(FALSE, TRUE);
12832     }
12833     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12834         fclose(lastLoadPositionFP);
12835     }
12836     if (positionNumber == 0) positionNumber = 1;
12837     lastLoadPositionFP = f;
12838     lastLoadPositionNumber = positionNumber;
12839     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12840     if (first.pr == NoProc && !appData.noChessProgram) {
12841       StartChessProgram(&first);
12842       InitChessProgram(&first, FALSE);
12843     }
12844     pn = positionNumber;
12845     if (positionNumber < 0) {
12846         /* Negative position number means to seek to that byte offset */
12847         if (fseek(f, -positionNumber, 0) == -1) {
12848             DisplayError(_("Can't seek on position file"), 0);
12849             return FALSE;
12850         };
12851         pn = 1;
12852     } else {
12853         if (fseek(f, 0, 0) == -1) {
12854             if (f == lastLoadPositionFP ?
12855                 positionNumber == lastLoadPositionNumber + 1 :
12856                 positionNumber == 1) {
12857                 pn = 1;
12858             } else {
12859                 DisplayError(_("Can't seek on position file"), 0);
12860                 return FALSE;
12861             }
12862         }
12863     }
12864     /* See if this file is FEN or old-style xboard */
12865     if (fgets(line, MSG_SIZ, f) == NULL) {
12866         DisplayError(_("Position not found in file"), 0);
12867         return FALSE;
12868     }
12869     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12870     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12871
12872     if (pn >= 2) {
12873         if (fenMode || line[0] == '#') pn--;
12874         while (pn > 0) {
12875             /* skip positions before number pn */
12876             if (fgets(line, MSG_SIZ, f) == NULL) {
12877                 Reset(TRUE, TRUE);
12878                 DisplayError(_("Position not found in file"), 0);
12879                 return FALSE;
12880             }
12881             if (fenMode || line[0] == '#') pn--;
12882         }
12883     }
12884
12885     if (fenMode) {
12886         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12887             DisplayError(_("Bad FEN position in file"), 0);
12888             return FALSE;
12889         }
12890     } else {
12891         (void) fgets(line, MSG_SIZ, f);
12892         (void) fgets(line, MSG_SIZ, f);
12893
12894         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12895             (void) fgets(line, MSG_SIZ, f);
12896             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12897                 if (*p == ' ')
12898                   continue;
12899                 initial_position[i][j++] = CharToPiece(*p);
12900             }
12901         }
12902
12903         blackPlaysFirst = FALSE;
12904         if (!feof(f)) {
12905             (void) fgets(line, MSG_SIZ, f);
12906             if (strncmp(line, "black", strlen("black"))==0)
12907               blackPlaysFirst = TRUE;
12908         }
12909     }
12910     startedFromSetupPosition = TRUE;
12911
12912     CopyBoard(boards[0], initial_position);
12913     if (blackPlaysFirst) {
12914         currentMove = forwardMostMove = backwardMostMove = 1;
12915         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12916         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12917         CopyBoard(boards[1], initial_position);
12918         DisplayMessage("", _("Black to play"));
12919     } else {
12920         currentMove = forwardMostMove = backwardMostMove = 0;
12921         DisplayMessage("", _("White to play"));
12922     }
12923     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12924     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12925         SendToProgram("force\n", &first);
12926         SendBoard(&first, forwardMostMove);
12927     }
12928     if (appData.debugMode) {
12929 int i, j;
12930   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12931   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12932         fprintf(debugFP, "Load Position\n");
12933     }
12934
12935     if (positionNumber > 1) {
12936       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12937         DisplayTitle(line);
12938     } else {
12939         DisplayTitle(title);
12940     }
12941     gameMode = EditGame;
12942     ModeHighlight();
12943     ResetClocks();
12944     timeRemaining[0][1] = whiteTimeRemaining;
12945     timeRemaining[1][1] = blackTimeRemaining;
12946     DrawPosition(FALSE, boards[currentMove]);
12947
12948     return TRUE;
12949 }
12950
12951
12952 void
12953 CopyPlayerNameIntoFileName (char **dest, char *src)
12954 {
12955     while (*src != NULLCHAR && *src != ',') {
12956         if (*src == ' ') {
12957             *(*dest)++ = '_';
12958             src++;
12959         } else {
12960             *(*dest)++ = *src++;
12961         }
12962     }
12963 }
12964
12965 char *
12966 DefaultFileName (char *ext)
12967 {
12968     static char def[MSG_SIZ];
12969     char *p;
12970
12971     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12972         p = def;
12973         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12974         *p++ = '-';
12975         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12976         *p++ = '.';
12977         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12978     } else {
12979         def[0] = NULLCHAR;
12980     }
12981     return def;
12982 }
12983
12984 /* Save the current game to the given file */
12985 int
12986 SaveGameToFile (char *filename, int append)
12987 {
12988     FILE *f;
12989     char buf[MSG_SIZ];
12990     int result, i, t,tot=0;
12991
12992     if (strcmp(filename, "-") == 0) {
12993         return SaveGame(stdout, 0, NULL);
12994     } else {
12995         for(i=0; i<10; i++) { // upto 10 tries
12996              f = fopen(filename, append ? "a" : "w");
12997              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12998              if(f || errno != 13) break;
12999              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13000              tot += t;
13001         }
13002         if (f == NULL) {
13003             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13004             DisplayError(buf, errno);
13005             return FALSE;
13006         } else {
13007             safeStrCpy(buf, lastMsg, MSG_SIZ);
13008             DisplayMessage(_("Waiting for access to save file"), "");
13009             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13010             DisplayMessage(_("Saving game"), "");
13011             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13012             result = SaveGame(f, 0, NULL);
13013             DisplayMessage(buf, "");
13014             return result;
13015         }
13016     }
13017 }
13018
13019 char *
13020 SavePart (char *str)
13021 {
13022     static char buf[MSG_SIZ];
13023     char *p;
13024
13025     p = strchr(str, ' ');
13026     if (p == NULL) return str;
13027     strncpy(buf, str, p - str);
13028     buf[p - str] = NULLCHAR;
13029     return buf;
13030 }
13031
13032 #define PGN_MAX_LINE 75
13033
13034 #define PGN_SIDE_WHITE  0
13035 #define PGN_SIDE_BLACK  1
13036
13037 static int
13038 FindFirstMoveOutOfBook (int side)
13039 {
13040     int result = -1;
13041
13042     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13043         int index = backwardMostMove;
13044         int has_book_hit = 0;
13045
13046         if( (index % 2) != side ) {
13047             index++;
13048         }
13049
13050         while( index < forwardMostMove ) {
13051             /* Check to see if engine is in book */
13052             int depth = pvInfoList[index].depth;
13053             int score = pvInfoList[index].score;
13054             int in_book = 0;
13055
13056             if( depth <= 2 ) {
13057                 in_book = 1;
13058             }
13059             else if( score == 0 && depth == 63 ) {
13060                 in_book = 1; /* Zappa */
13061             }
13062             else if( score == 2 && depth == 99 ) {
13063                 in_book = 1; /* Abrok */
13064             }
13065
13066             has_book_hit += in_book;
13067
13068             if( ! in_book ) {
13069                 result = index;
13070
13071                 break;
13072             }
13073
13074             index += 2;
13075         }
13076     }
13077
13078     return result;
13079 }
13080
13081 void
13082 GetOutOfBookInfo (char * buf)
13083 {
13084     int oob[2];
13085     int i;
13086     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13087
13088     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13089     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13090
13091     *buf = '\0';
13092
13093     if( oob[0] >= 0 || oob[1] >= 0 ) {
13094         for( i=0; i<2; i++ ) {
13095             int idx = oob[i];
13096
13097             if( idx >= 0 ) {
13098                 if( i > 0 && oob[0] >= 0 ) {
13099                     strcat( buf, "   " );
13100                 }
13101
13102                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13103                 sprintf( buf+strlen(buf), "%s%.2f",
13104                     pvInfoList[idx].score >= 0 ? "+" : "",
13105                     pvInfoList[idx].score / 100.0 );
13106             }
13107         }
13108     }
13109 }
13110
13111 /* Save game in PGN style and close the file */
13112 int
13113 SaveGamePGN (FILE *f)
13114 {
13115     int i, offset, linelen, newblock;
13116 //    char *movetext;
13117     char numtext[32];
13118     int movelen, numlen, blank;
13119     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13120
13121     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13122
13123     PrintPGNTags(f, &gameInfo);
13124
13125     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13126
13127     if (backwardMostMove > 0 || startedFromSetupPosition) {
13128         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13129         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13130         fprintf(f, "\n{--------------\n");
13131         PrintPosition(f, backwardMostMove);
13132         fprintf(f, "--------------}\n");
13133         free(fen);
13134     }
13135     else {
13136         /* [AS] Out of book annotation */
13137         if( appData.saveOutOfBookInfo ) {
13138             char buf[64];
13139
13140             GetOutOfBookInfo( buf );
13141
13142             if( buf[0] != '\0' ) {
13143                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13144             }
13145         }
13146
13147         fprintf(f, "\n");
13148     }
13149
13150     i = backwardMostMove;
13151     linelen = 0;
13152     newblock = TRUE;
13153
13154     while (i < forwardMostMove) {
13155         /* Print comments preceding this move */
13156         if (commentList[i] != NULL) {
13157             if (linelen > 0) fprintf(f, "\n");
13158             fprintf(f, "%s", commentList[i]);
13159             linelen = 0;
13160             newblock = TRUE;
13161         }
13162
13163         /* Format move number */
13164         if ((i % 2) == 0)
13165           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13166         else
13167           if (newblock)
13168             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13169           else
13170             numtext[0] = NULLCHAR;
13171
13172         numlen = strlen(numtext);
13173         newblock = FALSE;
13174
13175         /* Print move number */
13176         blank = linelen > 0 && numlen > 0;
13177         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13178             fprintf(f, "\n");
13179             linelen = 0;
13180             blank = 0;
13181         }
13182         if (blank) {
13183             fprintf(f, " ");
13184             linelen++;
13185         }
13186         fprintf(f, "%s", numtext);
13187         linelen += numlen;
13188
13189         /* Get move */
13190         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13191         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13192
13193         /* Print move */
13194         blank = linelen > 0 && movelen > 0;
13195         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13196             fprintf(f, "\n");
13197             linelen = 0;
13198             blank = 0;
13199         }
13200         if (blank) {
13201             fprintf(f, " ");
13202             linelen++;
13203         }
13204         fprintf(f, "%s", move_buffer);
13205         linelen += movelen;
13206
13207         /* [AS] Add PV info if present */
13208         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13209             /* [HGM] add time */
13210             char buf[MSG_SIZ]; int seconds;
13211
13212             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13213
13214             if( seconds <= 0)
13215               buf[0] = 0;
13216             else
13217               if( seconds < 30 )
13218                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13219               else
13220                 {
13221                   seconds = (seconds + 4)/10; // round to full seconds
13222                   if( seconds < 60 )
13223                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13224                   else
13225                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13226                 }
13227
13228             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13229                       pvInfoList[i].score >= 0 ? "+" : "",
13230                       pvInfoList[i].score / 100.0,
13231                       pvInfoList[i].depth,
13232                       buf );
13233
13234             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13235
13236             /* Print score/depth */
13237             blank = linelen > 0 && movelen > 0;
13238             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13239                 fprintf(f, "\n");
13240                 linelen = 0;
13241                 blank = 0;
13242             }
13243             if (blank) {
13244                 fprintf(f, " ");
13245                 linelen++;
13246             }
13247             fprintf(f, "%s", move_buffer);
13248             linelen += movelen;
13249         }
13250
13251         i++;
13252     }
13253
13254     /* Start a new line */
13255     if (linelen > 0) fprintf(f, "\n");
13256
13257     /* Print comments after last move */
13258     if (commentList[i] != NULL) {
13259         fprintf(f, "%s\n", commentList[i]);
13260     }
13261
13262     /* Print result */
13263     if (gameInfo.resultDetails != NULL &&
13264         gameInfo.resultDetails[0] != NULLCHAR) {
13265         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13266         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13267            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13268             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13269         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13270     } else {
13271         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13272     }
13273
13274     fclose(f);
13275     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13276     return TRUE;
13277 }
13278
13279 /* Save game in old style and close the file */
13280 int
13281 SaveGameOldStyle (FILE *f)
13282 {
13283     int i, offset;
13284     time_t tm;
13285
13286     tm = time((time_t *) NULL);
13287
13288     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13289     PrintOpponents(f);
13290
13291     if (backwardMostMove > 0 || startedFromSetupPosition) {
13292         fprintf(f, "\n[--------------\n");
13293         PrintPosition(f, backwardMostMove);
13294         fprintf(f, "--------------]\n");
13295     } else {
13296         fprintf(f, "\n");
13297     }
13298
13299     i = backwardMostMove;
13300     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13301
13302     while (i < forwardMostMove) {
13303         if (commentList[i] != NULL) {
13304             fprintf(f, "[%s]\n", commentList[i]);
13305         }
13306
13307         if ((i % 2) == 1) {
13308             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13309             i++;
13310         } else {
13311             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13312             i++;
13313             if (commentList[i] != NULL) {
13314                 fprintf(f, "\n");
13315                 continue;
13316             }
13317             if (i >= forwardMostMove) {
13318                 fprintf(f, "\n");
13319                 break;
13320             }
13321             fprintf(f, "%s\n", parseList[i]);
13322             i++;
13323         }
13324     }
13325
13326     if (commentList[i] != NULL) {
13327         fprintf(f, "[%s]\n", commentList[i]);
13328     }
13329
13330     /* This isn't really the old style, but it's close enough */
13331     if (gameInfo.resultDetails != NULL &&
13332         gameInfo.resultDetails[0] != NULLCHAR) {
13333         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13334                 gameInfo.resultDetails);
13335     } else {
13336         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13337     }
13338
13339     fclose(f);
13340     return TRUE;
13341 }
13342
13343 /* Save the current game to open file f and close the file */
13344 int
13345 SaveGame (FILE *f, int dummy, char *dummy2)
13346 {
13347     if (gameMode == EditPosition) EditPositionDone(TRUE);
13348     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13349     if (appData.oldSaveStyle)
13350       return SaveGameOldStyle(f);
13351     else
13352       return SaveGamePGN(f);
13353 }
13354
13355 /* Save the current position to the given file */
13356 int
13357 SavePositionToFile (char *filename)
13358 {
13359     FILE *f;
13360     char buf[MSG_SIZ];
13361
13362     if (strcmp(filename, "-") == 0) {
13363         return SavePosition(stdout, 0, NULL);
13364     } else {
13365         f = fopen(filename, "a");
13366         if (f == NULL) {
13367             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13368             DisplayError(buf, errno);
13369             return FALSE;
13370         } else {
13371             safeStrCpy(buf, lastMsg, MSG_SIZ);
13372             DisplayMessage(_("Waiting for access to save file"), "");
13373             flock(fileno(f), LOCK_EX); // [HGM] lock
13374             DisplayMessage(_("Saving position"), "");
13375             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13376             SavePosition(f, 0, NULL);
13377             DisplayMessage(buf, "");
13378             return TRUE;
13379         }
13380     }
13381 }
13382
13383 /* Save the current position to the given open file and close the file */
13384 int
13385 SavePosition (FILE *f, int dummy, char *dummy2)
13386 {
13387     time_t tm;
13388     char *fen;
13389
13390     if (gameMode == EditPosition) EditPositionDone(TRUE);
13391     if (appData.oldSaveStyle) {
13392         tm = time((time_t *) NULL);
13393
13394         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13395         PrintOpponents(f);
13396         fprintf(f, "[--------------\n");
13397         PrintPosition(f, currentMove);
13398         fprintf(f, "--------------]\n");
13399     } else {
13400         fen = PositionToFEN(currentMove, NULL, 1);
13401         fprintf(f, "%s\n", fen);
13402         free(fen);
13403     }
13404     fclose(f);
13405     return TRUE;
13406 }
13407
13408 void
13409 ReloadCmailMsgEvent (int unregister)
13410 {
13411 #if !WIN32
13412     static char *inFilename = NULL;
13413     static char *outFilename;
13414     int i;
13415     struct stat inbuf, outbuf;
13416     int status;
13417
13418     /* Any registered moves are unregistered if unregister is set, */
13419     /* i.e. invoked by the signal handler */
13420     if (unregister) {
13421         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13422             cmailMoveRegistered[i] = FALSE;
13423             if (cmailCommentList[i] != NULL) {
13424                 free(cmailCommentList[i]);
13425                 cmailCommentList[i] = NULL;
13426             }
13427         }
13428         nCmailMovesRegistered = 0;
13429     }
13430
13431     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13432         cmailResult[i] = CMAIL_NOT_RESULT;
13433     }
13434     nCmailResults = 0;
13435
13436     if (inFilename == NULL) {
13437         /* Because the filenames are static they only get malloced once  */
13438         /* and they never get freed                                      */
13439         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13440         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13441
13442         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13443         sprintf(outFilename, "%s.out", appData.cmailGameName);
13444     }
13445
13446     status = stat(outFilename, &outbuf);
13447     if (status < 0) {
13448         cmailMailedMove = FALSE;
13449     } else {
13450         status = stat(inFilename, &inbuf);
13451         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13452     }
13453
13454     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13455        counts the games, notes how each one terminated, etc.
13456
13457        It would be nice to remove this kludge and instead gather all
13458        the information while building the game list.  (And to keep it
13459        in the game list nodes instead of having a bunch of fixed-size
13460        parallel arrays.)  Note this will require getting each game's
13461        termination from the PGN tags, as the game list builder does
13462        not process the game moves.  --mann
13463        */
13464     cmailMsgLoaded = TRUE;
13465     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13466
13467     /* Load first game in the file or popup game menu */
13468     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13469
13470 #endif /* !WIN32 */
13471     return;
13472 }
13473
13474 int
13475 RegisterMove ()
13476 {
13477     FILE *f;
13478     char string[MSG_SIZ];
13479
13480     if (   cmailMailedMove
13481         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13482         return TRUE;            /* Allow free viewing  */
13483     }
13484
13485     /* Unregister move to ensure that we don't leave RegisterMove        */
13486     /* with the move registered when the conditions for registering no   */
13487     /* longer hold                                                       */
13488     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13489         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13490         nCmailMovesRegistered --;
13491
13492         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13493           {
13494               free(cmailCommentList[lastLoadGameNumber - 1]);
13495               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13496           }
13497     }
13498
13499     if (cmailOldMove == -1) {
13500         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13501         return FALSE;
13502     }
13503
13504     if (currentMove > cmailOldMove + 1) {
13505         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13506         return FALSE;
13507     }
13508
13509     if (currentMove < cmailOldMove) {
13510         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13511         return FALSE;
13512     }
13513
13514     if (forwardMostMove > currentMove) {
13515         /* Silently truncate extra moves */
13516         TruncateGame();
13517     }
13518
13519     if (   (currentMove == cmailOldMove + 1)
13520         || (   (currentMove == cmailOldMove)
13521             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13522                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13523         if (gameInfo.result != GameUnfinished) {
13524             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13525         }
13526
13527         if (commentList[currentMove] != NULL) {
13528             cmailCommentList[lastLoadGameNumber - 1]
13529               = StrSave(commentList[currentMove]);
13530         }
13531         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13532
13533         if (appData.debugMode)
13534           fprintf(debugFP, "Saving %s for game %d\n",
13535                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13536
13537         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13538
13539         f = fopen(string, "w");
13540         if (appData.oldSaveStyle) {
13541             SaveGameOldStyle(f); /* also closes the file */
13542
13543             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13544             f = fopen(string, "w");
13545             SavePosition(f, 0, NULL); /* also closes the file */
13546         } else {
13547             fprintf(f, "{--------------\n");
13548             PrintPosition(f, currentMove);
13549             fprintf(f, "--------------}\n\n");
13550
13551             SaveGame(f, 0, NULL); /* also closes the file*/
13552         }
13553
13554         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13555         nCmailMovesRegistered ++;
13556     } else if (nCmailGames == 1) {
13557         DisplayError(_("You have not made a move yet"), 0);
13558         return FALSE;
13559     }
13560
13561     return TRUE;
13562 }
13563
13564 void
13565 MailMoveEvent ()
13566 {
13567 #if !WIN32
13568     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13569     FILE *commandOutput;
13570     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13571     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13572     int nBuffers;
13573     int i;
13574     int archived;
13575     char *arcDir;
13576
13577     if (! cmailMsgLoaded) {
13578         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13579         return;
13580     }
13581
13582     if (nCmailGames == nCmailResults) {
13583         DisplayError(_("No unfinished games"), 0);
13584         return;
13585     }
13586
13587 #if CMAIL_PROHIBIT_REMAIL
13588     if (cmailMailedMove) {
13589       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);
13590         DisplayError(msg, 0);
13591         return;
13592     }
13593 #endif
13594
13595     if (! (cmailMailedMove || RegisterMove())) return;
13596
13597     if (   cmailMailedMove
13598         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13599       snprintf(string, MSG_SIZ, partCommandString,
13600                appData.debugMode ? " -v" : "", appData.cmailGameName);
13601         commandOutput = popen(string, "r");
13602
13603         if (commandOutput == NULL) {
13604             DisplayError(_("Failed to invoke cmail"), 0);
13605         } else {
13606             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13607                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13608             }
13609             if (nBuffers > 1) {
13610                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13611                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13612                 nBytes = MSG_SIZ - 1;
13613             } else {
13614                 (void) memcpy(msg, buffer, nBytes);
13615             }
13616             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13617
13618             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13619                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13620
13621                 archived = TRUE;
13622                 for (i = 0; i < nCmailGames; i ++) {
13623                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13624                         archived = FALSE;
13625                     }
13626                 }
13627                 if (   archived
13628                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13629                         != NULL)) {
13630                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13631                            arcDir,
13632                            appData.cmailGameName,
13633                            gameInfo.date);
13634                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13635                     cmailMsgLoaded = FALSE;
13636                 }
13637             }
13638
13639             DisplayInformation(msg);
13640             pclose(commandOutput);
13641         }
13642     } else {
13643         if ((*cmailMsg) != '\0') {
13644             DisplayInformation(cmailMsg);
13645         }
13646     }
13647
13648     return;
13649 #endif /* !WIN32 */
13650 }
13651
13652 char *
13653 CmailMsg ()
13654 {
13655 #if WIN32
13656     return NULL;
13657 #else
13658     int  prependComma = 0;
13659     char number[5];
13660     char string[MSG_SIZ];       /* Space for game-list */
13661     int  i;
13662
13663     if (!cmailMsgLoaded) return "";
13664
13665     if (cmailMailedMove) {
13666       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13667     } else {
13668         /* Create a list of games left */
13669       snprintf(string, MSG_SIZ, "[");
13670         for (i = 0; i < nCmailGames; i ++) {
13671             if (! (   cmailMoveRegistered[i]
13672                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13673                 if (prependComma) {
13674                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13675                 } else {
13676                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13677                     prependComma = 1;
13678                 }
13679
13680                 strcat(string, number);
13681             }
13682         }
13683         strcat(string, "]");
13684
13685         if (nCmailMovesRegistered + nCmailResults == 0) {
13686             switch (nCmailGames) {
13687               case 1:
13688                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13689                 break;
13690
13691               case 2:
13692                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13693                 break;
13694
13695               default:
13696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13697                          nCmailGames);
13698                 break;
13699             }
13700         } else {
13701             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13702               case 1:
13703                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13704                          string);
13705                 break;
13706
13707               case 0:
13708                 if (nCmailResults == nCmailGames) {
13709                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13710                 } else {
13711                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13712                 }
13713                 break;
13714
13715               default:
13716                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13717                          string);
13718             }
13719         }
13720     }
13721     return cmailMsg;
13722 #endif /* WIN32 */
13723 }
13724
13725 void
13726 ResetGameEvent ()
13727 {
13728     if (gameMode == Training)
13729       SetTrainingModeOff();
13730
13731     Reset(TRUE, TRUE);
13732     cmailMsgLoaded = FALSE;
13733     if (appData.icsActive) {
13734       SendToICS(ics_prefix);
13735       SendToICS("refresh\n");
13736     }
13737 }
13738
13739 void
13740 ExitEvent (int status)
13741 {
13742     exiting++;
13743     if (exiting > 2) {
13744       /* Give up on clean exit */
13745       exit(status);
13746     }
13747     if (exiting > 1) {
13748       /* Keep trying for clean exit */
13749       return;
13750     }
13751
13752     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13753
13754     if (telnetISR != NULL) {
13755       RemoveInputSource(telnetISR);
13756     }
13757     if (icsPR != NoProc) {
13758       DestroyChildProcess(icsPR, TRUE);
13759     }
13760
13761     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13762     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13763
13764     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13765     /* make sure this other one finishes before killing it!                  */
13766     if(endingGame) { int count = 0;
13767         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13768         while(endingGame && count++ < 10) DoSleep(1);
13769         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13770     }
13771
13772     /* Kill off chess programs */
13773     if (first.pr != NoProc) {
13774         ExitAnalyzeMode();
13775
13776         DoSleep( appData.delayBeforeQuit );
13777         SendToProgram("quit\n", &first);
13778         DoSleep( appData.delayAfterQuit );
13779         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13780     }
13781     if (second.pr != NoProc) {
13782         DoSleep( appData.delayBeforeQuit );
13783         SendToProgram("quit\n", &second);
13784         DoSleep( appData.delayAfterQuit );
13785         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13786     }
13787     if (first.isr != NULL) {
13788         RemoveInputSource(first.isr);
13789     }
13790     if (second.isr != NULL) {
13791         RemoveInputSource(second.isr);
13792     }
13793
13794     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13795     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13796
13797     ShutDownFrontEnd();
13798     exit(status);
13799 }
13800
13801 void
13802 PauseEngine (ChessProgramState *cps)
13803 {
13804     SendToProgram("pause\n", cps);
13805     cps->pause = 2;
13806 }
13807
13808 void
13809 UnPauseEngine (ChessProgramState *cps)
13810 {
13811     SendToProgram("resume\n", cps);
13812     cps->pause = 1;
13813 }
13814
13815 void
13816 PauseEvent ()
13817 {
13818     if (appData.debugMode)
13819         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13820     if (pausing) {
13821         pausing = FALSE;
13822         ModeHighlight();
13823         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13824             StartClocks();
13825             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13826                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13827                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13828             }
13829             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13830             HandleMachineMove(stashedInputMove, stalledEngine);
13831             stalledEngine = NULL;
13832             return;
13833         }
13834         if (gameMode == MachinePlaysWhite ||
13835             gameMode == TwoMachinesPlay   ||
13836             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13837             if(first.pause)  UnPauseEngine(&first);
13838             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13839             if(second.pause) UnPauseEngine(&second);
13840             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13841             StartClocks();
13842         } else {
13843             DisplayBothClocks();
13844         }
13845         if (gameMode == PlayFromGameFile) {
13846             if (appData.timeDelay >= 0)
13847                 AutoPlayGameLoop();
13848         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13849             Reset(FALSE, TRUE);
13850             SendToICS(ics_prefix);
13851             SendToICS("refresh\n");
13852         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13853             ForwardInner(forwardMostMove);
13854         }
13855         pauseExamInvalid = FALSE;
13856     } else {
13857         switch (gameMode) {
13858           default:
13859             return;
13860           case IcsExamining:
13861             pauseExamForwardMostMove = forwardMostMove;
13862             pauseExamInvalid = FALSE;
13863             /* fall through */
13864           case IcsObserving:
13865           case IcsPlayingWhite:
13866           case IcsPlayingBlack:
13867             pausing = TRUE;
13868             ModeHighlight();
13869             return;
13870           case PlayFromGameFile:
13871             (void) StopLoadGameTimer();
13872             pausing = TRUE;
13873             ModeHighlight();
13874             break;
13875           case BeginningOfGame:
13876             if (appData.icsActive) return;
13877             /* else fall through */
13878           case MachinePlaysWhite:
13879           case MachinePlaysBlack:
13880           case TwoMachinesPlay:
13881             if (forwardMostMove == 0)
13882               return;           /* don't pause if no one has moved */
13883             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13884                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13885                 if(onMove->pause) {           // thinking engine can be paused
13886                     PauseEngine(onMove);      // do it
13887                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13888                         PauseEngine(onMove->other);
13889                     else
13890                         SendToProgram("easy\n", onMove->other);
13891                     StopClocks();
13892                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13893             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13894                 if(first.pause) {
13895                     PauseEngine(&first);
13896                     StopClocks();
13897                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13898             } else { // human on move, pause pondering by either method
13899                 if(first.pause)
13900                     PauseEngine(&first);
13901                 else if(appData.ponderNextMove)
13902                     SendToProgram("easy\n", &first);
13903                 StopClocks();
13904             }
13905             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13906           case AnalyzeMode:
13907             pausing = TRUE;
13908             ModeHighlight();
13909             break;
13910         }
13911     }
13912 }
13913
13914 void
13915 EditCommentEvent ()
13916 {
13917     char title[MSG_SIZ];
13918
13919     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13920       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13921     } else {
13922       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13923                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13924                parseList[currentMove - 1]);
13925     }
13926
13927     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13928 }
13929
13930
13931 void
13932 EditTagsEvent ()
13933 {
13934     char *tags = PGNTags(&gameInfo);
13935     bookUp = FALSE;
13936     EditTagsPopUp(tags, NULL);
13937     free(tags);
13938 }
13939
13940 void
13941 ToggleSecond ()
13942 {
13943   if(second.analyzing) {
13944     SendToProgram("exit\n", &second);
13945     second.analyzing = FALSE;
13946   } else {
13947     if (second.pr == NoProc) StartChessProgram(&second);
13948     InitChessProgram(&second, FALSE);
13949     FeedMovesToProgram(&second, currentMove);
13950
13951     SendToProgram("analyze\n", &second);
13952     second.analyzing = TRUE;
13953   }
13954 }
13955
13956 /* Toggle ShowThinking */
13957 void
13958 ToggleShowThinking()
13959 {
13960   appData.showThinking = !appData.showThinking;
13961   ShowThinkingEvent();
13962 }
13963
13964 int
13965 AnalyzeModeEvent ()
13966 {
13967     char buf[MSG_SIZ];
13968
13969     if (!first.analysisSupport) {
13970       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13971       DisplayError(buf, 0);
13972       return 0;
13973     }
13974     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13975     if (appData.icsActive) {
13976         if (gameMode != IcsObserving) {
13977           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13978             DisplayError(buf, 0);
13979             /* secure check */
13980             if (appData.icsEngineAnalyze) {
13981                 if (appData.debugMode)
13982                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13983                 ExitAnalyzeMode();
13984                 ModeHighlight();
13985             }
13986             return 0;
13987         }
13988         /* if enable, user wants to disable icsEngineAnalyze */
13989         if (appData.icsEngineAnalyze) {
13990                 ExitAnalyzeMode();
13991                 ModeHighlight();
13992                 return 0;
13993         }
13994         appData.icsEngineAnalyze = TRUE;
13995         if (appData.debugMode)
13996             fprintf(debugFP, "ICS engine analyze starting... \n");
13997     }
13998
13999     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14000     if (appData.noChessProgram || gameMode == AnalyzeMode)
14001       return 0;
14002
14003     if (gameMode != AnalyzeFile) {
14004         if (!appData.icsEngineAnalyze) {
14005                EditGameEvent();
14006                if (gameMode != EditGame) return 0;
14007         }
14008         if (!appData.showThinking) ToggleShowThinking();
14009         ResurrectChessProgram();
14010         SendToProgram("analyze\n", &first);
14011         first.analyzing = TRUE;
14012         /*first.maybeThinking = TRUE;*/
14013         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14014         EngineOutputPopUp();
14015     }
14016     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14017     pausing = FALSE;
14018     ModeHighlight();
14019     SetGameInfo();
14020
14021     StartAnalysisClock();
14022     GetTimeMark(&lastNodeCountTime);
14023     lastNodeCount = 0;
14024     return 1;
14025 }
14026
14027 void
14028 AnalyzeFileEvent ()
14029 {
14030     if (appData.noChessProgram || gameMode == AnalyzeFile)
14031       return;
14032
14033     if (!first.analysisSupport) {
14034       char buf[MSG_SIZ];
14035       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14036       DisplayError(buf, 0);
14037       return;
14038     }
14039
14040     if (gameMode != AnalyzeMode) {
14041         keepInfo = 1; // mere annotating should not alter PGN tags
14042         EditGameEvent();
14043         keepInfo = 0;
14044         if (gameMode != EditGame) return;
14045         if (!appData.showThinking) ToggleShowThinking();
14046         ResurrectChessProgram();
14047         SendToProgram("analyze\n", &first);
14048         first.analyzing = TRUE;
14049         /*first.maybeThinking = TRUE;*/
14050         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14051         EngineOutputPopUp();
14052     }
14053     gameMode = AnalyzeFile;
14054     pausing = FALSE;
14055     ModeHighlight();
14056
14057     StartAnalysisClock();
14058     GetTimeMark(&lastNodeCountTime);
14059     lastNodeCount = 0;
14060     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14061     AnalysisPeriodicEvent(1);
14062 }
14063
14064 void
14065 MachineWhiteEvent ()
14066 {
14067     char buf[MSG_SIZ];
14068     char *bookHit = NULL;
14069
14070     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14071       return;
14072
14073
14074     if (gameMode == PlayFromGameFile ||
14075         gameMode == TwoMachinesPlay  ||
14076         gameMode == Training         ||
14077         gameMode == AnalyzeMode      ||
14078         gameMode == EndOfGame)
14079         EditGameEvent();
14080
14081     if (gameMode == EditPosition)
14082         EditPositionDone(TRUE);
14083
14084     if (!WhiteOnMove(currentMove)) {
14085         DisplayError(_("It is not White's turn"), 0);
14086         return;
14087     }
14088
14089     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14090       ExitAnalyzeMode();
14091
14092     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14093         gameMode == AnalyzeFile)
14094         TruncateGame();
14095
14096     ResurrectChessProgram();    /* in case it isn't running */
14097     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14098         gameMode = MachinePlaysWhite;
14099         ResetClocks();
14100     } else
14101     gameMode = MachinePlaysWhite;
14102     pausing = FALSE;
14103     ModeHighlight();
14104     SetGameInfo();
14105     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14106     DisplayTitle(buf);
14107     if (first.sendName) {
14108       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14109       SendToProgram(buf, &first);
14110     }
14111     if (first.sendTime) {
14112       if (first.useColors) {
14113         SendToProgram("black\n", &first); /*gnu kludge*/
14114       }
14115       SendTimeRemaining(&first, TRUE);
14116     }
14117     if (first.useColors) {
14118       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14119     }
14120     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14121     SetMachineThinkingEnables();
14122     first.maybeThinking = TRUE;
14123     StartClocks();
14124     firstMove = FALSE;
14125
14126     if (appData.autoFlipView && !flipView) {
14127       flipView = !flipView;
14128       DrawPosition(FALSE, NULL);
14129       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14130     }
14131
14132     if(bookHit) { // [HGM] book: simulate book reply
14133         static char bookMove[MSG_SIZ]; // a bit generous?
14134
14135         programStats.nodes = programStats.depth = programStats.time =
14136         programStats.score = programStats.got_only_move = 0;
14137         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14138
14139         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14140         strcat(bookMove, bookHit);
14141         HandleMachineMove(bookMove, &first);
14142     }
14143 }
14144
14145 void
14146 MachineBlackEvent ()
14147 {
14148   char buf[MSG_SIZ];
14149   char *bookHit = NULL;
14150
14151     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14152         return;
14153
14154
14155     if (gameMode == PlayFromGameFile ||
14156         gameMode == TwoMachinesPlay  ||
14157         gameMode == Training         ||
14158         gameMode == AnalyzeMode      ||
14159         gameMode == EndOfGame)
14160         EditGameEvent();
14161
14162     if (gameMode == EditPosition)
14163         EditPositionDone(TRUE);
14164
14165     if (WhiteOnMove(currentMove)) {
14166         DisplayError(_("It is not Black's turn"), 0);
14167         return;
14168     }
14169
14170     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14171       ExitAnalyzeMode();
14172
14173     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14174         gameMode == AnalyzeFile)
14175         TruncateGame();
14176
14177     ResurrectChessProgram();    /* in case it isn't running */
14178     gameMode = MachinePlaysBlack;
14179     pausing = FALSE;
14180     ModeHighlight();
14181     SetGameInfo();
14182     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14183     DisplayTitle(buf);
14184     if (first.sendName) {
14185       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14186       SendToProgram(buf, &first);
14187     }
14188     if (first.sendTime) {
14189       if (first.useColors) {
14190         SendToProgram("white\n", &first); /*gnu kludge*/
14191       }
14192       SendTimeRemaining(&first, FALSE);
14193     }
14194     if (first.useColors) {
14195       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14196     }
14197     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14198     SetMachineThinkingEnables();
14199     first.maybeThinking = TRUE;
14200     StartClocks();
14201
14202     if (appData.autoFlipView && flipView) {
14203       flipView = !flipView;
14204       DrawPosition(FALSE, NULL);
14205       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14206     }
14207     if(bookHit) { // [HGM] book: simulate book reply
14208         static char bookMove[MSG_SIZ]; // a bit generous?
14209
14210         programStats.nodes = programStats.depth = programStats.time =
14211         programStats.score = programStats.got_only_move = 0;
14212         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14213
14214         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14215         strcat(bookMove, bookHit);
14216         HandleMachineMove(bookMove, &first);
14217     }
14218 }
14219
14220
14221 void
14222 DisplayTwoMachinesTitle ()
14223 {
14224     char buf[MSG_SIZ];
14225     if (appData.matchGames > 0) {
14226         if(appData.tourneyFile[0]) {
14227           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14228                    gameInfo.white, _("vs."), gameInfo.black,
14229                    nextGame+1, appData.matchGames+1,
14230                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14231         } else
14232         if (first.twoMachinesColor[0] == 'w') {
14233           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14234                    gameInfo.white, _("vs."),  gameInfo.black,
14235                    first.matchWins, second.matchWins,
14236                    matchGame - 1 - (first.matchWins + second.matchWins));
14237         } else {
14238           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14239                    gameInfo.white, _("vs."), gameInfo.black,
14240                    second.matchWins, first.matchWins,
14241                    matchGame - 1 - (first.matchWins + second.matchWins));
14242         }
14243     } else {
14244       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14245     }
14246     DisplayTitle(buf);
14247 }
14248
14249 void
14250 SettingsMenuIfReady ()
14251 {
14252   if (second.lastPing != second.lastPong) {
14253     DisplayMessage("", _("Waiting for second chess program"));
14254     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14255     return;
14256   }
14257   ThawUI();
14258   DisplayMessage("", "");
14259   SettingsPopUp(&second);
14260 }
14261
14262 int
14263 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14264 {
14265     char buf[MSG_SIZ];
14266     if (cps->pr == NoProc) {
14267         StartChessProgram(cps);
14268         if (cps->protocolVersion == 1) {
14269           retry();
14270           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14271         } else {
14272           /* kludge: allow timeout for initial "feature" command */
14273           if(retry != TwoMachinesEventIfReady) FreezeUI();
14274           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14275           DisplayMessage("", buf);
14276           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14277         }
14278         return 1;
14279     }
14280     return 0;
14281 }
14282
14283 void
14284 TwoMachinesEvent P((void))
14285 {
14286     int i;
14287     char buf[MSG_SIZ];
14288     ChessProgramState *onmove;
14289     char *bookHit = NULL;
14290     static int stalling = 0;
14291     TimeMark now;
14292     long wait;
14293
14294     if (appData.noChessProgram) return;
14295
14296     switch (gameMode) {
14297       case TwoMachinesPlay:
14298         return;
14299       case MachinePlaysWhite:
14300       case MachinePlaysBlack:
14301         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14302             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14303             return;
14304         }
14305         /* fall through */
14306       case BeginningOfGame:
14307       case PlayFromGameFile:
14308       case EndOfGame:
14309         EditGameEvent();
14310         if (gameMode != EditGame) return;
14311         break;
14312       case EditPosition:
14313         EditPositionDone(TRUE);
14314         break;
14315       case AnalyzeMode:
14316       case AnalyzeFile:
14317         ExitAnalyzeMode();
14318         break;
14319       case EditGame:
14320       default:
14321         break;
14322     }
14323
14324 //    forwardMostMove = currentMove;
14325     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14326     startingEngine = TRUE;
14327
14328     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14329
14330     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14331     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14332       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14333       return;
14334     }
14335     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14336
14337     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14338         startingEngine = FALSE;
14339         DisplayError("second engine does not play this", 0);
14340         return;
14341     }
14342
14343     if(!stalling) {
14344       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14345       SendToProgram("force\n", &second);
14346       stalling = 1;
14347       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14348       return;
14349     }
14350     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14351     if(appData.matchPause>10000 || appData.matchPause<10)
14352                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14353     wait = SubtractTimeMarks(&now, &pauseStart);
14354     if(wait < appData.matchPause) {
14355         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14356         return;
14357     }
14358     // we are now committed to starting the game
14359     stalling = 0;
14360     DisplayMessage("", "");
14361     if (startedFromSetupPosition) {
14362         SendBoard(&second, backwardMostMove);
14363     if (appData.debugMode) {
14364         fprintf(debugFP, "Two Machines\n");
14365     }
14366     }
14367     for (i = backwardMostMove; i < forwardMostMove; i++) {
14368         SendMoveToProgram(i, &second);
14369     }
14370
14371     gameMode = TwoMachinesPlay;
14372     pausing = startingEngine = FALSE;
14373     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14374     SetGameInfo();
14375     DisplayTwoMachinesTitle();
14376     firstMove = TRUE;
14377     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14378         onmove = &first;
14379     } else {
14380         onmove = &second;
14381     }
14382     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14383     SendToProgram(first.computerString, &first);
14384     if (first.sendName) {
14385       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14386       SendToProgram(buf, &first);
14387     }
14388     SendToProgram(second.computerString, &second);
14389     if (second.sendName) {
14390       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14391       SendToProgram(buf, &second);
14392     }
14393
14394     ResetClocks();
14395     if (!first.sendTime || !second.sendTime) {
14396         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14397         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14398     }
14399     if (onmove->sendTime) {
14400       if (onmove->useColors) {
14401         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14402       }
14403       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14404     }
14405     if (onmove->useColors) {
14406       SendToProgram(onmove->twoMachinesColor, onmove);
14407     }
14408     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14409 //    SendToProgram("go\n", onmove);
14410     onmove->maybeThinking = TRUE;
14411     SetMachineThinkingEnables();
14412
14413     StartClocks();
14414
14415     if(bookHit) { // [HGM] book: simulate book reply
14416         static char bookMove[MSG_SIZ]; // a bit generous?
14417
14418         programStats.nodes = programStats.depth = programStats.time =
14419         programStats.score = programStats.got_only_move = 0;
14420         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14421
14422         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14423         strcat(bookMove, bookHit);
14424         savedMessage = bookMove; // args for deferred call
14425         savedState = onmove;
14426         ScheduleDelayedEvent(DeferredBookMove, 1);
14427     }
14428 }
14429
14430 void
14431 TrainingEvent ()
14432 {
14433     if (gameMode == Training) {
14434       SetTrainingModeOff();
14435       gameMode = PlayFromGameFile;
14436       DisplayMessage("", _("Training mode off"));
14437     } else {
14438       gameMode = Training;
14439       animateTraining = appData.animate;
14440
14441       /* make sure we are not already at the end of the game */
14442       if (currentMove < forwardMostMove) {
14443         SetTrainingModeOn();
14444         DisplayMessage("", _("Training mode on"));
14445       } else {
14446         gameMode = PlayFromGameFile;
14447         DisplayError(_("Already at end of game"), 0);
14448       }
14449     }
14450     ModeHighlight();
14451 }
14452
14453 void
14454 IcsClientEvent ()
14455 {
14456     if (!appData.icsActive) return;
14457     switch (gameMode) {
14458       case IcsPlayingWhite:
14459       case IcsPlayingBlack:
14460       case IcsObserving:
14461       case IcsIdle:
14462       case BeginningOfGame:
14463       case IcsExamining:
14464         return;
14465
14466       case EditGame:
14467         break;
14468
14469       case EditPosition:
14470         EditPositionDone(TRUE);
14471         break;
14472
14473       case AnalyzeMode:
14474       case AnalyzeFile:
14475         ExitAnalyzeMode();
14476         break;
14477
14478       default:
14479         EditGameEvent();
14480         break;
14481     }
14482
14483     gameMode = IcsIdle;
14484     ModeHighlight();
14485     return;
14486 }
14487
14488 void
14489 EditGameEvent ()
14490 {
14491     int i;
14492
14493     switch (gameMode) {
14494       case Training:
14495         SetTrainingModeOff();
14496         break;
14497       case MachinePlaysWhite:
14498       case MachinePlaysBlack:
14499       case BeginningOfGame:
14500         SendToProgram("force\n", &first);
14501         SetUserThinkingEnables();
14502         break;
14503       case PlayFromGameFile:
14504         (void) StopLoadGameTimer();
14505         if (gameFileFP != NULL) {
14506             gameFileFP = NULL;
14507         }
14508         break;
14509       case EditPosition:
14510         EditPositionDone(TRUE);
14511         break;
14512       case AnalyzeMode:
14513       case AnalyzeFile:
14514         ExitAnalyzeMode();
14515         SendToProgram("force\n", &first);
14516         break;
14517       case TwoMachinesPlay:
14518         GameEnds(EndOfFile, NULL, GE_PLAYER);
14519         ResurrectChessProgram();
14520         SetUserThinkingEnables();
14521         break;
14522       case EndOfGame:
14523         ResurrectChessProgram();
14524         break;
14525       case IcsPlayingBlack:
14526       case IcsPlayingWhite:
14527         DisplayError(_("Warning: You are still playing a game"), 0);
14528         break;
14529       case IcsObserving:
14530         DisplayError(_("Warning: You are still observing a game"), 0);
14531         break;
14532       case IcsExamining:
14533         DisplayError(_("Warning: You are still examining a game"), 0);
14534         break;
14535       case IcsIdle:
14536         break;
14537       case EditGame:
14538       default:
14539         return;
14540     }
14541
14542     pausing = FALSE;
14543     StopClocks();
14544     first.offeredDraw = second.offeredDraw = 0;
14545
14546     if (gameMode == PlayFromGameFile) {
14547         whiteTimeRemaining = timeRemaining[0][currentMove];
14548         blackTimeRemaining = timeRemaining[1][currentMove];
14549         DisplayTitle("");
14550     }
14551
14552     if (gameMode == MachinePlaysWhite ||
14553         gameMode == MachinePlaysBlack ||
14554         gameMode == TwoMachinesPlay ||
14555         gameMode == EndOfGame) {
14556         i = forwardMostMove;
14557         while (i > currentMove) {
14558             SendToProgram("undo\n", &first);
14559             i--;
14560         }
14561         if(!adjustedClock) {
14562         whiteTimeRemaining = timeRemaining[0][currentMove];
14563         blackTimeRemaining = timeRemaining[1][currentMove];
14564         DisplayBothClocks();
14565         }
14566         if (whiteFlag || blackFlag) {
14567             whiteFlag = blackFlag = 0;
14568         }
14569         DisplayTitle("");
14570     }
14571
14572     gameMode = EditGame;
14573     ModeHighlight();
14574     SetGameInfo();
14575 }
14576
14577
14578 void
14579 EditPositionEvent ()
14580 {
14581     if (gameMode == EditPosition) {
14582         EditGameEvent();
14583         return;
14584     }
14585
14586     EditGameEvent();
14587     if (gameMode != EditGame) return;
14588
14589     gameMode = EditPosition;
14590     ModeHighlight();
14591     SetGameInfo();
14592     if (currentMove > 0)
14593       CopyBoard(boards[0], boards[currentMove]);
14594
14595     blackPlaysFirst = !WhiteOnMove(currentMove);
14596     ResetClocks();
14597     currentMove = forwardMostMove = backwardMostMove = 0;
14598     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14599     DisplayMove(-1);
14600     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14601 }
14602
14603 void
14604 ExitAnalyzeMode ()
14605 {
14606     /* [DM] icsEngineAnalyze - possible call from other functions */
14607     if (appData.icsEngineAnalyze) {
14608         appData.icsEngineAnalyze = FALSE;
14609
14610         DisplayMessage("",_("Close ICS engine analyze..."));
14611     }
14612     if (first.analysisSupport && first.analyzing) {
14613       SendToBoth("exit\n");
14614       first.analyzing = second.analyzing = FALSE;
14615     }
14616     thinkOutput[0] = NULLCHAR;
14617 }
14618
14619 void
14620 EditPositionDone (Boolean fakeRights)
14621 {
14622     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14623
14624     startedFromSetupPosition = TRUE;
14625     InitChessProgram(&first, FALSE);
14626     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14627       boards[0][EP_STATUS] = EP_NONE;
14628       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14629       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14630         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14631         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14632       } else boards[0][CASTLING][2] = NoRights;
14633       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14634         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14635         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14636       } else boards[0][CASTLING][5] = NoRights;
14637       if(gameInfo.variant == VariantSChess) {
14638         int i;
14639         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14640           boards[0][VIRGIN][i] = 0;
14641           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14642           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14643         }
14644       }
14645     }
14646     SendToProgram("force\n", &first);
14647     if (blackPlaysFirst) {
14648         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14649         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14650         currentMove = forwardMostMove = backwardMostMove = 1;
14651         CopyBoard(boards[1], boards[0]);
14652     } else {
14653         currentMove = forwardMostMove = backwardMostMove = 0;
14654     }
14655     SendBoard(&first, forwardMostMove);
14656     if (appData.debugMode) {
14657         fprintf(debugFP, "EditPosDone\n");
14658     }
14659     DisplayTitle("");
14660     DisplayMessage("", "");
14661     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14662     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14663     gameMode = EditGame;
14664     ModeHighlight();
14665     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14666     ClearHighlights(); /* [AS] */
14667 }
14668
14669 /* Pause for `ms' milliseconds */
14670 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14671 void
14672 TimeDelay (long ms)
14673 {
14674     TimeMark m1, m2;
14675
14676     GetTimeMark(&m1);
14677     do {
14678         GetTimeMark(&m2);
14679     } while (SubtractTimeMarks(&m2, &m1) < ms);
14680 }
14681
14682 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14683 void
14684 SendMultiLineToICS (char *buf)
14685 {
14686     char temp[MSG_SIZ+1], *p;
14687     int len;
14688
14689     len = strlen(buf);
14690     if (len > MSG_SIZ)
14691       len = MSG_SIZ;
14692
14693     strncpy(temp, buf, len);
14694     temp[len] = 0;
14695
14696     p = temp;
14697     while (*p) {
14698         if (*p == '\n' || *p == '\r')
14699           *p = ' ';
14700         ++p;
14701     }
14702
14703     strcat(temp, "\n");
14704     SendToICS(temp);
14705     SendToPlayer(temp, strlen(temp));
14706 }
14707
14708 void
14709 SetWhiteToPlayEvent ()
14710 {
14711     if (gameMode == EditPosition) {
14712         blackPlaysFirst = FALSE;
14713         DisplayBothClocks();    /* works because currentMove is 0 */
14714     } else if (gameMode == IcsExamining) {
14715         SendToICS(ics_prefix);
14716         SendToICS("tomove white\n");
14717     }
14718 }
14719
14720 void
14721 SetBlackToPlayEvent ()
14722 {
14723     if (gameMode == EditPosition) {
14724         blackPlaysFirst = TRUE;
14725         currentMove = 1;        /* kludge */
14726         DisplayBothClocks();
14727         currentMove = 0;
14728     } else if (gameMode == IcsExamining) {
14729         SendToICS(ics_prefix);
14730         SendToICS("tomove black\n");
14731     }
14732 }
14733
14734 void
14735 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14736 {
14737     char buf[MSG_SIZ];
14738     ChessSquare piece = boards[0][y][x];
14739     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14740     static int lastVariant;
14741
14742     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14743
14744     switch (selection) {
14745       case ClearBoard:
14746         CopyBoard(currentBoard, boards[0]);
14747         CopyBoard(menuBoard, initialPosition);
14748         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14749             SendToICS(ics_prefix);
14750             SendToICS("bsetup clear\n");
14751         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14752             SendToICS(ics_prefix);
14753             SendToICS("clearboard\n");
14754         } else {
14755             int nonEmpty = 0;
14756             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14757                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14758                 for (y = 0; y < BOARD_HEIGHT; y++) {
14759                     if (gameMode == IcsExamining) {
14760                         if (boards[currentMove][y][x] != EmptySquare) {
14761                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14762                                     AAA + x, ONE + y);
14763                             SendToICS(buf);
14764                         }
14765                     } else {
14766                         if(boards[0][y][x] != p) nonEmpty++;
14767                         boards[0][y][x] = p;
14768                     }
14769                 }
14770                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14771             }
14772             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14773                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14774                     ChessSquare p = menuBoard[0][x];
14775                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14776                     p = menuBoard[BOARD_HEIGHT-1][x];
14777                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14778                 }
14779                 DisplayMessage("Clicking clock again restores position", "");
14780                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14781                 if(!nonEmpty) { // asked to clear an empty board
14782                     CopyBoard(boards[0], menuBoard);
14783                 } else
14784                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14785                     CopyBoard(boards[0], initialPosition);
14786                 } else
14787                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14788                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14789                     CopyBoard(boards[0], erasedBoard);
14790                 } else
14791                     CopyBoard(erasedBoard, currentBoard);
14792
14793             }
14794         }
14795         if (gameMode == EditPosition) {
14796             DrawPosition(FALSE, boards[0]);
14797         }
14798         break;
14799
14800       case WhitePlay:
14801         SetWhiteToPlayEvent();
14802         break;
14803
14804       case BlackPlay:
14805         SetBlackToPlayEvent();
14806         break;
14807
14808       case EmptySquare:
14809         if (gameMode == IcsExamining) {
14810             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14811             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14812             SendToICS(buf);
14813         } else {
14814             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14815                 if(x == BOARD_LEFT-2) {
14816                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14817                     boards[0][y][1] = 0;
14818                 } else
14819                 if(x == BOARD_RGHT+1) {
14820                     if(y >= gameInfo.holdingsSize) break;
14821                     boards[0][y][BOARD_WIDTH-2] = 0;
14822                 } else break;
14823             }
14824             boards[0][y][x] = EmptySquare;
14825             DrawPosition(FALSE, boards[0]);
14826         }
14827         break;
14828
14829       case PromotePiece:
14830         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14831            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14832             selection = (ChessSquare) (PROMOTED piece);
14833         } else if(piece == EmptySquare) selection = WhiteSilver;
14834         else selection = (ChessSquare)((int)piece - 1);
14835         goto defaultlabel;
14836
14837       case DemotePiece:
14838         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14839            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14840             selection = (ChessSquare) (DEMOTED piece);
14841         } else if(piece == EmptySquare) selection = BlackSilver;
14842         else selection = (ChessSquare)((int)piece + 1);
14843         goto defaultlabel;
14844
14845       case WhiteQueen:
14846       case BlackQueen:
14847         if(gameInfo.variant == VariantShatranj ||
14848            gameInfo.variant == VariantXiangqi  ||
14849            gameInfo.variant == VariantCourier  ||
14850            gameInfo.variant == VariantASEAN    ||
14851            gameInfo.variant == VariantMakruk     )
14852             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14853         goto defaultlabel;
14854
14855       case WhiteKing:
14856       case BlackKing:
14857         if(gameInfo.variant == VariantXiangqi)
14858             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14859         if(gameInfo.variant == VariantKnightmate)
14860             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14861       default:
14862         defaultlabel:
14863         if (gameMode == IcsExamining) {
14864             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14865             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14866                      PieceToChar(selection), AAA + x, ONE + y);
14867             SendToICS(buf);
14868         } else {
14869             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14870                 int n;
14871                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14872                     n = PieceToNumber(selection - BlackPawn);
14873                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14874                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14875                     boards[0][BOARD_HEIGHT-1-n][1]++;
14876                 } else
14877                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14878                     n = PieceToNumber(selection);
14879                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14880                     boards[0][n][BOARD_WIDTH-1] = selection;
14881                     boards[0][n][BOARD_WIDTH-2]++;
14882                 }
14883             } else
14884             boards[0][y][x] = selection;
14885             DrawPosition(TRUE, boards[0]);
14886             ClearHighlights();
14887             fromX = fromY = -1;
14888         }
14889         break;
14890     }
14891 }
14892
14893
14894 void
14895 DropMenuEvent (ChessSquare selection, int x, int y)
14896 {
14897     ChessMove moveType;
14898
14899     switch (gameMode) {
14900       case IcsPlayingWhite:
14901       case MachinePlaysBlack:
14902         if (!WhiteOnMove(currentMove)) {
14903             DisplayMoveError(_("It is Black's turn"));
14904             return;
14905         }
14906         moveType = WhiteDrop;
14907         break;
14908       case IcsPlayingBlack:
14909       case MachinePlaysWhite:
14910         if (WhiteOnMove(currentMove)) {
14911             DisplayMoveError(_("It is White's turn"));
14912             return;
14913         }
14914         moveType = BlackDrop;
14915         break;
14916       case EditGame:
14917         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14918         break;
14919       default:
14920         return;
14921     }
14922
14923     if (moveType == BlackDrop && selection < BlackPawn) {
14924       selection = (ChessSquare) ((int) selection
14925                                  + (int) BlackPawn - (int) WhitePawn);
14926     }
14927     if (boards[currentMove][y][x] != EmptySquare) {
14928         DisplayMoveError(_("That square is occupied"));
14929         return;
14930     }
14931
14932     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14933 }
14934
14935 void
14936 AcceptEvent ()
14937 {
14938     /* Accept a pending offer of any kind from opponent */
14939
14940     if (appData.icsActive) {
14941         SendToICS(ics_prefix);
14942         SendToICS("accept\n");
14943     } else if (cmailMsgLoaded) {
14944         if (currentMove == cmailOldMove &&
14945             commentList[cmailOldMove] != NULL &&
14946             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14947                    "Black offers a draw" : "White offers a draw")) {
14948             TruncateGame();
14949             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14950             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14951         } else {
14952             DisplayError(_("There is no pending offer on this move"), 0);
14953             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14954         }
14955     } else {
14956         /* Not used for offers from chess program */
14957     }
14958 }
14959
14960 void
14961 DeclineEvent ()
14962 {
14963     /* Decline a pending offer of any kind from opponent */
14964
14965     if (appData.icsActive) {
14966         SendToICS(ics_prefix);
14967         SendToICS("decline\n");
14968     } else if (cmailMsgLoaded) {
14969         if (currentMove == cmailOldMove &&
14970             commentList[cmailOldMove] != NULL &&
14971             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14972                    "Black offers a draw" : "White offers a draw")) {
14973 #ifdef NOTDEF
14974             AppendComment(cmailOldMove, "Draw declined", TRUE);
14975             DisplayComment(cmailOldMove - 1, "Draw declined");
14976 #endif /*NOTDEF*/
14977         } else {
14978             DisplayError(_("There is no pending offer on this move"), 0);
14979         }
14980     } else {
14981         /* Not used for offers from chess program */
14982     }
14983 }
14984
14985 void
14986 RematchEvent ()
14987 {
14988     /* Issue ICS rematch command */
14989     if (appData.icsActive) {
14990         SendToICS(ics_prefix);
14991         SendToICS("rematch\n");
14992     }
14993 }
14994
14995 void
14996 CallFlagEvent ()
14997 {
14998     /* Call your opponent's flag (claim a win on time) */
14999     if (appData.icsActive) {
15000         SendToICS(ics_prefix);
15001         SendToICS("flag\n");
15002     } else {
15003         switch (gameMode) {
15004           default:
15005             return;
15006           case MachinePlaysWhite:
15007             if (whiteFlag) {
15008                 if (blackFlag)
15009                   GameEnds(GameIsDrawn, "Both players ran out of time",
15010                            GE_PLAYER);
15011                 else
15012                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15013             } else {
15014                 DisplayError(_("Your opponent is not out of time"), 0);
15015             }
15016             break;
15017           case MachinePlaysBlack:
15018             if (blackFlag) {
15019                 if (whiteFlag)
15020                   GameEnds(GameIsDrawn, "Both players ran out of time",
15021                            GE_PLAYER);
15022                 else
15023                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15024             } else {
15025                 DisplayError(_("Your opponent is not out of time"), 0);
15026             }
15027             break;
15028         }
15029     }
15030 }
15031
15032 void
15033 ClockClick (int which)
15034 {       // [HGM] code moved to back-end from winboard.c
15035         if(which) { // black clock
15036           if (gameMode == EditPosition || gameMode == IcsExamining) {
15037             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15038             SetBlackToPlayEvent();
15039           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15040           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15041           } else if (shiftKey) {
15042             AdjustClock(which, -1);
15043           } else if (gameMode == IcsPlayingWhite ||
15044                      gameMode == MachinePlaysBlack) {
15045             CallFlagEvent();
15046           }
15047         } else { // white clock
15048           if (gameMode == EditPosition || gameMode == IcsExamining) {
15049             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15050             SetWhiteToPlayEvent();
15051           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15052           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15053           } else if (shiftKey) {
15054             AdjustClock(which, -1);
15055           } else if (gameMode == IcsPlayingBlack ||
15056                    gameMode == MachinePlaysWhite) {
15057             CallFlagEvent();
15058           }
15059         }
15060 }
15061
15062 void
15063 DrawEvent ()
15064 {
15065     /* Offer draw or accept pending draw offer from opponent */
15066
15067     if (appData.icsActive) {
15068         /* Note: tournament rules require draw offers to be
15069            made after you make your move but before you punch
15070            your clock.  Currently ICS doesn't let you do that;
15071            instead, you immediately punch your clock after making
15072            a move, but you can offer a draw at any time. */
15073
15074         SendToICS(ics_prefix);
15075         SendToICS("draw\n");
15076         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15077     } else if (cmailMsgLoaded) {
15078         if (currentMove == cmailOldMove &&
15079             commentList[cmailOldMove] != NULL &&
15080             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15081                    "Black offers a draw" : "White offers a draw")) {
15082             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15083             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15084         } else if (currentMove == cmailOldMove + 1) {
15085             char *offer = WhiteOnMove(cmailOldMove) ?
15086               "White offers a draw" : "Black offers a draw";
15087             AppendComment(currentMove, offer, TRUE);
15088             DisplayComment(currentMove - 1, offer);
15089             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15090         } else {
15091             DisplayError(_("You must make your move before offering a draw"), 0);
15092             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15093         }
15094     } else if (first.offeredDraw) {
15095         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15096     } else {
15097         if (first.sendDrawOffers) {
15098             SendToProgram("draw\n", &first);
15099             userOfferedDraw = TRUE;
15100         }
15101     }
15102 }
15103
15104 void
15105 AdjournEvent ()
15106 {
15107     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15108
15109     if (appData.icsActive) {
15110         SendToICS(ics_prefix);
15111         SendToICS("adjourn\n");
15112     } else {
15113         /* Currently GNU Chess doesn't offer or accept Adjourns */
15114     }
15115 }
15116
15117
15118 void
15119 AbortEvent ()
15120 {
15121     /* Offer Abort or accept pending Abort offer from opponent */
15122
15123     if (appData.icsActive) {
15124         SendToICS(ics_prefix);
15125         SendToICS("abort\n");
15126     } else {
15127         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15128     }
15129 }
15130
15131 void
15132 ResignEvent ()
15133 {
15134     /* Resign.  You can do this even if it's not your turn. */
15135
15136     if (appData.icsActive) {
15137         SendToICS(ics_prefix);
15138         SendToICS("resign\n");
15139     } else {
15140         switch (gameMode) {
15141           case MachinePlaysWhite:
15142             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15143             break;
15144           case MachinePlaysBlack:
15145             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15146             break;
15147           case EditGame:
15148             if (cmailMsgLoaded) {
15149                 TruncateGame();
15150                 if (WhiteOnMove(cmailOldMove)) {
15151                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15152                 } else {
15153                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15154                 }
15155                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15156             }
15157             break;
15158           default:
15159             break;
15160         }
15161     }
15162 }
15163
15164
15165 void
15166 StopObservingEvent ()
15167 {
15168     /* Stop observing current games */
15169     SendToICS(ics_prefix);
15170     SendToICS("unobserve\n");
15171 }
15172
15173 void
15174 StopExaminingEvent ()
15175 {
15176     /* Stop observing current game */
15177     SendToICS(ics_prefix);
15178     SendToICS("unexamine\n");
15179 }
15180
15181 void
15182 ForwardInner (int target)
15183 {
15184     int limit; int oldSeekGraphUp = seekGraphUp;
15185
15186     if (appData.debugMode)
15187         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15188                 target, currentMove, forwardMostMove);
15189
15190     if (gameMode == EditPosition)
15191       return;
15192
15193     seekGraphUp = FALSE;
15194     MarkTargetSquares(1);
15195
15196     if (gameMode == PlayFromGameFile && !pausing)
15197       PauseEvent();
15198
15199     if (gameMode == IcsExamining && pausing)
15200       limit = pauseExamForwardMostMove;
15201     else
15202       limit = forwardMostMove;
15203
15204     if (target > limit) target = limit;
15205
15206     if (target > 0 && moveList[target - 1][0]) {
15207         int fromX, fromY, toX, toY;
15208         toX = moveList[target - 1][2] - AAA;
15209         toY = moveList[target - 1][3] - ONE;
15210         if (moveList[target - 1][1] == '@') {
15211             if (appData.highlightLastMove) {
15212                 SetHighlights(-1, -1, toX, toY);
15213             }
15214         } else {
15215             fromX = moveList[target - 1][0] - AAA;
15216             fromY = moveList[target - 1][1] - ONE;
15217             if (target == currentMove + 1) {
15218                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15219             }
15220             if (appData.highlightLastMove) {
15221                 SetHighlights(fromX, fromY, toX, toY);
15222             }
15223         }
15224     }
15225     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15226         gameMode == Training || gameMode == PlayFromGameFile ||
15227         gameMode == AnalyzeFile) {
15228         while (currentMove < target) {
15229             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15230             SendMoveToProgram(currentMove++, &first);
15231         }
15232     } else {
15233         currentMove = target;
15234     }
15235
15236     if (gameMode == EditGame || gameMode == EndOfGame) {
15237         whiteTimeRemaining = timeRemaining[0][currentMove];
15238         blackTimeRemaining = timeRemaining[1][currentMove];
15239     }
15240     DisplayBothClocks();
15241     DisplayMove(currentMove - 1);
15242     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15243     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15244     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15245         DisplayComment(currentMove - 1, commentList[currentMove]);
15246     }
15247     ClearMap(); // [HGM] exclude: invalidate map
15248 }
15249
15250
15251 void
15252 ForwardEvent ()
15253 {
15254     if (gameMode == IcsExamining && !pausing) {
15255         SendToICS(ics_prefix);
15256         SendToICS("forward\n");
15257     } else {
15258         ForwardInner(currentMove + 1);
15259     }
15260 }
15261
15262 void
15263 ToEndEvent ()
15264 {
15265     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15266         /* to optimze, we temporarily turn off analysis mode while we feed
15267          * the remaining moves to the engine. Otherwise we get analysis output
15268          * after each move.
15269          */
15270         if (first.analysisSupport) {
15271           SendToProgram("exit\nforce\n", &first);
15272           first.analyzing = FALSE;
15273         }
15274     }
15275
15276     if (gameMode == IcsExamining && !pausing) {
15277         SendToICS(ics_prefix);
15278         SendToICS("forward 999999\n");
15279     } else {
15280         ForwardInner(forwardMostMove);
15281     }
15282
15283     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15284         /* we have fed all the moves, so reactivate analysis mode */
15285         SendToProgram("analyze\n", &first);
15286         first.analyzing = TRUE;
15287         /*first.maybeThinking = TRUE;*/
15288         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15289     }
15290 }
15291
15292 void
15293 BackwardInner (int target)
15294 {
15295     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15296
15297     if (appData.debugMode)
15298         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15299                 target, currentMove, forwardMostMove);
15300
15301     if (gameMode == EditPosition) return;
15302     seekGraphUp = FALSE;
15303     MarkTargetSquares(1);
15304     if (currentMove <= backwardMostMove) {
15305         ClearHighlights();
15306         DrawPosition(full_redraw, boards[currentMove]);
15307         return;
15308     }
15309     if (gameMode == PlayFromGameFile && !pausing)
15310       PauseEvent();
15311
15312     if (moveList[target][0]) {
15313         int fromX, fromY, toX, toY;
15314         toX = moveList[target][2] - AAA;
15315         toY = moveList[target][3] - ONE;
15316         if (moveList[target][1] == '@') {
15317             if (appData.highlightLastMove) {
15318                 SetHighlights(-1, -1, toX, toY);
15319             }
15320         } else {
15321             fromX = moveList[target][0] - AAA;
15322             fromY = moveList[target][1] - ONE;
15323             if (target == currentMove - 1) {
15324                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15325             }
15326             if (appData.highlightLastMove) {
15327                 SetHighlights(fromX, fromY, toX, toY);
15328             }
15329         }
15330     }
15331     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15332         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15333         while (currentMove > target) {
15334             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15335                 // null move cannot be undone. Reload program with move history before it.
15336                 int i;
15337                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15338                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15339                 }
15340                 SendBoard(&first, i);
15341               if(second.analyzing) SendBoard(&second, i);
15342                 for(currentMove=i; currentMove<target; currentMove++) {
15343                     SendMoveToProgram(currentMove, &first);
15344                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15345                 }
15346                 break;
15347             }
15348             SendToBoth("undo\n");
15349             currentMove--;
15350         }
15351     } else {
15352         currentMove = target;
15353     }
15354
15355     if (gameMode == EditGame || gameMode == EndOfGame) {
15356         whiteTimeRemaining = timeRemaining[0][currentMove];
15357         blackTimeRemaining = timeRemaining[1][currentMove];
15358     }
15359     DisplayBothClocks();
15360     DisplayMove(currentMove - 1);
15361     DrawPosition(full_redraw, boards[currentMove]);
15362     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15363     // [HGM] PV info: routine tests if comment empty
15364     DisplayComment(currentMove - 1, commentList[currentMove]);
15365     ClearMap(); // [HGM] exclude: invalidate map
15366 }
15367
15368 void
15369 BackwardEvent ()
15370 {
15371     if (gameMode == IcsExamining && !pausing) {
15372         SendToICS(ics_prefix);
15373         SendToICS("backward\n");
15374     } else {
15375         BackwardInner(currentMove - 1);
15376     }
15377 }
15378
15379 void
15380 ToStartEvent ()
15381 {
15382     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15383         /* to optimize, we temporarily turn off analysis mode while we undo
15384          * all the moves. Otherwise we get analysis output after each undo.
15385          */
15386         if (first.analysisSupport) {
15387           SendToProgram("exit\nforce\n", &first);
15388           first.analyzing = FALSE;
15389         }
15390     }
15391
15392     if (gameMode == IcsExamining && !pausing) {
15393         SendToICS(ics_prefix);
15394         SendToICS("backward 999999\n");
15395     } else {
15396         BackwardInner(backwardMostMove);
15397     }
15398
15399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15400         /* we have fed all the moves, so reactivate analysis mode */
15401         SendToProgram("analyze\n", &first);
15402         first.analyzing = TRUE;
15403         /*first.maybeThinking = TRUE;*/
15404         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15405     }
15406 }
15407
15408 void
15409 ToNrEvent (int to)
15410 {
15411   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15412   if (to >= forwardMostMove) to = forwardMostMove;
15413   if (to <= backwardMostMove) to = backwardMostMove;
15414   if (to < currentMove) {
15415     BackwardInner(to);
15416   } else {
15417     ForwardInner(to);
15418   }
15419 }
15420
15421 void
15422 RevertEvent (Boolean annotate)
15423 {
15424     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15425         return;
15426     }
15427     if (gameMode != IcsExamining) {
15428         DisplayError(_("You are not examining a game"), 0);
15429         return;
15430     }
15431     if (pausing) {
15432         DisplayError(_("You can't revert while pausing"), 0);
15433         return;
15434     }
15435     SendToICS(ics_prefix);
15436     SendToICS("revert\n");
15437 }
15438
15439 void
15440 RetractMoveEvent ()
15441 {
15442     switch (gameMode) {
15443       case MachinePlaysWhite:
15444       case MachinePlaysBlack:
15445         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15446             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15447             return;
15448         }
15449         if (forwardMostMove < 2) return;
15450         currentMove = forwardMostMove = forwardMostMove - 2;
15451         whiteTimeRemaining = timeRemaining[0][currentMove];
15452         blackTimeRemaining = timeRemaining[1][currentMove];
15453         DisplayBothClocks();
15454         DisplayMove(currentMove - 1);
15455         ClearHighlights();/*!! could figure this out*/
15456         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15457         SendToProgram("remove\n", &first);
15458         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15459         break;
15460
15461       case BeginningOfGame:
15462       default:
15463         break;
15464
15465       case IcsPlayingWhite:
15466       case IcsPlayingBlack:
15467         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15468             SendToICS(ics_prefix);
15469             SendToICS("takeback 2\n");
15470         } else {
15471             SendToICS(ics_prefix);
15472             SendToICS("takeback 1\n");
15473         }
15474         break;
15475     }
15476 }
15477
15478 void
15479 MoveNowEvent ()
15480 {
15481     ChessProgramState *cps;
15482
15483     switch (gameMode) {
15484       case MachinePlaysWhite:
15485         if (!WhiteOnMove(forwardMostMove)) {
15486             DisplayError(_("It is your turn"), 0);
15487             return;
15488         }
15489         cps = &first;
15490         break;
15491       case MachinePlaysBlack:
15492         if (WhiteOnMove(forwardMostMove)) {
15493             DisplayError(_("It is your turn"), 0);
15494             return;
15495         }
15496         cps = &first;
15497         break;
15498       case TwoMachinesPlay:
15499         if (WhiteOnMove(forwardMostMove) ==
15500             (first.twoMachinesColor[0] == 'w')) {
15501             cps = &first;
15502         } else {
15503             cps = &second;
15504         }
15505         break;
15506       case BeginningOfGame:
15507       default:
15508         return;
15509     }
15510     SendToProgram("?\n", cps);
15511 }
15512
15513 void
15514 TruncateGameEvent ()
15515 {
15516     EditGameEvent();
15517     if (gameMode != EditGame) return;
15518     TruncateGame();
15519 }
15520
15521 void
15522 TruncateGame ()
15523 {
15524     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15525     if (forwardMostMove > currentMove) {
15526         if (gameInfo.resultDetails != NULL) {
15527             free(gameInfo.resultDetails);
15528             gameInfo.resultDetails = NULL;
15529             gameInfo.result = GameUnfinished;
15530         }
15531         forwardMostMove = currentMove;
15532         HistorySet(parseList, backwardMostMove, forwardMostMove,
15533                    currentMove-1);
15534     }
15535 }
15536
15537 void
15538 HintEvent ()
15539 {
15540     if (appData.noChessProgram) return;
15541     switch (gameMode) {
15542       case MachinePlaysWhite:
15543         if (WhiteOnMove(forwardMostMove)) {
15544             DisplayError(_("Wait until your turn."), 0);
15545             return;
15546         }
15547         break;
15548       case BeginningOfGame:
15549       case MachinePlaysBlack:
15550         if (!WhiteOnMove(forwardMostMove)) {
15551             DisplayError(_("Wait until your turn."), 0);
15552             return;
15553         }
15554         break;
15555       default:
15556         DisplayError(_("No hint available"), 0);
15557         return;
15558     }
15559     SendToProgram("hint\n", &first);
15560     hintRequested = TRUE;
15561 }
15562
15563 void
15564 CreateBookEvent ()
15565 {
15566     ListGame * lg = (ListGame *) gameList.head;
15567     FILE *f, *g;
15568     int nItem;
15569     static int secondTime = FALSE;
15570
15571     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15572         DisplayError(_("Game list not loaded or empty"), 0);
15573         return;
15574     }
15575
15576     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15577         fclose(g);
15578         secondTime++;
15579         DisplayNote(_("Book file exists! Try again for overwrite."));
15580         return;
15581     }
15582
15583     creatingBook = TRUE;
15584     secondTime = FALSE;
15585
15586     /* Get list size */
15587     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15588         LoadGame(f, nItem, "", TRUE);
15589         AddGameToBook(TRUE);
15590         lg = (ListGame *) lg->node.succ;
15591     }
15592
15593     creatingBook = FALSE;
15594     FlushBook();
15595 }
15596
15597 void
15598 BookEvent ()
15599 {
15600     if (appData.noChessProgram) return;
15601     switch (gameMode) {
15602       case MachinePlaysWhite:
15603         if (WhiteOnMove(forwardMostMove)) {
15604             DisplayError(_("Wait until your turn."), 0);
15605             return;
15606         }
15607         break;
15608       case BeginningOfGame:
15609       case MachinePlaysBlack:
15610         if (!WhiteOnMove(forwardMostMove)) {
15611             DisplayError(_("Wait until your turn."), 0);
15612             return;
15613         }
15614         break;
15615       case EditPosition:
15616         EditPositionDone(TRUE);
15617         break;
15618       case TwoMachinesPlay:
15619         return;
15620       default:
15621         break;
15622     }
15623     SendToProgram("bk\n", &first);
15624     bookOutput[0] = NULLCHAR;
15625     bookRequested = TRUE;
15626 }
15627
15628 void
15629 AboutGameEvent ()
15630 {
15631     char *tags = PGNTags(&gameInfo);
15632     TagsPopUp(tags, CmailMsg());
15633     free(tags);
15634 }
15635
15636 /* end button procedures */
15637
15638 void
15639 PrintPosition (FILE *fp, int move)
15640 {
15641     int i, j;
15642
15643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15644         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15645             char c = PieceToChar(boards[move][i][j]);
15646             fputc(c == 'x' ? '.' : c, fp);
15647             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15648         }
15649     }
15650     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15651       fprintf(fp, "white to play\n");
15652     else
15653       fprintf(fp, "black to play\n");
15654 }
15655
15656 void
15657 PrintOpponents (FILE *fp)
15658 {
15659     if (gameInfo.white != NULL) {
15660         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15661     } else {
15662         fprintf(fp, "\n");
15663     }
15664 }
15665
15666 /* Find last component of program's own name, using some heuristics */
15667 void
15668 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15669 {
15670     char *p, *q, c;
15671     int local = (strcmp(host, "localhost") == 0);
15672     while (!local && (p = strchr(prog, ';')) != NULL) {
15673         p++;
15674         while (*p == ' ') p++;
15675         prog = p;
15676     }
15677     if (*prog == '"' || *prog == '\'') {
15678         q = strchr(prog + 1, *prog);
15679     } else {
15680         q = strchr(prog, ' ');
15681     }
15682     if (q == NULL) q = prog + strlen(prog);
15683     p = q;
15684     while (p >= prog && *p != '/' && *p != '\\') p--;
15685     p++;
15686     if(p == prog && *p == '"') p++;
15687     c = *q; *q = 0;
15688     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15689     memcpy(buf, p, q - p);
15690     buf[q - p] = NULLCHAR;
15691     if (!local) {
15692         strcat(buf, "@");
15693         strcat(buf, host);
15694     }
15695 }
15696
15697 char *
15698 TimeControlTagValue ()
15699 {
15700     char buf[MSG_SIZ];
15701     if (!appData.clockMode) {
15702       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15703     } else if (movesPerSession > 0) {
15704       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15705     } else if (timeIncrement == 0) {
15706       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15707     } else {
15708       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15709     }
15710     return StrSave(buf);
15711 }
15712
15713 void
15714 SetGameInfo ()
15715 {
15716     /* This routine is used only for certain modes */
15717     VariantClass v = gameInfo.variant;
15718     ChessMove r = GameUnfinished;
15719     char *p = NULL;
15720
15721     if(keepInfo) return;
15722
15723     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15724         r = gameInfo.result;
15725         p = gameInfo.resultDetails;
15726         gameInfo.resultDetails = NULL;
15727     }
15728     ClearGameInfo(&gameInfo);
15729     gameInfo.variant = v;
15730
15731     switch (gameMode) {
15732       case MachinePlaysWhite:
15733         gameInfo.event = StrSave( appData.pgnEventHeader );
15734         gameInfo.site = StrSave(HostName());
15735         gameInfo.date = PGNDate();
15736         gameInfo.round = StrSave("-");
15737         gameInfo.white = StrSave(first.tidy);
15738         gameInfo.black = StrSave(UserName());
15739         gameInfo.timeControl = TimeControlTagValue();
15740         break;
15741
15742       case MachinePlaysBlack:
15743         gameInfo.event = StrSave( appData.pgnEventHeader );
15744         gameInfo.site = StrSave(HostName());
15745         gameInfo.date = PGNDate();
15746         gameInfo.round = StrSave("-");
15747         gameInfo.white = StrSave(UserName());
15748         gameInfo.black = StrSave(first.tidy);
15749         gameInfo.timeControl = TimeControlTagValue();
15750         break;
15751
15752       case TwoMachinesPlay:
15753         gameInfo.event = StrSave( appData.pgnEventHeader );
15754         gameInfo.site = StrSave(HostName());
15755         gameInfo.date = PGNDate();
15756         if (roundNr > 0) {
15757             char buf[MSG_SIZ];
15758             snprintf(buf, MSG_SIZ, "%d", roundNr);
15759             gameInfo.round = StrSave(buf);
15760         } else {
15761             gameInfo.round = StrSave("-");
15762         }
15763         if (first.twoMachinesColor[0] == 'w') {
15764             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15765             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15766         } else {
15767             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15768             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15769         }
15770         gameInfo.timeControl = TimeControlTagValue();
15771         break;
15772
15773       case EditGame:
15774         gameInfo.event = StrSave("Edited game");
15775         gameInfo.site = StrSave(HostName());
15776         gameInfo.date = PGNDate();
15777         gameInfo.round = StrSave("-");
15778         gameInfo.white = StrSave("-");
15779         gameInfo.black = StrSave("-");
15780         gameInfo.result = r;
15781         gameInfo.resultDetails = p;
15782         break;
15783
15784       case EditPosition:
15785         gameInfo.event = StrSave("Edited position");
15786         gameInfo.site = StrSave(HostName());
15787         gameInfo.date = PGNDate();
15788         gameInfo.round = StrSave("-");
15789         gameInfo.white = StrSave("-");
15790         gameInfo.black = StrSave("-");
15791         break;
15792
15793       case IcsPlayingWhite:
15794       case IcsPlayingBlack:
15795       case IcsObserving:
15796       case IcsExamining:
15797         break;
15798
15799       case PlayFromGameFile:
15800         gameInfo.event = StrSave("Game from non-PGN file");
15801         gameInfo.site = StrSave(HostName());
15802         gameInfo.date = PGNDate();
15803         gameInfo.round = StrSave("-");
15804         gameInfo.white = StrSave("?");
15805         gameInfo.black = StrSave("?");
15806         break;
15807
15808       default:
15809         break;
15810     }
15811 }
15812
15813 void
15814 ReplaceComment (int index, char *text)
15815 {
15816     int len;
15817     char *p;
15818     float score;
15819
15820     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15821        pvInfoList[index-1].depth == len &&
15822        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15823        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15824     while (*text == '\n') text++;
15825     len = strlen(text);
15826     while (len > 0 && text[len - 1] == '\n') len--;
15827
15828     if (commentList[index] != NULL)
15829       free(commentList[index]);
15830
15831     if (len == 0) {
15832         commentList[index] = NULL;
15833         return;
15834     }
15835   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15836       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15837       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15838     commentList[index] = (char *) malloc(len + 2);
15839     strncpy(commentList[index], text, len);
15840     commentList[index][len] = '\n';
15841     commentList[index][len + 1] = NULLCHAR;
15842   } else {
15843     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15844     char *p;
15845     commentList[index] = (char *) malloc(len + 7);
15846     safeStrCpy(commentList[index], "{\n", 3);
15847     safeStrCpy(commentList[index]+2, text, len+1);
15848     commentList[index][len+2] = NULLCHAR;
15849     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15850     strcat(commentList[index], "\n}\n");
15851   }
15852 }
15853
15854 void
15855 CrushCRs (char *text)
15856 {
15857   char *p = text;
15858   char *q = text;
15859   char ch;
15860
15861   do {
15862     ch = *p++;
15863     if (ch == '\r') continue;
15864     *q++ = ch;
15865   } while (ch != '\0');
15866 }
15867
15868 void
15869 AppendComment (int index, char *text, Boolean addBraces)
15870 /* addBraces  tells if we should add {} */
15871 {
15872     int oldlen, len;
15873     char *old;
15874
15875 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15876     if(addBraces == 3) addBraces = 0; else // force appending literally
15877     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15878
15879     CrushCRs(text);
15880     while (*text == '\n') text++;
15881     len = strlen(text);
15882     while (len > 0 && text[len - 1] == '\n') len--;
15883     text[len] = NULLCHAR;
15884
15885     if (len == 0) return;
15886
15887     if (commentList[index] != NULL) {
15888       Boolean addClosingBrace = addBraces;
15889         old = commentList[index];
15890         oldlen = strlen(old);
15891         while(commentList[index][oldlen-1] ==  '\n')
15892           commentList[index][--oldlen] = NULLCHAR;
15893         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15894         safeStrCpy(commentList[index], old, oldlen + len + 6);
15895         free(old);
15896         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15897         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15898           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15899           while (*text == '\n') { text++; len--; }
15900           commentList[index][--oldlen] = NULLCHAR;
15901       }
15902         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15903         else          strcat(commentList[index], "\n");
15904         strcat(commentList[index], text);
15905         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15906         else          strcat(commentList[index], "\n");
15907     } else {
15908         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15909         if(addBraces)
15910           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15911         else commentList[index][0] = NULLCHAR;
15912         strcat(commentList[index], text);
15913         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15914         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15915     }
15916 }
15917
15918 static char *
15919 FindStr (char * text, char * sub_text)
15920 {
15921     char * result = strstr( text, sub_text );
15922
15923     if( result != NULL ) {
15924         result += strlen( sub_text );
15925     }
15926
15927     return result;
15928 }
15929
15930 /* [AS] Try to extract PV info from PGN comment */
15931 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15932 char *
15933 GetInfoFromComment (int index, char * text)
15934 {
15935     char * sep = text, *p;
15936
15937     if( text != NULL && index > 0 ) {
15938         int score = 0;
15939         int depth = 0;
15940         int time = -1, sec = 0, deci;
15941         char * s_eval = FindStr( text, "[%eval " );
15942         char * s_emt = FindStr( text, "[%emt " );
15943 #if 0
15944         if( s_eval != NULL || s_emt != NULL ) {
15945 #else
15946         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15947 #endif
15948             /* New style */
15949             char delim;
15950
15951             if( s_eval != NULL ) {
15952                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15953                     return text;
15954                 }
15955
15956                 if( delim != ']' ) {
15957                     return text;
15958                 }
15959             }
15960
15961             if( s_emt != NULL ) {
15962             }
15963                 return text;
15964         }
15965         else {
15966             /* We expect something like: [+|-]nnn.nn/dd */
15967             int score_lo = 0;
15968
15969             if(*text != '{') return text; // [HGM] braces: must be normal comment
15970
15971             sep = strchr( text, '/' );
15972             if( sep == NULL || sep < (text+4) ) {
15973                 return text;
15974             }
15975
15976             p = text;
15977             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15978             if(p[1] == '(') { // comment starts with PV
15979                p = strchr(p, ')'); // locate end of PV
15980                if(p == NULL || sep < p+5) return text;
15981                // at this point we have something like "{(.*) +0.23/6 ..."
15982                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15983                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15984                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15985             }
15986             time = -1; sec = -1; deci = -1;
15987             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15988                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15989                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15990                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15991                 return text;
15992             }
15993
15994             if( score_lo < 0 || score_lo >= 100 ) {
15995                 return text;
15996             }
15997
15998             if(sec >= 0) time = 600*time + 10*sec; else
15999             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16000
16001             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16002
16003             /* [HGM] PV time: now locate end of PV info */
16004             while( *++sep >= '0' && *sep <= '9'); // strip depth
16005             if(time >= 0)
16006             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16007             if(sec >= 0)
16008             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16009             if(deci >= 0)
16010             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16011             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16012         }
16013
16014         if( depth <= 0 ) {
16015             return text;
16016         }
16017
16018         if( time < 0 ) {
16019             time = -1;
16020         }
16021
16022         pvInfoList[index-1].depth = depth;
16023         pvInfoList[index-1].score = score;
16024         pvInfoList[index-1].time  = 10*time; // centi-sec
16025         if(*sep == '}') *sep = 0; else *--sep = '{';
16026         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16027     }
16028     return sep;
16029 }
16030
16031 void
16032 SendToProgram (char *message, ChessProgramState *cps)
16033 {
16034     int count, outCount, error;
16035     char buf[MSG_SIZ];
16036
16037     if (cps->pr == NoProc) return;
16038     Attention(cps);
16039
16040     if (appData.debugMode) {
16041         TimeMark now;
16042         GetTimeMark(&now);
16043         fprintf(debugFP, "%ld >%-6s: %s",
16044                 SubtractTimeMarks(&now, &programStartTime),
16045                 cps->which, message);
16046         if(serverFP)
16047             fprintf(serverFP, "%ld >%-6s: %s",
16048                 SubtractTimeMarks(&now, &programStartTime),
16049                 cps->which, message), fflush(serverFP);
16050     }
16051
16052     count = strlen(message);
16053     outCount = OutputToProcess(cps->pr, message, count, &error);
16054     if (outCount < count && !exiting
16055                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16056       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16057       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16058         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16059             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16060                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16061                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16062                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16063             } else {
16064                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16065                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16066                 gameInfo.result = res;
16067             }
16068             gameInfo.resultDetails = StrSave(buf);
16069         }
16070         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16071         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16072     }
16073 }
16074
16075 void
16076 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16077 {
16078     char *end_str;
16079     char buf[MSG_SIZ];
16080     ChessProgramState *cps = (ChessProgramState *)closure;
16081
16082     if (isr != cps->isr) return; /* Killed intentionally */
16083     if (count <= 0) {
16084         if (count == 0) {
16085             RemoveInputSource(cps->isr);
16086             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16087                     _(cps->which), cps->program);
16088             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16089             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16090                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16091                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16092                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16093                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16094                 } else {
16095                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16096                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16097                     gameInfo.result = res;
16098                 }
16099                 gameInfo.resultDetails = StrSave(buf);
16100             }
16101             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16102             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16103         } else {
16104             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16105                     _(cps->which), cps->program);
16106             RemoveInputSource(cps->isr);
16107
16108             /* [AS] Program is misbehaving badly... kill it */
16109             if( count == -2 ) {
16110                 DestroyChildProcess( cps->pr, 9 );
16111                 cps->pr = NoProc;
16112             }
16113
16114             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16115         }
16116         return;
16117     }
16118
16119     if ((end_str = strchr(message, '\r')) != NULL)
16120       *end_str = NULLCHAR;
16121     if ((end_str = strchr(message, '\n')) != NULL)
16122       *end_str = NULLCHAR;
16123
16124     if (appData.debugMode) {
16125         TimeMark now; int print = 1;
16126         char *quote = ""; char c; int i;
16127
16128         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16129                 char start = message[0];
16130                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16131                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16132                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16133                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16134                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16135                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16136                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16137                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16138                    sscanf(message, "hint: %c", &c)!=1 &&
16139                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16140                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16141                     print = (appData.engineComments >= 2);
16142                 }
16143                 message[0] = start; // restore original message
16144         }
16145         if(print) {
16146                 GetTimeMark(&now);
16147                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16148                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16149                         quote,
16150                         message);
16151                 if(serverFP)
16152                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16153                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16154                         quote,
16155                         message), fflush(serverFP);
16156         }
16157     }
16158
16159     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16160     if (appData.icsEngineAnalyze) {
16161         if (strstr(message, "whisper") != NULL ||
16162              strstr(message, "kibitz") != NULL ||
16163             strstr(message, "tellics") != NULL) return;
16164     }
16165
16166     HandleMachineMove(message, cps);
16167 }
16168
16169
16170 void
16171 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16172 {
16173     char buf[MSG_SIZ];
16174     int seconds;
16175
16176     if( timeControl_2 > 0 ) {
16177         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16178             tc = timeControl_2;
16179         }
16180     }
16181     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16182     inc /= cps->timeOdds;
16183     st  /= cps->timeOdds;
16184
16185     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16186
16187     if (st > 0) {
16188       /* Set exact time per move, normally using st command */
16189       if (cps->stKludge) {
16190         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16191         seconds = st % 60;
16192         if (seconds == 0) {
16193           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16194         } else {
16195           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16196         }
16197       } else {
16198         snprintf(buf, MSG_SIZ, "st %d\n", st);
16199       }
16200     } else {
16201       /* Set conventional or incremental time control, using level command */
16202       if (seconds == 0) {
16203         /* Note old gnuchess bug -- minutes:seconds used to not work.
16204            Fixed in later versions, but still avoid :seconds
16205            when seconds is 0. */
16206         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16207       } else {
16208         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16209                  seconds, inc/1000.);
16210       }
16211     }
16212     SendToProgram(buf, cps);
16213
16214     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16215     /* Orthogonally, limit search to given depth */
16216     if (sd > 0) {
16217       if (cps->sdKludge) {
16218         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16219       } else {
16220         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16221       }
16222       SendToProgram(buf, cps);
16223     }
16224
16225     if(cps->nps >= 0) { /* [HGM] nps */
16226         if(cps->supportsNPS == FALSE)
16227           cps->nps = -1; // don't use if engine explicitly says not supported!
16228         else {
16229           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16230           SendToProgram(buf, cps);
16231         }
16232     }
16233 }
16234
16235 ChessProgramState *
16236 WhitePlayer ()
16237 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16238 {
16239     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16240        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16241         return &second;
16242     return &first;
16243 }
16244
16245 void
16246 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16247 {
16248     char message[MSG_SIZ];
16249     long time, otime;
16250
16251     /* Note: this routine must be called when the clocks are stopped
16252        or when they have *just* been set or switched; otherwise
16253        it will be off by the time since the current tick started.
16254     */
16255     if (machineWhite) {
16256         time = whiteTimeRemaining / 10;
16257         otime = blackTimeRemaining / 10;
16258     } else {
16259         time = blackTimeRemaining / 10;
16260         otime = whiteTimeRemaining / 10;
16261     }
16262     /* [HGM] translate opponent's time by time-odds factor */
16263     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16264
16265     if (time <= 0) time = 1;
16266     if (otime <= 0) otime = 1;
16267
16268     snprintf(message, MSG_SIZ, "time %ld\n", time);
16269     SendToProgram(message, cps);
16270
16271     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16272     SendToProgram(message, cps);
16273 }
16274
16275 char *
16276 EngineDefinedVariant (ChessProgramState *cps, int n)
16277 {   // return name of n-th unknown variant that engine supports
16278     static char buf[MSG_SIZ];
16279     char *p, *s = cps->variants;
16280     if(!s) return NULL;
16281     do { // parse string from variants feature
16282       VariantClass v;
16283         p = strchr(s, ',');
16284         if(p) *p = NULLCHAR;
16285       v = StringToVariant(s);
16286       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16287         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16288             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16289         }
16290         if(p) *p++ = ',';
16291         if(n < 0) return buf;
16292     } while(s = p);
16293     return NULL;
16294 }
16295
16296 int
16297 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16298 {
16299   char buf[MSG_SIZ];
16300   int len = strlen(name);
16301   int val;
16302
16303   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16304     (*p) += len + 1;
16305     sscanf(*p, "%d", &val);
16306     *loc = (val != 0);
16307     while (**p && **p != ' ')
16308       (*p)++;
16309     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16310     SendToProgram(buf, cps);
16311     return TRUE;
16312   }
16313   return FALSE;
16314 }
16315
16316 int
16317 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16318 {
16319   char buf[MSG_SIZ];
16320   int len = strlen(name);
16321   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16322     (*p) += len + 1;
16323     sscanf(*p, "%d", loc);
16324     while (**p && **p != ' ') (*p)++;
16325     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16326     SendToProgram(buf, cps);
16327     return TRUE;
16328   }
16329   return FALSE;
16330 }
16331
16332 int
16333 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16334 {
16335   char buf[MSG_SIZ];
16336   int len = strlen(name);
16337   if (strncmp((*p), name, len) == 0
16338       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16339     (*p) += len + 2;
16340     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16341     sscanf(*p, "%[^\"]", *loc);
16342     while (**p && **p != '\"') (*p)++;
16343     if (**p == '\"') (*p)++;
16344     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16345     SendToProgram(buf, cps);
16346     return TRUE;
16347   }
16348   return FALSE;
16349 }
16350
16351 int
16352 ParseOption (Option *opt, ChessProgramState *cps)
16353 // [HGM] options: process the string that defines an engine option, and determine
16354 // name, type, default value, and allowed value range
16355 {
16356         char *p, *q, buf[MSG_SIZ];
16357         int n, min = (-1)<<31, max = 1<<31, def;
16358
16359         if(p = strstr(opt->name, " -spin ")) {
16360             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16361             if(max < min) max = min; // enforce consistency
16362             if(def < min) def = min;
16363             if(def > max) def = max;
16364             opt->value = def;
16365             opt->min = min;
16366             opt->max = max;
16367             opt->type = Spin;
16368         } else if((p = strstr(opt->name, " -slider "))) {
16369             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16370             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16371             if(max < min) max = min; // enforce consistency
16372             if(def < min) def = min;
16373             if(def > max) def = max;
16374             opt->value = def;
16375             opt->min = min;
16376             opt->max = max;
16377             opt->type = Spin; // Slider;
16378         } else if((p = strstr(opt->name, " -string "))) {
16379             opt->textValue = p+9;
16380             opt->type = TextBox;
16381         } else if((p = strstr(opt->name, " -file "))) {
16382             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16383             opt->textValue = p+7;
16384             opt->type = FileName; // FileName;
16385         } else if((p = strstr(opt->name, " -path "))) {
16386             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16387             opt->textValue = p+7;
16388             opt->type = PathName; // PathName;
16389         } else if(p = strstr(opt->name, " -check ")) {
16390             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16391             opt->value = (def != 0);
16392             opt->type = CheckBox;
16393         } else if(p = strstr(opt->name, " -combo ")) {
16394             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16395             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16396             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16397             opt->value = n = 0;
16398             while(q = StrStr(q, " /// ")) {
16399                 n++; *q = 0;    // count choices, and null-terminate each of them
16400                 q += 5;
16401                 if(*q == '*') { // remember default, which is marked with * prefix
16402                     q++;
16403                     opt->value = n;
16404                 }
16405                 cps->comboList[cps->comboCnt++] = q;
16406             }
16407             cps->comboList[cps->comboCnt++] = NULL;
16408             opt->max = n + 1;
16409             opt->type = ComboBox;
16410         } else if(p = strstr(opt->name, " -button")) {
16411             opt->type = Button;
16412         } else if(p = strstr(opt->name, " -save")) {
16413             opt->type = SaveButton;
16414         } else return FALSE;
16415         *p = 0; // terminate option name
16416         // now look if the command-line options define a setting for this engine option.
16417         if(cps->optionSettings && cps->optionSettings[0])
16418             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16419         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16420           snprintf(buf, MSG_SIZ, "option %s", p);
16421                 if(p = strstr(buf, ",")) *p = 0;
16422                 if(q = strchr(buf, '=')) switch(opt->type) {
16423                     case ComboBox:
16424                         for(n=0; n<opt->max; n++)
16425                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16426                         break;
16427                     case TextBox:
16428                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16429                         break;
16430                     case Spin:
16431                     case CheckBox:
16432                         opt->value = atoi(q+1);
16433                     default:
16434                         break;
16435                 }
16436                 strcat(buf, "\n");
16437                 SendToProgram(buf, cps);
16438         }
16439         return TRUE;
16440 }
16441
16442 void
16443 FeatureDone (ChessProgramState *cps, int val)
16444 {
16445   DelayedEventCallback cb = GetDelayedEvent();
16446   if ((cb == InitBackEnd3 && cps == &first) ||
16447       (cb == SettingsMenuIfReady && cps == &second) ||
16448       (cb == LoadEngine) ||
16449       (cb == TwoMachinesEventIfReady)) {
16450     CancelDelayedEvent();
16451     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16452   }
16453   cps->initDone = val;
16454   if(val) cps->reload = FALSE;
16455 }
16456
16457 /* Parse feature command from engine */
16458 void
16459 ParseFeatures (char *args, ChessProgramState *cps)
16460 {
16461   char *p = args;
16462   char *q = NULL;
16463   int val;
16464   char buf[MSG_SIZ];
16465
16466   for (;;) {
16467     while (*p == ' ') p++;
16468     if (*p == NULLCHAR) return;
16469
16470     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16471     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16472     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16473     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16474     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16475     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16476     if (BoolFeature(&p, "reuse", &val, cps)) {
16477       /* Engine can disable reuse, but can't enable it if user said no */
16478       if (!val) cps->reuse = FALSE;
16479       continue;
16480     }
16481     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16482     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16483       if (gameMode == TwoMachinesPlay) {
16484         DisplayTwoMachinesTitle();
16485       } else {
16486         DisplayTitle("");
16487       }
16488       continue;
16489     }
16490     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16491     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16492     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16493     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16494     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16495     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16496     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16497     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16498     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16499     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16500     if (IntFeature(&p, "done", &val, cps)) {
16501       FeatureDone(cps, val);
16502       continue;
16503     }
16504     /* Added by Tord: */
16505     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16506     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16507     /* End of additions by Tord */
16508
16509     /* [HGM] added features: */
16510     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16511     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16512     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16513     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16514     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16515     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16516     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16517     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16518         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16519         FREE(cps->option[cps->nrOptions].name);
16520         cps->option[cps->nrOptions].name = q; q = NULL;
16521         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16522           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16523             SendToProgram(buf, cps);
16524             continue;
16525         }
16526         if(cps->nrOptions >= MAX_OPTIONS) {
16527             cps->nrOptions--;
16528             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16529             DisplayError(buf, 0);
16530         }
16531         continue;
16532     }
16533     /* End of additions by HGM */
16534
16535     /* unknown feature: complain and skip */
16536     q = p;
16537     while (*q && *q != '=') q++;
16538     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16539     SendToProgram(buf, cps);
16540     p = q;
16541     if (*p == '=') {
16542       p++;
16543       if (*p == '\"') {
16544         p++;
16545         while (*p && *p != '\"') p++;
16546         if (*p == '\"') p++;
16547       } else {
16548         while (*p && *p != ' ') p++;
16549       }
16550     }
16551   }
16552
16553 }
16554
16555 void
16556 PeriodicUpdatesEvent (int newState)
16557 {
16558     if (newState == appData.periodicUpdates)
16559       return;
16560
16561     appData.periodicUpdates=newState;
16562
16563     /* Display type changes, so update it now */
16564 //    DisplayAnalysis();
16565
16566     /* Get the ball rolling again... */
16567     if (newState) {
16568         AnalysisPeriodicEvent(1);
16569         StartAnalysisClock();
16570     }
16571 }
16572
16573 void
16574 PonderNextMoveEvent (int newState)
16575 {
16576     if (newState == appData.ponderNextMove) return;
16577     if (gameMode == EditPosition) EditPositionDone(TRUE);
16578     if (newState) {
16579         SendToProgram("hard\n", &first);
16580         if (gameMode == TwoMachinesPlay) {
16581             SendToProgram("hard\n", &second);
16582         }
16583     } else {
16584         SendToProgram("easy\n", &first);
16585         thinkOutput[0] = NULLCHAR;
16586         if (gameMode == TwoMachinesPlay) {
16587             SendToProgram("easy\n", &second);
16588         }
16589     }
16590     appData.ponderNextMove = newState;
16591 }
16592
16593 void
16594 NewSettingEvent (int option, int *feature, char *command, int value)
16595 {
16596     char buf[MSG_SIZ];
16597
16598     if (gameMode == EditPosition) EditPositionDone(TRUE);
16599     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16600     if(feature == NULL || *feature) SendToProgram(buf, &first);
16601     if (gameMode == TwoMachinesPlay) {
16602         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16603     }
16604 }
16605
16606 void
16607 ShowThinkingEvent ()
16608 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16609 {
16610     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16611     int newState = appData.showThinking
16612         // [HGM] thinking: other features now need thinking output as well
16613         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16614
16615     if (oldState == newState) return;
16616     oldState = newState;
16617     if (gameMode == EditPosition) EditPositionDone(TRUE);
16618     if (oldState) {
16619         SendToProgram("post\n", &first);
16620         if (gameMode == TwoMachinesPlay) {
16621             SendToProgram("post\n", &second);
16622         }
16623     } else {
16624         SendToProgram("nopost\n", &first);
16625         thinkOutput[0] = NULLCHAR;
16626         if (gameMode == TwoMachinesPlay) {
16627             SendToProgram("nopost\n", &second);
16628         }
16629     }
16630 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16631 }
16632
16633 void
16634 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16635 {
16636   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16637   if (pr == NoProc) return;
16638   AskQuestion(title, question, replyPrefix, pr);
16639 }
16640
16641 void
16642 TypeInEvent (char firstChar)
16643 {
16644     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16645         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16646         gameMode == AnalyzeMode || gameMode == EditGame ||
16647         gameMode == EditPosition || gameMode == IcsExamining ||
16648         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16649         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16650                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16651                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16652         gameMode == Training) PopUpMoveDialog(firstChar);
16653 }
16654
16655 void
16656 TypeInDoneEvent (char *move)
16657 {
16658         Board board;
16659         int n, fromX, fromY, toX, toY;
16660         char promoChar;
16661         ChessMove moveType;
16662
16663         // [HGM] FENedit
16664         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16665                 EditPositionPasteFEN(move);
16666                 return;
16667         }
16668         // [HGM] movenum: allow move number to be typed in any mode
16669         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16670           ToNrEvent(2*n-1);
16671           return;
16672         }
16673         // undocumented kludge: allow command-line option to be typed in!
16674         // (potentially fatal, and does not implement the effect of the option.)
16675         // should only be used for options that are values on which future decisions will be made,
16676         // and definitely not on options that would be used during initialization.
16677         if(strstr(move, "!!! -") == move) {
16678             ParseArgsFromString(move+4);
16679             return;
16680         }
16681
16682       if (gameMode != EditGame && currentMove != forwardMostMove &&
16683         gameMode != Training) {
16684         DisplayMoveError(_("Displayed move is not current"));
16685       } else {
16686         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16687           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16688         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16689         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16690           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16691           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16692         } else {
16693           DisplayMoveError(_("Could not parse move"));
16694         }
16695       }
16696 }
16697
16698 void
16699 DisplayMove (int moveNumber)
16700 {
16701     char message[MSG_SIZ];
16702     char res[MSG_SIZ];
16703     char cpThinkOutput[MSG_SIZ];
16704
16705     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16706
16707     if (moveNumber == forwardMostMove - 1 ||
16708         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16709
16710         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16711
16712         if (strchr(cpThinkOutput, '\n')) {
16713             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16714         }
16715     } else {
16716         *cpThinkOutput = NULLCHAR;
16717     }
16718
16719     /* [AS] Hide thinking from human user */
16720     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16721         *cpThinkOutput = NULLCHAR;
16722         if( thinkOutput[0] != NULLCHAR ) {
16723             int i;
16724
16725             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16726                 cpThinkOutput[i] = '.';
16727             }
16728             cpThinkOutput[i] = NULLCHAR;
16729             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16730         }
16731     }
16732
16733     if (moveNumber == forwardMostMove - 1 &&
16734         gameInfo.resultDetails != NULL) {
16735         if (gameInfo.resultDetails[0] == NULLCHAR) {
16736           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16737         } else {
16738           snprintf(res, MSG_SIZ, " {%s} %s",
16739                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16740         }
16741     } else {
16742         res[0] = NULLCHAR;
16743     }
16744
16745     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16746         DisplayMessage(res, cpThinkOutput);
16747     } else {
16748       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16749                 WhiteOnMove(moveNumber) ? " " : ".. ",
16750                 parseList[moveNumber], res);
16751         DisplayMessage(message, cpThinkOutput);
16752     }
16753 }
16754
16755 void
16756 DisplayComment (int moveNumber, char *text)
16757 {
16758     char title[MSG_SIZ];
16759
16760     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16761       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16762     } else {
16763       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16764               WhiteOnMove(moveNumber) ? " " : ".. ",
16765               parseList[moveNumber]);
16766     }
16767     if (text != NULL && (appData.autoDisplayComment || commentUp))
16768         CommentPopUp(title, text);
16769 }
16770
16771 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16772  * might be busy thinking or pondering.  It can be omitted if your
16773  * gnuchess is configured to stop thinking immediately on any user
16774  * input.  However, that gnuchess feature depends on the FIONREAD
16775  * ioctl, which does not work properly on some flavors of Unix.
16776  */
16777 void
16778 Attention (ChessProgramState *cps)
16779 {
16780 #if ATTENTION
16781     if (!cps->useSigint) return;
16782     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16783     switch (gameMode) {
16784       case MachinePlaysWhite:
16785       case MachinePlaysBlack:
16786       case TwoMachinesPlay:
16787       case IcsPlayingWhite:
16788       case IcsPlayingBlack:
16789       case AnalyzeMode:
16790       case AnalyzeFile:
16791         /* Skip if we know it isn't thinking */
16792         if (!cps->maybeThinking) return;
16793         if (appData.debugMode)
16794           fprintf(debugFP, "Interrupting %s\n", cps->which);
16795         InterruptChildProcess(cps->pr);
16796         cps->maybeThinking = FALSE;
16797         break;
16798       default:
16799         break;
16800     }
16801 #endif /*ATTENTION*/
16802 }
16803
16804 int
16805 CheckFlags ()
16806 {
16807     if (whiteTimeRemaining <= 0) {
16808         if (!whiteFlag) {
16809             whiteFlag = TRUE;
16810             if (appData.icsActive) {
16811                 if (appData.autoCallFlag &&
16812                     gameMode == IcsPlayingBlack && !blackFlag) {
16813                   SendToICS(ics_prefix);
16814                   SendToICS("flag\n");
16815                 }
16816             } else {
16817                 if (blackFlag) {
16818                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16819                 } else {
16820                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16821                     if (appData.autoCallFlag) {
16822                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16823                         return TRUE;
16824                     }
16825                 }
16826             }
16827         }
16828     }
16829     if (blackTimeRemaining <= 0) {
16830         if (!blackFlag) {
16831             blackFlag = TRUE;
16832             if (appData.icsActive) {
16833                 if (appData.autoCallFlag &&
16834                     gameMode == IcsPlayingWhite && !whiteFlag) {
16835                   SendToICS(ics_prefix);
16836                   SendToICS("flag\n");
16837                 }
16838             } else {
16839                 if (whiteFlag) {
16840                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16841                 } else {
16842                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16843                     if (appData.autoCallFlag) {
16844                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16845                         return TRUE;
16846                     }
16847                 }
16848             }
16849         }
16850     }
16851     return FALSE;
16852 }
16853
16854 void
16855 CheckTimeControl ()
16856 {
16857     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16858         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16859
16860     /*
16861      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16862      */
16863     if ( !WhiteOnMove(forwardMostMove) ) {
16864         /* White made time control */
16865         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16866         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16867         /* [HGM] time odds: correct new time quota for time odds! */
16868                                             / WhitePlayer()->timeOdds;
16869         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16870     } else {
16871         lastBlack -= blackTimeRemaining;
16872         /* Black made time control */
16873         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16874                                             / WhitePlayer()->other->timeOdds;
16875         lastWhite = whiteTimeRemaining;
16876     }
16877 }
16878
16879 void
16880 DisplayBothClocks ()
16881 {
16882     int wom = gameMode == EditPosition ?
16883       !blackPlaysFirst : WhiteOnMove(currentMove);
16884     DisplayWhiteClock(whiteTimeRemaining, wom);
16885     DisplayBlackClock(blackTimeRemaining, !wom);
16886 }
16887
16888
16889 /* Timekeeping seems to be a portability nightmare.  I think everyone
16890    has ftime(), but I'm really not sure, so I'm including some ifdefs
16891    to use other calls if you don't.  Clocks will be less accurate if
16892    you have neither ftime nor gettimeofday.
16893 */
16894
16895 /* VS 2008 requires the #include outside of the function */
16896 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16897 #include <sys/timeb.h>
16898 #endif
16899
16900 /* Get the current time as a TimeMark */
16901 void
16902 GetTimeMark (TimeMark *tm)
16903 {
16904 #if HAVE_GETTIMEOFDAY
16905
16906     struct timeval timeVal;
16907     struct timezone timeZone;
16908
16909     gettimeofday(&timeVal, &timeZone);
16910     tm->sec = (long) timeVal.tv_sec;
16911     tm->ms = (int) (timeVal.tv_usec / 1000L);
16912
16913 #else /*!HAVE_GETTIMEOFDAY*/
16914 #if HAVE_FTIME
16915
16916 // include <sys/timeb.h> / moved to just above start of function
16917     struct timeb timeB;
16918
16919     ftime(&timeB);
16920     tm->sec = (long) timeB.time;
16921     tm->ms = (int) timeB.millitm;
16922
16923 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16924     tm->sec = (long) time(NULL);
16925     tm->ms = 0;
16926 #endif
16927 #endif
16928 }
16929
16930 /* Return the difference in milliseconds between two
16931    time marks.  We assume the difference will fit in a long!
16932 */
16933 long
16934 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16935 {
16936     return 1000L*(tm2->sec - tm1->sec) +
16937            (long) (tm2->ms - tm1->ms);
16938 }
16939
16940
16941 /*
16942  * Code to manage the game clocks.
16943  *
16944  * In tournament play, black starts the clock and then white makes a move.
16945  * We give the human user a slight advantage if he is playing white---the
16946  * clocks don't run until he makes his first move, so it takes zero time.
16947  * Also, we don't account for network lag, so we could get out of sync
16948  * with GNU Chess's clock -- but then, referees are always right.
16949  */
16950
16951 static TimeMark tickStartTM;
16952 static long intendedTickLength;
16953
16954 long
16955 NextTickLength (long timeRemaining)
16956 {
16957     long nominalTickLength, nextTickLength;
16958
16959     if (timeRemaining > 0L && timeRemaining <= 10000L)
16960       nominalTickLength = 100L;
16961     else
16962       nominalTickLength = 1000L;
16963     nextTickLength = timeRemaining % nominalTickLength;
16964     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16965
16966     return nextTickLength;
16967 }
16968
16969 /* Adjust clock one minute up or down */
16970 void
16971 AdjustClock (Boolean which, int dir)
16972 {
16973     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16974     if(which) blackTimeRemaining += 60000*dir;
16975     else      whiteTimeRemaining += 60000*dir;
16976     DisplayBothClocks();
16977     adjustedClock = TRUE;
16978 }
16979
16980 /* Stop clocks and reset to a fresh time control */
16981 void
16982 ResetClocks ()
16983 {
16984     (void) StopClockTimer();
16985     if (appData.icsActive) {
16986         whiteTimeRemaining = blackTimeRemaining = 0;
16987     } else if (searchTime) {
16988         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16989         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16990     } else { /* [HGM] correct new time quote for time odds */
16991         whiteTC = blackTC = fullTimeControlString;
16992         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16993         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16994     }
16995     if (whiteFlag || blackFlag) {
16996         DisplayTitle("");
16997         whiteFlag = blackFlag = FALSE;
16998     }
16999     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17000     DisplayBothClocks();
17001     adjustedClock = FALSE;
17002 }
17003
17004 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17005
17006 /* Decrement running clock by amount of time that has passed */
17007 void
17008 DecrementClocks ()
17009 {
17010     long timeRemaining;
17011     long lastTickLength, fudge;
17012     TimeMark now;
17013
17014     if (!appData.clockMode) return;
17015     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17016
17017     GetTimeMark(&now);
17018
17019     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17020
17021     /* Fudge if we woke up a little too soon */
17022     fudge = intendedTickLength - lastTickLength;
17023     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17024
17025     if (WhiteOnMove(forwardMostMove)) {
17026         if(whiteNPS >= 0) lastTickLength = 0;
17027         timeRemaining = whiteTimeRemaining -= lastTickLength;
17028         if(timeRemaining < 0 && !appData.icsActive) {
17029             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17030             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17031                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17032                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17033             }
17034         }
17035         DisplayWhiteClock(whiteTimeRemaining - fudge,
17036                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17037     } else {
17038         if(blackNPS >= 0) lastTickLength = 0;
17039         timeRemaining = blackTimeRemaining -= lastTickLength;
17040         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17041             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17042             if(suddenDeath) {
17043                 blackStartMove = forwardMostMove;
17044                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17045             }
17046         }
17047         DisplayBlackClock(blackTimeRemaining - fudge,
17048                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17049     }
17050     if (CheckFlags()) return;
17051
17052     if(twoBoards) { // count down secondary board's clocks as well
17053         activePartnerTime -= lastTickLength;
17054         partnerUp = 1;
17055         if(activePartner == 'W')
17056             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17057         else
17058             DisplayBlackClock(activePartnerTime, TRUE);
17059         partnerUp = 0;
17060     }
17061
17062     tickStartTM = now;
17063     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17064     StartClockTimer(intendedTickLength);
17065
17066     /* if the time remaining has fallen below the alarm threshold, sound the
17067      * alarm. if the alarm has sounded and (due to a takeback or time control
17068      * with increment) the time remaining has increased to a level above the
17069      * threshold, reset the alarm so it can sound again.
17070      */
17071
17072     if (appData.icsActive && appData.icsAlarm) {
17073
17074         /* make sure we are dealing with the user's clock */
17075         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17076                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17077            )) return;
17078
17079         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17080             alarmSounded = FALSE;
17081         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17082             PlayAlarmSound();
17083             alarmSounded = TRUE;
17084         }
17085     }
17086 }
17087
17088
17089 /* A player has just moved, so stop the previously running
17090    clock and (if in clock mode) start the other one.
17091    We redisplay both clocks in case we're in ICS mode, because
17092    ICS gives us an update to both clocks after every move.
17093    Note that this routine is called *after* forwardMostMove
17094    is updated, so the last fractional tick must be subtracted
17095    from the color that is *not* on move now.
17096 */
17097 void
17098 SwitchClocks (int newMoveNr)
17099 {
17100     long lastTickLength;
17101     TimeMark now;
17102     int flagged = FALSE;
17103
17104     GetTimeMark(&now);
17105
17106     if (StopClockTimer() && appData.clockMode) {
17107         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17108         if (!WhiteOnMove(forwardMostMove)) {
17109             if(blackNPS >= 0) lastTickLength = 0;
17110             blackTimeRemaining -= lastTickLength;
17111            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17112 //         if(pvInfoList[forwardMostMove].time == -1)
17113                  pvInfoList[forwardMostMove].time =               // use GUI time
17114                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17115         } else {
17116            if(whiteNPS >= 0) lastTickLength = 0;
17117            whiteTimeRemaining -= lastTickLength;
17118            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17119 //         if(pvInfoList[forwardMostMove].time == -1)
17120                  pvInfoList[forwardMostMove].time =
17121                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17122         }
17123         flagged = CheckFlags();
17124     }
17125     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17126     CheckTimeControl();
17127
17128     if (flagged || !appData.clockMode) return;
17129
17130     switch (gameMode) {
17131       case MachinePlaysBlack:
17132       case MachinePlaysWhite:
17133       case BeginningOfGame:
17134         if (pausing) return;
17135         break;
17136
17137       case EditGame:
17138       case PlayFromGameFile:
17139       case IcsExamining:
17140         return;
17141
17142       default:
17143         break;
17144     }
17145
17146     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17147         if(WhiteOnMove(forwardMostMove))
17148              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17149         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17150     }
17151
17152     tickStartTM = now;
17153     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17154       whiteTimeRemaining : blackTimeRemaining);
17155     StartClockTimer(intendedTickLength);
17156 }
17157
17158
17159 /* Stop both clocks */
17160 void
17161 StopClocks ()
17162 {
17163     long lastTickLength;
17164     TimeMark now;
17165
17166     if (!StopClockTimer()) return;
17167     if (!appData.clockMode) return;
17168
17169     GetTimeMark(&now);
17170
17171     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17172     if (WhiteOnMove(forwardMostMove)) {
17173         if(whiteNPS >= 0) lastTickLength = 0;
17174         whiteTimeRemaining -= lastTickLength;
17175         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17176     } else {
17177         if(blackNPS >= 0) lastTickLength = 0;
17178         blackTimeRemaining -= lastTickLength;
17179         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17180     }
17181     CheckFlags();
17182 }
17183
17184 /* Start clock of player on move.  Time may have been reset, so
17185    if clock is already running, stop and restart it. */
17186 void
17187 StartClocks ()
17188 {
17189     (void) StopClockTimer(); /* in case it was running already */
17190     DisplayBothClocks();
17191     if (CheckFlags()) return;
17192
17193     if (!appData.clockMode) return;
17194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17195
17196     GetTimeMark(&tickStartTM);
17197     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17198       whiteTimeRemaining : blackTimeRemaining);
17199
17200    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17201     whiteNPS = blackNPS = -1;
17202     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17203        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17204         whiteNPS = first.nps;
17205     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17206        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17207         blackNPS = first.nps;
17208     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17209         whiteNPS = second.nps;
17210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17211         blackNPS = second.nps;
17212     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17213
17214     StartClockTimer(intendedTickLength);
17215 }
17216
17217 char *
17218 TimeString (long ms)
17219 {
17220     long second, minute, hour, day;
17221     char *sign = "";
17222     static char buf[32];
17223
17224     if (ms > 0 && ms <= 9900) {
17225       /* convert milliseconds to tenths, rounding up */
17226       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17227
17228       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17229       return buf;
17230     }
17231
17232     /* convert milliseconds to seconds, rounding up */
17233     /* use floating point to avoid strangeness of integer division
17234        with negative dividends on many machines */
17235     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17236
17237     if (second < 0) {
17238         sign = "-";
17239         second = -second;
17240     }
17241
17242     day = second / (60 * 60 * 24);
17243     second = second % (60 * 60 * 24);
17244     hour = second / (60 * 60);
17245     second = second % (60 * 60);
17246     minute = second / 60;
17247     second = second % 60;
17248
17249     if (day > 0)
17250       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17251               sign, day, hour, minute, second);
17252     else if (hour > 0)
17253       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17254     else
17255       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17256
17257     return buf;
17258 }
17259
17260
17261 /*
17262  * This is necessary because some C libraries aren't ANSI C compliant yet.
17263  */
17264 char *
17265 StrStr (char *string, char *match)
17266 {
17267     int i, length;
17268
17269     length = strlen(match);
17270
17271     for (i = strlen(string) - length; i >= 0; i--, string++)
17272       if (!strncmp(match, string, length))
17273         return string;
17274
17275     return NULL;
17276 }
17277
17278 char *
17279 StrCaseStr (char *string, char *match)
17280 {
17281     int i, j, length;
17282
17283     length = strlen(match);
17284
17285     for (i = strlen(string) - length; i >= 0; i--, string++) {
17286         for (j = 0; j < length; j++) {
17287             if (ToLower(match[j]) != ToLower(string[j]))
17288               break;
17289         }
17290         if (j == length) return string;
17291     }
17292
17293     return NULL;
17294 }
17295
17296 #ifndef _amigados
17297 int
17298 StrCaseCmp (char *s1, char *s2)
17299 {
17300     char c1, c2;
17301
17302     for (;;) {
17303         c1 = ToLower(*s1++);
17304         c2 = ToLower(*s2++);
17305         if (c1 > c2) return 1;
17306         if (c1 < c2) return -1;
17307         if (c1 == NULLCHAR) return 0;
17308     }
17309 }
17310
17311
17312 int
17313 ToLower (int c)
17314 {
17315     return isupper(c) ? tolower(c) : c;
17316 }
17317
17318
17319 int
17320 ToUpper (int c)
17321 {
17322     return islower(c) ? toupper(c) : c;
17323 }
17324 #endif /* !_amigados    */
17325
17326 char *
17327 StrSave (char *s)
17328 {
17329   char *ret;
17330
17331   if ((ret = (char *) malloc(strlen(s) + 1)))
17332     {
17333       safeStrCpy(ret, s, strlen(s)+1);
17334     }
17335   return ret;
17336 }
17337
17338 char *
17339 StrSavePtr (char *s, char **savePtr)
17340 {
17341     if (*savePtr) {
17342         free(*savePtr);
17343     }
17344     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17345       safeStrCpy(*savePtr, s, strlen(s)+1);
17346     }
17347     return(*savePtr);
17348 }
17349
17350 char *
17351 PGNDate ()
17352 {
17353     time_t clock;
17354     struct tm *tm;
17355     char buf[MSG_SIZ];
17356
17357     clock = time((time_t *)NULL);
17358     tm = localtime(&clock);
17359     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17360             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17361     return StrSave(buf);
17362 }
17363
17364
17365 char *
17366 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17367 {
17368     int i, j, fromX, fromY, toX, toY;
17369     int whiteToPlay;
17370     char buf[MSG_SIZ];
17371     char *p, *q;
17372     int emptycount;
17373     ChessSquare piece;
17374
17375     whiteToPlay = (gameMode == EditPosition) ?
17376       !blackPlaysFirst : (move % 2 == 0);
17377     p = buf;
17378
17379     /* Piece placement data */
17380     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17381         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17382         emptycount = 0;
17383         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17384             if (boards[move][i][j] == EmptySquare) {
17385                 emptycount++;
17386             } else { ChessSquare piece = boards[move][i][j];
17387                 if (emptycount > 0) {
17388                     if(emptycount<10) /* [HGM] can be >= 10 */
17389                         *p++ = '0' + emptycount;
17390                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17391                     emptycount = 0;
17392                 }
17393                 if(PieceToChar(piece) == '+') {
17394                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17395                     *p++ = '+';
17396                     piece = (ChessSquare)(DEMOTED piece);
17397                 }
17398                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17399                 if(p[-1] == '~') {
17400                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17401                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17402                     *p++ = '~';
17403                 }
17404             }
17405         }
17406         if (emptycount > 0) {
17407             if(emptycount<10) /* [HGM] can be >= 10 */
17408                 *p++ = '0' + emptycount;
17409             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17410             emptycount = 0;
17411         }
17412         *p++ = '/';
17413     }
17414     *(p - 1) = ' ';
17415
17416     /* [HGM] print Crazyhouse or Shogi holdings */
17417     if( gameInfo.holdingsWidth ) {
17418         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17419         q = p;
17420         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17421             piece = boards[move][i][BOARD_WIDTH-1];
17422             if( piece != EmptySquare )
17423               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17424                   *p++ = PieceToChar(piece);
17425         }
17426         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17427             piece = boards[move][BOARD_HEIGHT-i-1][0];
17428             if( piece != EmptySquare )
17429               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17430                   *p++ = PieceToChar(piece);
17431         }
17432
17433         if( q == p ) *p++ = '-';
17434         *p++ = ']';
17435         *p++ = ' ';
17436     }
17437
17438     /* Active color */
17439     *p++ = whiteToPlay ? 'w' : 'b';
17440     *p++ = ' ';
17441
17442   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17443     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17444   } else {
17445   if(nrCastlingRights) {
17446      q = p;
17447      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17448        /* [HGM] write directly from rights */
17449            if(boards[move][CASTLING][2] != NoRights &&
17450               boards[move][CASTLING][0] != NoRights   )
17451                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17452            if(boards[move][CASTLING][2] != NoRights &&
17453               boards[move][CASTLING][1] != NoRights   )
17454                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17455            if(boards[move][CASTLING][5] != NoRights &&
17456               boards[move][CASTLING][3] != NoRights   )
17457                 *p++ = boards[move][CASTLING][3] + AAA;
17458            if(boards[move][CASTLING][5] != NoRights &&
17459               boards[move][CASTLING][4] != NoRights   )
17460                 *p++ = boards[move][CASTLING][4] + AAA;
17461      } else {
17462
17463         /* [HGM] write true castling rights */
17464         if( nrCastlingRights == 6 ) {
17465             int q, k=0;
17466             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17467                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17468             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17469                  boards[move][CASTLING][2] != NoRights  );
17470             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17471                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17472                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17473                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17474                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17475             }
17476             if(q) *p++ = 'Q';
17477             k = 0;
17478             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17479                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17480             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17481                  boards[move][CASTLING][5] != NoRights  );
17482             if(gameInfo.variant == VariantSChess) {
17483                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17484                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17485                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17486                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17487             }
17488             if(q) *p++ = 'q';
17489         }
17490      }
17491      if (q == p) *p++ = '-'; /* No castling rights */
17492      *p++ = ' ';
17493   }
17494
17495   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17496      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17497      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17498     /* En passant target square */
17499     if (move > backwardMostMove) {
17500         fromX = moveList[move - 1][0] - AAA;
17501         fromY = moveList[move - 1][1] - ONE;
17502         toX = moveList[move - 1][2] - AAA;
17503         toY = moveList[move - 1][3] - ONE;
17504         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17505             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17506             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17507             fromX == toX) {
17508             /* 2-square pawn move just happened */
17509             *p++ = toX + AAA;
17510             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17511         } else {
17512             *p++ = '-';
17513         }
17514     } else if(move == backwardMostMove) {
17515         // [HGM] perhaps we should always do it like this, and forget the above?
17516         if((signed char)boards[move][EP_STATUS] >= 0) {
17517             *p++ = boards[move][EP_STATUS] + AAA;
17518             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17519         } else {
17520             *p++ = '-';
17521         }
17522     } else {
17523         *p++ = '-';
17524     }
17525     *p++ = ' ';
17526   }
17527   }
17528
17529     if(moveCounts)
17530     {   int i = 0, j=move;
17531
17532         /* [HGM] find reversible plies */
17533         if (appData.debugMode) { int k;
17534             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17535             for(k=backwardMostMove; k<=forwardMostMove; k++)
17536                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17537
17538         }
17539
17540         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17541         if( j == backwardMostMove ) i += initialRulePlies;
17542         sprintf(p, "%d ", i);
17543         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17544
17545         /* Fullmove number */
17546         sprintf(p, "%d", (move / 2) + 1);
17547     } else *--p = NULLCHAR;
17548
17549     return StrSave(buf);
17550 }
17551
17552 Boolean
17553 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17554 {
17555     int i, j, k, w=0;
17556     char *p, c;
17557     int emptycount, virgin[BOARD_FILES];
17558     ChessSquare piece;
17559
17560     p = fen;
17561
17562     /* Piece placement data */
17563     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17564         j = 0;
17565         for (;;) {
17566             if (*p == '/' || *p == ' ' || *p == '[' ) {
17567                 if(j > w) w = j;
17568                 emptycount = gameInfo.boardWidth - j;
17569                 while (emptycount--)
17570                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17571                 if (*p == '/') p++;
17572                 else if(autoSize) { // we stumbled unexpectedly into end of board
17573                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17574                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17575                     }
17576                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17577                 }
17578                 break;
17579 #if(BOARD_FILES >= 10)
17580             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17581                 p++; emptycount=10;
17582                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17583                 while (emptycount--)
17584                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17585 #endif
17586             } else if (*p == '*') {
17587                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17588             } else if (isdigit(*p)) {
17589                 emptycount = *p++ - '0';
17590                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17591                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17592                 while (emptycount--)
17593                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17594             } else if (*p == '+' || isalpha(*p)) {
17595                 if (j >= gameInfo.boardWidth) return FALSE;
17596                 if(*p=='+') {
17597                     piece = CharToPiece(*++p);
17598                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17599                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17600                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17601                 } else piece = CharToPiece(*p++);
17602
17603                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17604                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17605                     piece = (ChessSquare) (PROMOTED piece);
17606                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17607                     p++;
17608                 }
17609                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17610             } else {
17611                 return FALSE;
17612             }
17613         }
17614     }
17615     while (*p == '/' || *p == ' ') p++;
17616
17617     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17618
17619     /* [HGM] by default clear Crazyhouse holdings, if present */
17620     if(gameInfo.holdingsWidth) {
17621        for(i=0; i<BOARD_HEIGHT; i++) {
17622            board[i][0]             = EmptySquare; /* black holdings */
17623            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17624            board[i][1]             = (ChessSquare) 0; /* black counts */
17625            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17626        }
17627     }
17628
17629     /* [HGM] look for Crazyhouse holdings here */
17630     while(*p==' ') p++;
17631     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17632         if(*p == '[') p++;
17633         if(*p == '-' ) p++; /* empty holdings */ else {
17634             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17635             /* if we would allow FEN reading to set board size, we would   */
17636             /* have to add holdings and shift the board read so far here   */
17637             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17638                 p++;
17639                 if((int) piece >= (int) BlackPawn ) {
17640                     i = (int)piece - (int)BlackPawn;
17641                     i = PieceToNumber((ChessSquare)i);
17642                     if( i >= gameInfo.holdingsSize ) return FALSE;
17643                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17644                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17645                 } else {
17646                     i = (int)piece - (int)WhitePawn;
17647                     i = PieceToNumber((ChessSquare)i);
17648                     if( i >= gameInfo.holdingsSize ) return FALSE;
17649                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17650                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17651                 }
17652             }
17653         }
17654         if(*p == ']') p++;
17655     }
17656
17657     while(*p == ' ') p++;
17658
17659     /* Active color */
17660     c = *p++;
17661     if(appData.colorNickNames) {
17662       if( c == appData.colorNickNames[0] ) c = 'w'; else
17663       if( c == appData.colorNickNames[1] ) c = 'b';
17664     }
17665     switch (c) {
17666       case 'w':
17667         *blackPlaysFirst = FALSE;
17668         break;
17669       case 'b':
17670         *blackPlaysFirst = TRUE;
17671         break;
17672       default:
17673         return FALSE;
17674     }
17675
17676     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17677     /* return the extra info in global variiables             */
17678
17679     /* set defaults in case FEN is incomplete */
17680     board[EP_STATUS] = EP_UNKNOWN;
17681     for(i=0; i<nrCastlingRights; i++ ) {
17682         board[CASTLING][i] =
17683             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17684     }   /* assume possible unless obviously impossible */
17685     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17686     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17687     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17688                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17689     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17690     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17691     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17692                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17693     FENrulePlies = 0;
17694
17695     while(*p==' ') p++;
17696     if(nrCastlingRights) {
17697       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17698       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17699           /* castling indicator present, so default becomes no castlings */
17700           for(i=0; i<nrCastlingRights; i++ ) {
17701                  board[CASTLING][i] = NoRights;
17702           }
17703       }
17704       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17705              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17706              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17707              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17708         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17709
17710         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17711             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17712             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17713         }
17714         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17715             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17716         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17717                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17718         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17719                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17720         switch(c) {
17721           case'K':
17722               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17723               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17724               board[CASTLING][2] = whiteKingFile;
17725               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17726               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17727               break;
17728           case'Q':
17729               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17730               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17731               board[CASTLING][2] = whiteKingFile;
17732               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17733               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17734               break;
17735           case'k':
17736               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17737               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17738               board[CASTLING][5] = blackKingFile;
17739               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17740               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17741               break;
17742           case'q':
17743               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17744               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17745               board[CASTLING][5] = blackKingFile;
17746               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17747               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17748           case '-':
17749               break;
17750           default: /* FRC castlings */
17751               if(c >= 'a') { /* black rights */
17752                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17753                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17754                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17755                   if(i == BOARD_RGHT) break;
17756                   board[CASTLING][5] = i;
17757                   c -= AAA;
17758                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17759                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17760                   if(c > i)
17761                       board[CASTLING][3] = c;
17762                   else
17763                       board[CASTLING][4] = c;
17764               } else { /* white rights */
17765                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17766                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17767                     if(board[0][i] == WhiteKing) break;
17768                   if(i == BOARD_RGHT) break;
17769                   board[CASTLING][2] = i;
17770                   c -= AAA - 'a' + 'A';
17771                   if(board[0][c] >= WhiteKing) break;
17772                   if(c > i)
17773                       board[CASTLING][0] = c;
17774                   else
17775                       board[CASTLING][1] = c;
17776               }
17777         }
17778       }
17779       for(i=0; i<nrCastlingRights; i++)
17780         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17781       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17782     if (appData.debugMode) {
17783         fprintf(debugFP, "FEN castling rights:");
17784         for(i=0; i<nrCastlingRights; i++)
17785         fprintf(debugFP, " %d", board[CASTLING][i]);
17786         fprintf(debugFP, "\n");
17787     }
17788
17789       while(*p==' ') p++;
17790     }
17791
17792     /* read e.p. field in games that know e.p. capture */
17793     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17794        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17795        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17796       if(*p=='-') {
17797         p++; board[EP_STATUS] = EP_NONE;
17798       } else {
17799          char c = *p++ - AAA;
17800
17801          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17802          if(*p >= '0' && *p <='9') p++;
17803          board[EP_STATUS] = c;
17804       }
17805     }
17806
17807
17808     if(sscanf(p, "%d", &i) == 1) {
17809         FENrulePlies = i; /* 50-move ply counter */
17810         /* (The move number is still ignored)    */
17811     }
17812
17813     return TRUE;
17814 }
17815
17816 void
17817 EditPositionPasteFEN (char *fen)
17818 {
17819   if (fen != NULL) {
17820     Board initial_position;
17821
17822     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17823       DisplayError(_("Bad FEN position in clipboard"), 0);
17824       return ;
17825     } else {
17826       int savedBlackPlaysFirst = blackPlaysFirst;
17827       EditPositionEvent();
17828       blackPlaysFirst = savedBlackPlaysFirst;
17829       CopyBoard(boards[0], initial_position);
17830       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17831       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17832       DisplayBothClocks();
17833       DrawPosition(FALSE, boards[currentMove]);
17834     }
17835   }
17836 }
17837
17838 static char cseq[12] = "\\   ";
17839
17840 Boolean
17841 set_cont_sequence (char *new_seq)
17842 {
17843     int len;
17844     Boolean ret;
17845
17846     // handle bad attempts to set the sequence
17847         if (!new_seq)
17848                 return 0; // acceptable error - no debug
17849
17850     len = strlen(new_seq);
17851     ret = (len > 0) && (len < sizeof(cseq));
17852     if (ret)
17853       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17854     else if (appData.debugMode)
17855       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17856     return ret;
17857 }
17858
17859 /*
17860     reformat a source message so words don't cross the width boundary.  internal
17861     newlines are not removed.  returns the wrapped size (no null character unless
17862     included in source message).  If dest is NULL, only calculate the size required
17863     for the dest buffer.  lp argument indicats line position upon entry, and it's
17864     passed back upon exit.
17865 */
17866 int
17867 wrap (char *dest, char *src, int count, int width, int *lp)
17868 {
17869     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17870
17871     cseq_len = strlen(cseq);
17872     old_line = line = *lp;
17873     ansi = len = clen = 0;
17874
17875     for (i=0; i < count; i++)
17876     {
17877         if (src[i] == '\033')
17878             ansi = 1;
17879
17880         // if we hit the width, back up
17881         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17882         {
17883             // store i & len in case the word is too long
17884             old_i = i, old_len = len;
17885
17886             // find the end of the last word
17887             while (i && src[i] != ' ' && src[i] != '\n')
17888             {
17889                 i--;
17890                 len--;
17891             }
17892
17893             // word too long?  restore i & len before splitting it
17894             if ((old_i-i+clen) >= width)
17895             {
17896                 i = old_i;
17897                 len = old_len;
17898             }
17899
17900             // extra space?
17901             if (i && src[i-1] == ' ')
17902                 len--;
17903
17904             if (src[i] != ' ' && src[i] != '\n')
17905             {
17906                 i--;
17907                 if (len)
17908                     len--;
17909             }
17910
17911             // now append the newline and continuation sequence
17912             if (dest)
17913                 dest[len] = '\n';
17914             len++;
17915             if (dest)
17916                 strncpy(dest+len, cseq, cseq_len);
17917             len += cseq_len;
17918             line = cseq_len;
17919             clen = cseq_len;
17920             continue;
17921         }
17922
17923         if (dest)
17924             dest[len] = src[i];
17925         len++;
17926         if (!ansi)
17927             line++;
17928         if (src[i] == '\n')
17929             line = 0;
17930         if (src[i] == 'm')
17931             ansi = 0;
17932     }
17933     if (dest && appData.debugMode)
17934     {
17935         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17936             count, width, line, len, *lp);
17937         show_bytes(debugFP, src, count);
17938         fprintf(debugFP, "\ndest: ");
17939         show_bytes(debugFP, dest, len);
17940         fprintf(debugFP, "\n");
17941     }
17942     *lp = dest ? line : old_line;
17943
17944     return len;
17945 }
17946
17947 // [HGM] vari: routines for shelving variations
17948 Boolean modeRestore = FALSE;
17949
17950 void
17951 PushInner (int firstMove, int lastMove)
17952 {
17953         int i, j, nrMoves = lastMove - firstMove;
17954
17955         // push current tail of game on stack
17956         savedResult[storedGames] = gameInfo.result;
17957         savedDetails[storedGames] = gameInfo.resultDetails;
17958         gameInfo.resultDetails = NULL;
17959         savedFirst[storedGames] = firstMove;
17960         savedLast [storedGames] = lastMove;
17961         savedFramePtr[storedGames] = framePtr;
17962         framePtr -= nrMoves; // reserve space for the boards
17963         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17964             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17965             for(j=0; j<MOVE_LEN; j++)
17966                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17967             for(j=0; j<2*MOVE_LEN; j++)
17968                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17969             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17970             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17971             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17972             pvInfoList[firstMove+i-1].depth = 0;
17973             commentList[framePtr+i] = commentList[firstMove+i];
17974             commentList[firstMove+i] = NULL;
17975         }
17976
17977         storedGames++;
17978         forwardMostMove = firstMove; // truncate game so we can start variation
17979 }
17980
17981 void
17982 PushTail (int firstMove, int lastMove)
17983 {
17984         if(appData.icsActive) { // only in local mode
17985                 forwardMostMove = currentMove; // mimic old ICS behavior
17986                 return;
17987         }
17988         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17989
17990         PushInner(firstMove, lastMove);
17991         if(storedGames == 1) GreyRevert(FALSE);
17992         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17993 }
17994
17995 void
17996 PopInner (Boolean annotate)
17997 {
17998         int i, j, nrMoves;
17999         char buf[8000], moveBuf[20];
18000
18001         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18002         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18003         nrMoves = savedLast[storedGames] - currentMove;
18004         if(annotate) {
18005                 int cnt = 10;
18006                 if(!WhiteOnMove(currentMove))
18007                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18008                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18009                 for(i=currentMove; i<forwardMostMove; i++) {
18010                         if(WhiteOnMove(i))
18011                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18012                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18013                         strcat(buf, moveBuf);
18014                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18015                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18016                 }
18017                 strcat(buf, ")");
18018         }
18019         for(i=1; i<=nrMoves; i++) { // copy last variation back
18020             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18021             for(j=0; j<MOVE_LEN; j++)
18022                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18023             for(j=0; j<2*MOVE_LEN; j++)
18024                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18025             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18026             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18027             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18028             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18029             commentList[currentMove+i] = commentList[framePtr+i];
18030             commentList[framePtr+i] = NULL;
18031         }
18032         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18033         framePtr = savedFramePtr[storedGames];
18034         gameInfo.result = savedResult[storedGames];
18035         if(gameInfo.resultDetails != NULL) {
18036             free(gameInfo.resultDetails);
18037       }
18038         gameInfo.resultDetails = savedDetails[storedGames];
18039         forwardMostMove = currentMove + nrMoves;
18040 }
18041
18042 Boolean
18043 PopTail (Boolean annotate)
18044 {
18045         if(appData.icsActive) return FALSE; // only in local mode
18046         if(!storedGames) return FALSE; // sanity
18047         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18048
18049         PopInner(annotate);
18050         if(currentMove < forwardMostMove) ForwardEvent(); else
18051         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18052
18053         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18054         return TRUE;
18055 }
18056
18057 void
18058 CleanupTail ()
18059 {       // remove all shelved variations
18060         int i;
18061         for(i=0; i<storedGames; i++) {
18062             if(savedDetails[i])
18063                 free(savedDetails[i]);
18064             savedDetails[i] = NULL;
18065         }
18066         for(i=framePtr; i<MAX_MOVES; i++) {
18067                 if(commentList[i]) free(commentList[i]);
18068                 commentList[i] = NULL;
18069         }
18070         framePtr = MAX_MOVES-1;
18071         storedGames = 0;
18072 }
18073
18074 void
18075 LoadVariation (int index, char *text)
18076 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18077         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18078         int level = 0, move;
18079
18080         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18081         // first find outermost bracketing variation
18082         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18083             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18084                 if(*p == '{') wait = '}'; else
18085                 if(*p == '[') wait = ']'; else
18086                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18087                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18088             }
18089             if(*p == wait) wait = NULLCHAR; // closing ]} found
18090             p++;
18091         }
18092         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18093         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18094         end[1] = NULLCHAR; // clip off comment beyond variation
18095         ToNrEvent(currentMove-1);
18096         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18097         // kludge: use ParsePV() to append variation to game
18098         move = currentMove;
18099         ParsePV(start, TRUE, TRUE);
18100         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18101         ClearPremoveHighlights();
18102         CommentPopDown();
18103         ToNrEvent(currentMove+1);
18104 }
18105
18106 void
18107 LoadTheme ()
18108 {
18109     char *p, *q, buf[MSG_SIZ];
18110     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18111         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18112         ParseArgsFromString(buf);
18113         ActivateTheme(TRUE); // also redo colors
18114         return;
18115     }
18116     p = nickName;
18117     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18118     {
18119         int len;
18120         q = appData.themeNames;
18121         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18122       if(appData.useBitmaps) {
18123         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18124                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18125                 appData.liteBackTextureMode,
18126                 appData.darkBackTextureMode );
18127       } else {
18128         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18129                 Col2Text(2),   // lightSquareColor
18130                 Col2Text(3) ); // darkSquareColor
18131       }
18132       if(appData.useBorder) {
18133         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18134                 appData.border);
18135       } else {
18136         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18137       }
18138       if(appData.useFont) {
18139         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18140                 appData.renderPiecesWithFont,
18141                 appData.fontToPieceTable,
18142                 Col2Text(9),    // appData.fontBackColorWhite
18143                 Col2Text(10) ); // appData.fontForeColorBlack
18144       } else {
18145         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18146                 appData.pieceDirectory);
18147         if(!appData.pieceDirectory[0])
18148           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18149                 Col2Text(0),   // whitePieceColor
18150                 Col2Text(1) ); // blackPieceColor
18151       }
18152       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18153                 Col2Text(4),   // highlightSquareColor
18154                 Col2Text(5) ); // premoveHighlightColor
18155         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18156         if(insert != q) insert[-1] = NULLCHAR;
18157         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18158         if(q)   free(q);
18159     }
18160     ActivateTheme(FALSE);
18161 }