bb79d8d507766720925c2acb20b8b6d8f4577d26
[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(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5062           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5063                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5064                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5065                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5066           SendToProgram(buf, cps);
5067       } else
5068       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5069         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5070           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5071           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5072                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5073         } else
5074           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5075                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5076         SendToProgram(buf, cps);
5077       }
5078       else SendToProgram(moveList[moveNum], cps);
5079       /* End of additions by Tord */
5080     }
5081
5082     /* [HGM] setting up the opening has brought engine in force mode! */
5083     /*       Send 'go' if we are in a mode where machine should play. */
5084     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5085         (gameMode == TwoMachinesPlay   ||
5086 #if ZIPPY
5087          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5088 #endif
5089          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5090         SendToProgram("go\n", cps);
5091   if (appData.debugMode) {
5092     fprintf(debugFP, "(extra)\n");
5093   }
5094     }
5095     setboardSpoiledMachineBlack = 0;
5096 }
5097
5098 void
5099 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5100 {
5101     char user_move[MSG_SIZ];
5102     char suffix[4];
5103
5104     if(gameInfo.variant == VariantSChess && promoChar) {
5105         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5106         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5107     } else suffix[0] = NULLCHAR;
5108
5109     switch (moveType) {
5110       default:
5111         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5112                 (int)moveType, fromX, fromY, toX, toY);
5113         DisplayError(user_move + strlen("say "), 0);
5114         break;
5115       case WhiteKingSideCastle:
5116       case BlackKingSideCastle:
5117       case WhiteQueenSideCastleWild:
5118       case BlackQueenSideCastleWild:
5119       /* PUSH Fabien */
5120       case WhiteHSideCastleFR:
5121       case BlackHSideCastleFR:
5122       /* POP Fabien */
5123         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5124         break;
5125       case WhiteQueenSideCastle:
5126       case BlackQueenSideCastle:
5127       case WhiteKingSideCastleWild:
5128       case BlackKingSideCastleWild:
5129       /* PUSH Fabien */
5130       case WhiteASideCastleFR:
5131       case BlackASideCastleFR:
5132       /* POP Fabien */
5133         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5134         break;
5135       case WhiteNonPromotion:
5136       case BlackNonPromotion:
5137         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5138         break;
5139       case WhitePromotion:
5140       case BlackPromotion:
5141         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5142            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5143           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5144                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5145                 PieceToChar(WhiteFerz));
5146         else if(gameInfo.variant == VariantGreat)
5147           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5148                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5149                 PieceToChar(WhiteMan));
5150         else
5151           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5152                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5153                 promoChar);
5154         break;
5155       case WhiteDrop:
5156       case BlackDrop:
5157       drop:
5158         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5159                  ToUpper(PieceToChar((ChessSquare) fromX)),
5160                  AAA + toX, ONE + toY);
5161         break;
5162       case IllegalMove:  /* could be a variant we don't quite understand */
5163         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5164       case NormalMove:
5165       case WhiteCapturesEnPassant:
5166       case BlackCapturesEnPassant:
5167         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5168                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5169         break;
5170     }
5171     SendToICS(user_move);
5172     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5173         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5174 }
5175
5176 void
5177 UploadGameEvent ()
5178 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5179     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5180     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5181     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5182       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5183       return;
5184     }
5185     if(gameMode != IcsExamining) { // is this ever not the case?
5186         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5187
5188         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5189           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5190         } else { // on FICS we must first go to general examine mode
5191           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5192         }
5193         if(gameInfo.variant != VariantNormal) {
5194             // try figure out wild number, as xboard names are not always valid on ICS
5195             for(i=1; i<=36; i++) {
5196               snprintf(buf, MSG_SIZ, "wild/%d", i);
5197                 if(StringToVariant(buf) == gameInfo.variant) break;
5198             }
5199             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5200             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5201             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5202         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5203         SendToICS(ics_prefix);
5204         SendToICS(buf);
5205         if(startedFromSetupPosition || backwardMostMove != 0) {
5206           fen = PositionToFEN(backwardMostMove, NULL, 1);
5207           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5208             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5209             SendToICS(buf);
5210           } else { // FICS: everything has to set by separate bsetup commands
5211             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5212             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5213             SendToICS(buf);
5214             if(!WhiteOnMove(backwardMostMove)) {
5215                 SendToICS("bsetup tomove black\n");
5216             }
5217             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5218             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5219             SendToICS(buf);
5220             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5221             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5222             SendToICS(buf);
5223             i = boards[backwardMostMove][EP_STATUS];
5224             if(i >= 0) { // set e.p.
5225               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5226                 SendToICS(buf);
5227             }
5228             bsetup++;
5229           }
5230         }
5231       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5232             SendToICS("bsetup done\n"); // switch to normal examining.
5233     }
5234     for(i = backwardMostMove; i<last; i++) {
5235         char buf[20];
5236         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5237         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5238             int len = strlen(moveList[i]);
5239             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5240             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5241         }
5242         SendToICS(buf);
5243     }
5244     SendToICS(ics_prefix);
5245     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5246 }
5247
5248 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5249
5250 void
5251 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5252 {
5253     if (rf == DROP_RANK) {
5254       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5255       sprintf(move, "%c@%c%c\n",
5256                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5257     } else {
5258         if (promoChar == 'x' || promoChar == NULLCHAR) {
5259           sprintf(move, "%c%c%c%c\n",
5260                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5261           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5262         } else {
5263             sprintf(move, "%c%c%c%c%c\n",
5264                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5265         }
5266     }
5267 }
5268
5269 void
5270 ProcessICSInitScript (FILE *f)
5271 {
5272     char buf[MSG_SIZ];
5273
5274     while (fgets(buf, MSG_SIZ, f)) {
5275         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5276     }
5277
5278     fclose(f);
5279 }
5280
5281
5282 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5283 static ClickType lastClickType;
5284
5285 void
5286 Sweep (int step)
5287 {
5288     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5289     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5290     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5291     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5292     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5293     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5294     do {
5295         promoSweep -= step;
5296         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5297         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5298         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5299         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5300         if(!step) step = -1;
5301     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5302             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5303             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5304     if(toX >= 0) {
5305         int victim = boards[currentMove][toY][toX];
5306         boards[currentMove][toY][toX] = promoSweep;
5307         DrawPosition(FALSE, boards[currentMove]);
5308         boards[currentMove][toY][toX] = victim;
5309     } else
5310     ChangeDragPiece(promoSweep);
5311 }
5312
5313 int
5314 PromoScroll (int x, int y)
5315 {
5316   int step = 0;
5317
5318   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5319   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5320   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5321   if(!step) return FALSE;
5322   lastX = x; lastY = y;
5323   if((promoSweep < BlackPawn) == flipView) step = -step;
5324   if(step > 0) selectFlag = 1;
5325   if(!selectFlag) Sweep(step);
5326   return FALSE;
5327 }
5328
5329 void
5330 NextPiece (int step)
5331 {
5332     ChessSquare piece = boards[currentMove][toY][toX];
5333     do {
5334         pieceSweep -= step;
5335         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5336         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5337         if(!step) step = -1;
5338     } while(PieceToChar(pieceSweep) == '.');
5339     boards[currentMove][toY][toX] = pieceSweep;
5340     DrawPosition(FALSE, boards[currentMove]);
5341     boards[currentMove][toY][toX] = piece;
5342 }
5343 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5344 void
5345 AlphaRank (char *move, int n)
5346 {
5347 //    char *p = move, c; int x, y;
5348
5349     if (appData.debugMode) {
5350         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5351     }
5352
5353     if(move[1]=='*' &&
5354        move[2]>='0' && move[2]<='9' &&
5355        move[3]>='a' && move[3]<='x'    ) {
5356         move[1] = '@';
5357         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5358         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5359     } else
5360     if(move[0]>='0' && move[0]<='9' &&
5361        move[1]>='a' && move[1]<='x' &&
5362        move[2]>='0' && move[2]<='9' &&
5363        move[3]>='a' && move[3]<='x'    ) {
5364         /* input move, Shogi -> normal */
5365         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5366         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5367         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5368         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5369     } else
5370     if(move[1]=='@' &&
5371        move[3]>='0' && move[3]<='9' &&
5372        move[2]>='a' && move[2]<='x'    ) {
5373         move[1] = '*';
5374         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5375         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5376     } else
5377     if(
5378        move[0]>='a' && move[0]<='x' &&
5379        move[3]>='0' && move[3]<='9' &&
5380        move[2]>='a' && move[2]<='x'    ) {
5381          /* output move, normal -> Shogi */
5382         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5383         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5384         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5385         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5386         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5387     }
5388     if (appData.debugMode) {
5389         fprintf(debugFP, "   out = '%s'\n", move);
5390     }
5391 }
5392
5393 char yy_textstr[8000];
5394
5395 /* Parser for moves from gnuchess, ICS, or user typein box */
5396 Boolean
5397 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5398 {
5399     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5400
5401     switch (*moveType) {
5402       case WhitePromotion:
5403       case BlackPromotion:
5404       case WhiteNonPromotion:
5405       case BlackNonPromotion:
5406       case NormalMove:
5407       case FirstLeg:
5408       case WhiteCapturesEnPassant:
5409       case BlackCapturesEnPassant:
5410       case WhiteKingSideCastle:
5411       case WhiteQueenSideCastle:
5412       case BlackKingSideCastle:
5413       case BlackQueenSideCastle:
5414       case WhiteKingSideCastleWild:
5415       case WhiteQueenSideCastleWild:
5416       case BlackKingSideCastleWild:
5417       case BlackQueenSideCastleWild:
5418       /* Code added by Tord: */
5419       case WhiteHSideCastleFR:
5420       case WhiteASideCastleFR:
5421       case BlackHSideCastleFR:
5422       case BlackASideCastleFR:
5423       /* End of code added by Tord */
5424       case IllegalMove:         /* bug or odd chess variant */
5425         *fromX = currentMoveString[0] - AAA;
5426         *fromY = currentMoveString[1] - ONE;
5427         *toX = currentMoveString[2] - AAA;
5428         *toY = currentMoveString[3] - ONE;
5429         *promoChar = currentMoveString[4];
5430         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5431             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5432     if (appData.debugMode) {
5433         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5434     }
5435             *fromX = *fromY = *toX = *toY = 0;
5436             return FALSE;
5437         }
5438         if (appData.testLegality) {
5439           return (*moveType != IllegalMove);
5440         } else {
5441           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5442                         killX < 0 && // [HGM] lion: if this is a double move we are less critical
5443                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5444         }
5445
5446       case WhiteDrop:
5447       case BlackDrop:
5448         *fromX = *moveType == WhiteDrop ?
5449           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5450           (int) CharToPiece(ToLower(currentMoveString[0]));
5451         *fromY = DROP_RANK;
5452         *toX = currentMoveString[2] - AAA;
5453         *toY = currentMoveString[3] - ONE;
5454         *promoChar = NULLCHAR;
5455         return TRUE;
5456
5457       case AmbiguousMove:
5458       case ImpossibleMove:
5459       case EndOfFile:
5460       case ElapsedTime:
5461       case Comment:
5462       case PGNTag:
5463       case NAG:
5464       case WhiteWins:
5465       case BlackWins:
5466       case GameIsDrawn:
5467       default:
5468     if (appData.debugMode) {
5469         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5470     }
5471         /* bug? */
5472         *fromX = *fromY = *toX = *toY = 0;
5473         *promoChar = NULLCHAR;
5474         return FALSE;
5475     }
5476 }
5477
5478 Boolean pushed = FALSE;
5479 char *lastParseAttempt;
5480
5481 void
5482 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5483 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5484   int fromX, fromY, toX, toY; char promoChar;
5485   ChessMove moveType;
5486   Boolean valid;
5487   int nr = 0;
5488
5489   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5490   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5491     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5492     pushed = TRUE;
5493   }
5494   endPV = forwardMostMove;
5495   do {
5496     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5497     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5498     lastParseAttempt = pv;
5499     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5500     if(!valid && nr == 0 &&
5501        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5502         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5503         // Hande case where played move is different from leading PV move
5504         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5505         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5506         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5507         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5508           endPV += 2; // if position different, keep this
5509           moveList[endPV-1][0] = fromX + AAA;
5510           moveList[endPV-1][1] = fromY + ONE;
5511           moveList[endPV-1][2] = toX + AAA;
5512           moveList[endPV-1][3] = toY + ONE;
5513           parseList[endPV-1][0] = NULLCHAR;
5514           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5515         }
5516       }
5517     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5518     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5519     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5520     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5521         valid++; // allow comments in PV
5522         continue;
5523     }
5524     nr++;
5525     if(endPV+1 > framePtr) break; // no space, truncate
5526     if(!valid) break;
5527     endPV++;
5528     CopyBoard(boards[endPV], boards[endPV-1]);
5529     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5530     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5531     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5532     CoordsToAlgebraic(boards[endPV - 1],
5533                              PosFlags(endPV - 1),
5534                              fromY, fromX, toY, toX, promoChar,
5535                              parseList[endPV - 1]);
5536   } while(valid);
5537   if(atEnd == 2) return; // used hidden, for PV conversion
5538   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5539   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5540   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5541                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5542   DrawPosition(TRUE, boards[currentMove]);
5543 }
5544
5545 int
5546 MultiPV (ChessProgramState *cps)
5547 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5548         int i;
5549         for(i=0; i<cps->nrOptions; i++)
5550             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5551                 return i;
5552         return -1;
5553 }
5554
5555 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5556
5557 Boolean
5558 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5559 {
5560         int startPV, multi, lineStart, origIndex = index;
5561         char *p, buf2[MSG_SIZ];
5562         ChessProgramState *cps = (pane ? &second : &first);
5563
5564         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5565         lastX = x; lastY = y;
5566         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5567         lineStart = startPV = index;
5568         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5569         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5570         index = startPV;
5571         do{ while(buf[index] && buf[index] != '\n') index++;
5572         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5573         buf[index] = 0;
5574         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5575                 int n = cps->option[multi].value;
5576                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5577                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5578                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5579                 cps->option[multi].value = n;
5580                 *start = *end = 0;
5581                 return FALSE;
5582         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5583                 ExcludeClick(origIndex - lineStart);
5584                 return FALSE;
5585         }
5586         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5587         *start = startPV; *end = index-1;
5588         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5589         return TRUE;
5590 }
5591
5592 char *
5593 PvToSAN (char *pv)
5594 {
5595         static char buf[10*MSG_SIZ];
5596         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5597         *buf = NULLCHAR;
5598         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5599         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5600         for(i = forwardMostMove; i<endPV; i++){
5601             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5602             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5603             k += strlen(buf+k);
5604         }
5605         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5606         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5607         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5608         endPV = savedEnd;
5609         return buf;
5610 }
5611
5612 Boolean
5613 LoadPV (int x, int y)
5614 { // called on right mouse click to load PV
5615   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5616   lastX = x; lastY = y;
5617   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5618   extendGame = FALSE;
5619   return TRUE;
5620 }
5621
5622 void
5623 UnLoadPV ()
5624 {
5625   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5626   if(endPV < 0) return;
5627   if(appData.autoCopyPV) CopyFENToClipboard();
5628   endPV = -1;
5629   if(extendGame && currentMove > forwardMostMove) {
5630         Boolean saveAnimate = appData.animate;
5631         if(pushed) {
5632             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5633                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5634             } else storedGames--; // abandon shelved tail of original game
5635         }
5636         pushed = FALSE;
5637         forwardMostMove = currentMove;
5638         currentMove = oldFMM;
5639         appData.animate = FALSE;
5640         ToNrEvent(forwardMostMove);
5641         appData.animate = saveAnimate;
5642   }
5643   currentMove = forwardMostMove;
5644   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5645   ClearPremoveHighlights();
5646   DrawPosition(TRUE, boards[currentMove]);
5647 }
5648
5649 void
5650 MovePV (int x, int y, int h)
5651 { // step through PV based on mouse coordinates (called on mouse move)
5652   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5653
5654   // we must somehow check if right button is still down (might be released off board!)
5655   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5656   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5657   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5658   if(!step) return;
5659   lastX = x; lastY = y;
5660
5661   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5662   if(endPV < 0) return;
5663   if(y < margin) step = 1; else
5664   if(y > h - margin) step = -1;
5665   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5666   currentMove += step;
5667   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5668   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5669                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5670   DrawPosition(FALSE, boards[currentMove]);
5671 }
5672
5673
5674 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5675 // All positions will have equal probability, but the current method will not provide a unique
5676 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5677 #define DARK 1
5678 #define LITE 2
5679 #define ANY 3
5680
5681 int squaresLeft[4];
5682 int piecesLeft[(int)BlackPawn];
5683 int seed, nrOfShuffles;
5684
5685 void
5686 GetPositionNumber ()
5687 {       // sets global variable seed
5688         int i;
5689
5690         seed = appData.defaultFrcPosition;
5691         if(seed < 0) { // randomize based on time for negative FRC position numbers
5692                 for(i=0; i<50; i++) seed += random();
5693                 seed = random() ^ random() >> 8 ^ random() << 8;
5694                 if(seed<0) seed = -seed;
5695         }
5696 }
5697
5698 int
5699 put (Board board, int pieceType, int rank, int n, int shade)
5700 // put the piece on the (n-1)-th empty squares of the given shade
5701 {
5702         int i;
5703
5704         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5705                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5706                         board[rank][i] = (ChessSquare) pieceType;
5707                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5708                         squaresLeft[ANY]--;
5709                         piecesLeft[pieceType]--;
5710                         return i;
5711                 }
5712         }
5713         return -1;
5714 }
5715
5716
5717 void
5718 AddOnePiece (Board board, int pieceType, int rank, int shade)
5719 // calculate where the next piece goes, (any empty square), and put it there
5720 {
5721         int i;
5722
5723         i = seed % squaresLeft[shade];
5724         nrOfShuffles *= squaresLeft[shade];
5725         seed /= squaresLeft[shade];
5726         put(board, pieceType, rank, i, shade);
5727 }
5728
5729 void
5730 AddTwoPieces (Board board, int pieceType, int rank)
5731 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5732 {
5733         int i, n=squaresLeft[ANY], j=n-1, k;
5734
5735         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5736         i = seed % k;  // pick one
5737         nrOfShuffles *= k;
5738         seed /= k;
5739         while(i >= j) i -= j--;
5740         j = n - 1 - j; i += j;
5741         put(board, pieceType, rank, j, ANY);
5742         put(board, pieceType, rank, i, ANY);
5743 }
5744
5745 void
5746 SetUpShuffle (Board board, int number)
5747 {
5748         int i, p, first=1;
5749
5750         GetPositionNumber(); nrOfShuffles = 1;
5751
5752         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5753         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5754         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5755
5756         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5757
5758         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5759             p = (int) board[0][i];
5760             if(p < (int) BlackPawn) piecesLeft[p] ++;
5761             board[0][i] = EmptySquare;
5762         }
5763
5764         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5765             // shuffles restricted to allow normal castling put KRR first
5766             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5767                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5768             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5769                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5770             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5771                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5772             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5773                 put(board, WhiteRook, 0, 0, ANY);
5774             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5775         }
5776
5777         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5778             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5779             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5780                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5781                 while(piecesLeft[p] >= 2) {
5782                     AddOnePiece(board, p, 0, LITE);
5783                     AddOnePiece(board, p, 0, DARK);
5784                 }
5785                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5786             }
5787
5788         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5789             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5790             // but we leave King and Rooks for last, to possibly obey FRC restriction
5791             if(p == (int)WhiteRook) continue;
5792             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5793             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5794         }
5795
5796         // now everything is placed, except perhaps King (Unicorn) and Rooks
5797
5798         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5799             // Last King gets castling rights
5800             while(piecesLeft[(int)WhiteUnicorn]) {
5801                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5802                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5803             }
5804
5805             while(piecesLeft[(int)WhiteKing]) {
5806                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5807                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5808             }
5809
5810
5811         } else {
5812             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5813             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5814         }
5815
5816         // Only Rooks can be left; simply place them all
5817         while(piecesLeft[(int)WhiteRook]) {
5818                 i = put(board, WhiteRook, 0, 0, ANY);
5819                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5820                         if(first) {
5821                                 first=0;
5822                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5823                         }
5824                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5825                 }
5826         }
5827         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5828             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5829         }
5830
5831         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5832 }
5833
5834 int
5835 SetCharTable (char *table, const char * map)
5836 /* [HGM] moved here from winboard.c because of its general usefulness */
5837 /*       Basically a safe strcpy that uses the last character as King */
5838 {
5839     int result = FALSE; int NrPieces;
5840
5841     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5842                     && NrPieces >= 12 && !(NrPieces&1)) {
5843         int i; /* [HGM] Accept even length from 12 to 34 */
5844
5845         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5846         for( i=0; i<NrPieces/2-1; i++ ) {
5847             table[i] = map[i];
5848             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5849         }
5850         table[(int) WhiteKing]  = map[NrPieces/2-1];
5851         table[(int) BlackKing]  = map[NrPieces-1];
5852
5853         result = TRUE;
5854     }
5855
5856     return result;
5857 }
5858
5859 void
5860 Prelude (Board board)
5861 {       // [HGM] superchess: random selection of exo-pieces
5862         int i, j, k; ChessSquare p;
5863         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5864
5865         GetPositionNumber(); // use FRC position number
5866
5867         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5868             SetCharTable(pieceToChar, appData.pieceToCharTable);
5869             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5870                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5871         }
5872
5873         j = seed%4;                 seed /= 4;
5874         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5875         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5876         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5877         j = seed%3 + (seed%3 >= j); seed /= 3;
5878         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5879         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5880         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5881         j = seed%3;                 seed /= 3;
5882         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5883         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5884         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5885         j = seed%2 + (seed%2 >= j); seed /= 2;
5886         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5887         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5888         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5889         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5890         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5891         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5892         put(board, exoPieces[0],    0, 0, ANY);
5893         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5894 }
5895
5896 void
5897 InitPosition (int redraw)
5898 {
5899     ChessSquare (* pieces)[BOARD_FILES];
5900     int i, j, pawnRow=1, pieceRows=1, overrule,
5901     oldx = gameInfo.boardWidth,
5902     oldy = gameInfo.boardHeight,
5903     oldh = gameInfo.holdingsWidth;
5904     static int oldv;
5905
5906     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5907
5908     /* [AS] Initialize pv info list [HGM] and game status */
5909     {
5910         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5911             pvInfoList[i].depth = 0;
5912             boards[i][EP_STATUS] = EP_NONE;
5913             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5914         }
5915
5916         initialRulePlies = 0; /* 50-move counter start */
5917
5918         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5919         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5920     }
5921
5922
5923     /* [HGM] logic here is completely changed. In stead of full positions */
5924     /* the initialized data only consist of the two backranks. The switch */
5925     /* selects which one we will use, which is than copied to the Board   */
5926     /* initialPosition, which for the rest is initialized by Pawns and    */
5927     /* empty squares. This initial position is then copied to boards[0],  */
5928     /* possibly after shuffling, so that it remains available.            */
5929
5930     gameInfo.holdingsWidth = 0; /* default board sizes */
5931     gameInfo.boardWidth    = 8;
5932     gameInfo.boardHeight   = 8;
5933     gameInfo.holdingsSize  = 0;
5934     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5935     for(i=0; i<BOARD_FILES-2; i++)
5936       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5937     initialPosition[EP_STATUS] = EP_NONE;
5938     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5939     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5940          SetCharTable(pieceNickName, appData.pieceNickNames);
5941     else SetCharTable(pieceNickName, "............");
5942     pieces = FIDEArray;
5943
5944     switch (gameInfo.variant) {
5945     case VariantFischeRandom:
5946       shuffleOpenings = TRUE;
5947     default:
5948       break;
5949     case VariantShatranj:
5950       pieces = ShatranjArray;
5951       nrCastlingRights = 0;
5952       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5953       break;
5954     case VariantMakruk:
5955       pieces = makrukArray;
5956       nrCastlingRights = 0;
5957       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5958       break;
5959     case VariantASEAN:
5960       pieces = aseanArray;
5961       nrCastlingRights = 0;
5962       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5963       break;
5964     case VariantTwoKings:
5965       pieces = twoKingsArray;
5966       break;
5967     case VariantGrand:
5968       pieces = GrandArray;
5969       nrCastlingRights = 0;
5970       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5971       gameInfo.boardWidth = 10;
5972       gameInfo.boardHeight = 10;
5973       gameInfo.holdingsSize = 7;
5974       break;
5975     case VariantCapaRandom:
5976       shuffleOpenings = TRUE;
5977     case VariantCapablanca:
5978       pieces = CapablancaArray;
5979       gameInfo.boardWidth = 10;
5980       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5981       break;
5982     case VariantGothic:
5983       pieces = GothicArray;
5984       gameInfo.boardWidth = 10;
5985       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5986       break;
5987     case VariantSChess:
5988       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5989       gameInfo.holdingsSize = 7;
5990       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5991       break;
5992     case VariantJanus:
5993       pieces = JanusArray;
5994       gameInfo.boardWidth = 10;
5995       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5996       nrCastlingRights = 6;
5997         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5998         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5999         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6000         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6001         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6002         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6003       break;
6004     case VariantFalcon:
6005       pieces = FalconArray;
6006       gameInfo.boardWidth = 10;
6007       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6008       break;
6009     case VariantXiangqi:
6010       pieces = XiangqiArray;
6011       gameInfo.boardWidth  = 9;
6012       gameInfo.boardHeight = 10;
6013       nrCastlingRights = 0;
6014       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6015       break;
6016     case VariantShogi:
6017       pieces = ShogiArray;
6018       gameInfo.boardWidth  = 9;
6019       gameInfo.boardHeight = 9;
6020       gameInfo.holdingsSize = 7;
6021       nrCastlingRights = 0;
6022       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6023       break;
6024     case VariantChu:
6025       pieces = ChuArray; pieceRows = 3;
6026       gameInfo.boardWidth  = 12;
6027       gameInfo.boardHeight = 12;
6028       nrCastlingRights = 0;
6029       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6030                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6031       break;
6032     case VariantCourier:
6033       pieces = CourierArray;
6034       gameInfo.boardWidth  = 12;
6035       nrCastlingRights = 0;
6036       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6037       break;
6038     case VariantKnightmate:
6039       pieces = KnightmateArray;
6040       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6041       break;
6042     case VariantSpartan:
6043       pieces = SpartanArray;
6044       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6045       break;
6046     case VariantLion:
6047       pieces = lionArray;
6048       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6049       break;
6050     case VariantFairy:
6051       pieces = fairyArray;
6052       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6053       break;
6054     case VariantGreat:
6055       pieces = GreatArray;
6056       gameInfo.boardWidth = 10;
6057       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6058       gameInfo.holdingsSize = 8;
6059       break;
6060     case VariantSuper:
6061       pieces = FIDEArray;
6062       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6063       gameInfo.holdingsSize = 8;
6064       startedFromSetupPosition = TRUE;
6065       break;
6066     case VariantCrazyhouse:
6067     case VariantBughouse:
6068       pieces = FIDEArray;
6069       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6070       gameInfo.holdingsSize = 5;
6071       break;
6072     case VariantWildCastle:
6073       pieces = FIDEArray;
6074       /* !!?shuffle with kings guaranteed to be on d or e file */
6075       shuffleOpenings = 1;
6076       break;
6077     case VariantNoCastle:
6078       pieces = FIDEArray;
6079       nrCastlingRights = 0;
6080       /* !!?unconstrained back-rank shuffle */
6081       shuffleOpenings = 1;
6082       break;
6083     }
6084
6085     overrule = 0;
6086     if(appData.NrFiles >= 0) {
6087         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6088         gameInfo.boardWidth = appData.NrFiles;
6089     }
6090     if(appData.NrRanks >= 0) {
6091         gameInfo.boardHeight = appData.NrRanks;
6092     }
6093     if(appData.holdingsSize >= 0) {
6094         i = appData.holdingsSize;
6095         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6096         gameInfo.holdingsSize = i;
6097     }
6098     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6099     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6100         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6101
6102     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6103     if(pawnRow < 1) pawnRow = 1;
6104     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6105     if(gameInfo.variant == VariantChu) pawnRow = 3;
6106
6107     /* User pieceToChar list overrules defaults */
6108     if(appData.pieceToCharTable != NULL)
6109         SetCharTable(pieceToChar, appData.pieceToCharTable);
6110
6111     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6112
6113         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6114             s = (ChessSquare) 0; /* account holding counts in guard band */
6115         for( i=0; i<BOARD_HEIGHT; i++ )
6116             initialPosition[i][j] = s;
6117
6118         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6119         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6120         initialPosition[pawnRow][j] = WhitePawn;
6121         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6122         if(gameInfo.variant == VariantXiangqi) {
6123             if(j&1) {
6124                 initialPosition[pawnRow][j] =
6125                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6126                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6127                    initialPosition[2][j] = WhiteCannon;
6128                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6129                 }
6130             }
6131         }
6132         if(gameInfo.variant == VariantChu) {
6133              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6134                initialPosition[pawnRow+1][j] = WhiteCobra,
6135                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6136              for(i=1; i<pieceRows; i++) {
6137                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6138                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6139              }
6140         }
6141         if(gameInfo.variant == VariantGrand) {
6142             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6143                initialPosition[0][j] = WhiteRook;
6144                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6145             }
6146         }
6147         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6148     }
6149     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6150
6151             j=BOARD_LEFT+1;
6152             initialPosition[1][j] = WhiteBishop;
6153             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6154             j=BOARD_RGHT-2;
6155             initialPosition[1][j] = WhiteRook;
6156             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6157     }
6158
6159     if( nrCastlingRights == -1) {
6160         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6161         /*       This sets default castling rights from none to normal corners   */
6162         /* Variants with other castling rights must set them themselves above    */
6163         nrCastlingRights = 6;
6164
6165         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6166         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6167         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6168         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6169         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6170         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6171      }
6172
6173      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6174      if(gameInfo.variant == VariantGreat) { // promotion commoners
6175         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6176         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6177         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6178         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6179      }
6180      if( gameInfo.variant == VariantSChess ) {
6181       initialPosition[1][0] = BlackMarshall;
6182       initialPosition[2][0] = BlackAngel;
6183       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6184       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6185       initialPosition[1][1] = initialPosition[2][1] =
6186       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6187      }
6188   if (appData.debugMode) {
6189     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6190   }
6191     if(shuffleOpenings) {
6192         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6193         startedFromSetupPosition = TRUE;
6194     }
6195     if(startedFromPositionFile) {
6196       /* [HGM] loadPos: use PositionFile for every new game */
6197       CopyBoard(initialPosition, filePosition);
6198       for(i=0; i<nrCastlingRights; i++)
6199           initialRights[i] = filePosition[CASTLING][i];
6200       startedFromSetupPosition = TRUE;
6201     }
6202
6203     CopyBoard(boards[0], initialPosition);
6204
6205     if(oldx != gameInfo.boardWidth ||
6206        oldy != gameInfo.boardHeight ||
6207        oldv != gameInfo.variant ||
6208        oldh != gameInfo.holdingsWidth
6209                                          )
6210             InitDrawingSizes(-2 ,0);
6211
6212     oldv = gameInfo.variant;
6213     if (redraw)
6214       DrawPosition(TRUE, boards[currentMove]);
6215 }
6216
6217 void
6218 SendBoard (ChessProgramState *cps, int moveNum)
6219 {
6220     char message[MSG_SIZ];
6221
6222     if (cps->useSetboard) {
6223       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6224       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6225       SendToProgram(message, cps);
6226       free(fen);
6227
6228     } else {
6229       ChessSquare *bp;
6230       int i, j, left=0, right=BOARD_WIDTH;
6231       /* Kludge to set black to move, avoiding the troublesome and now
6232        * deprecated "black" command.
6233        */
6234       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6235         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6236
6237       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6238
6239       SendToProgram("edit\n", cps);
6240       SendToProgram("#\n", cps);
6241       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6242         bp = &boards[moveNum][i][left];
6243         for (j = left; j < right; j++, bp++) {
6244           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6245           if ((int) *bp < (int) BlackPawn) {
6246             if(j == BOARD_RGHT+1)
6247                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6248             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6249             if(message[0] == '+' || message[0] == '~') {
6250               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6251                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6252                         AAA + j, ONE + i);
6253             }
6254             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6255                 message[1] = BOARD_RGHT   - 1 - j + '1';
6256                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6257             }
6258             SendToProgram(message, cps);
6259           }
6260         }
6261       }
6262
6263       SendToProgram("c\n", cps);
6264       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6265         bp = &boards[moveNum][i][left];
6266         for (j = left; j < right; j++, bp++) {
6267           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6268           if (((int) *bp != (int) EmptySquare)
6269               && ((int) *bp >= (int) BlackPawn)) {
6270             if(j == BOARD_LEFT-2)
6271                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6272             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6273                     AAA + j, ONE + i);
6274             if(message[0] == '+' || message[0] == '~') {
6275               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6276                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6277                         AAA + j, ONE + i);
6278             }
6279             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6280                 message[1] = BOARD_RGHT   - 1 - j + '1';
6281                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6282             }
6283             SendToProgram(message, cps);
6284           }
6285         }
6286       }
6287
6288       SendToProgram(".\n", cps);
6289     }
6290     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6291 }
6292
6293 char exclusionHeader[MSG_SIZ];
6294 int exCnt, excludePtr;
6295 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6296 static Exclusion excluTab[200];
6297 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6298
6299 static void
6300 WriteMap (int s)
6301 {
6302     int j;
6303     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6304     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6305 }
6306
6307 static void
6308 ClearMap ()
6309 {
6310     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6311     excludePtr = 24; exCnt = 0;
6312     WriteMap(0);
6313 }
6314
6315 static void
6316 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6317 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6318     char buf[2*MOVE_LEN], *p;
6319     Exclusion *e = excluTab;
6320     int i;
6321     for(i=0; i<exCnt; i++)
6322         if(e[i].ff == fromX && e[i].fr == fromY &&
6323            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6324     if(i == exCnt) { // was not in exclude list; add it
6325         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6326         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6327             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6328             return; // abort
6329         }
6330         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6331         excludePtr++; e[i].mark = excludePtr++;
6332         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6333         exCnt++;
6334     }
6335     exclusionHeader[e[i].mark] = state;
6336 }
6337
6338 static int
6339 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6340 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6341     char buf[MSG_SIZ];
6342     int j, k;
6343     ChessMove moveType;
6344     if((signed char)promoChar == -1) { // kludge to indicate best move
6345         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6346             return 1; // if unparsable, abort
6347     }
6348     // update exclusion map (resolving toggle by consulting existing state)
6349     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6350     j = k%8; k >>= 3;
6351     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6352     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6353          excludeMap[k] |=   1<<j;
6354     else excludeMap[k] &= ~(1<<j);
6355     // update header
6356     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6357     // inform engine
6358     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6359     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6360     SendToBoth(buf);
6361     return (state == '+');
6362 }
6363
6364 static void
6365 ExcludeClick (int index)
6366 {
6367     int i, j;
6368     Exclusion *e = excluTab;
6369     if(index < 25) { // none, best or tail clicked
6370         if(index < 13) { // none: include all
6371             WriteMap(0); // clear map
6372             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6373             SendToBoth("include all\n"); // and inform engine
6374         } else if(index > 18) { // tail
6375             if(exclusionHeader[19] == '-') { // tail was excluded
6376                 SendToBoth("include all\n");
6377                 WriteMap(0); // clear map completely
6378                 // now re-exclude selected moves
6379                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6380                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6381             } else { // tail was included or in mixed state
6382                 SendToBoth("exclude all\n");
6383                 WriteMap(0xFF); // fill map completely
6384                 // now re-include selected moves
6385                 j = 0; // count them
6386                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6387                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6388                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6389             }
6390         } else { // best
6391             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6392         }
6393     } else {
6394         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6395             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6396             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6397             break;
6398         }
6399     }
6400 }
6401
6402 ChessSquare
6403 DefaultPromoChoice (int white)
6404 {
6405     ChessSquare result;
6406     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6407        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6408         result = WhiteFerz; // no choice
6409     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6410         result= WhiteKing; // in Suicide Q is the last thing we want
6411     else if(gameInfo.variant == VariantSpartan)
6412         result = white ? WhiteQueen : WhiteAngel;
6413     else result = WhiteQueen;
6414     if(!white) result = WHITE_TO_BLACK result;
6415     return result;
6416 }
6417
6418 static int autoQueen; // [HGM] oneclick
6419
6420 int
6421 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6422 {
6423     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6424     /* [HGM] add Shogi promotions */
6425     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6426     ChessSquare piece;
6427     ChessMove moveType;
6428     Boolean premove;
6429
6430     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6431     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6432
6433     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6434       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6435         return FALSE;
6436
6437     piece = boards[currentMove][fromY][fromX];
6438     if(gameInfo.variant == VariantChu) {
6439         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6440         promotionZoneSize = BOARD_HEIGHT/3;
6441         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6442     } else if(gameInfo.variant == VariantShogi) {
6443         promotionZoneSize = BOARD_HEIGHT/3;
6444         highestPromotingPiece = (int)WhiteAlfil;
6445     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6446         promotionZoneSize = 3;
6447     }
6448
6449     // Treat Lance as Pawn when it is not representing Amazon
6450     if(gameInfo.variant != VariantSuper) {
6451         if(piece == WhiteLance) piece = WhitePawn; else
6452         if(piece == BlackLance) piece = BlackPawn;
6453     }
6454
6455     // next weed out all moves that do not touch the promotion zone at all
6456     if((int)piece >= BlackPawn) {
6457         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6458              return FALSE;
6459         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6460     } else {
6461         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6462            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6463     }
6464
6465     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6466
6467     // weed out mandatory Shogi promotions
6468     if(gameInfo.variant == VariantShogi) {
6469         if(piece >= BlackPawn) {
6470             if(toY == 0 && piece == BlackPawn ||
6471                toY == 0 && piece == BlackQueen ||
6472                toY <= 1 && piece == BlackKnight) {
6473                 *promoChoice = '+';
6474                 return FALSE;
6475             }
6476         } else {
6477             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6478                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6479                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6480                 *promoChoice = '+';
6481                 return FALSE;
6482             }
6483         }
6484     }
6485
6486     // weed out obviously illegal Pawn moves
6487     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6488         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6489         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6490         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6491         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6492         // note we are not allowed to test for valid (non-)capture, due to premove
6493     }
6494
6495     // we either have a choice what to promote to, or (in Shogi) whether to promote
6496     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6497        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6498         *promoChoice = PieceToChar(BlackFerz);  // no choice
6499         return FALSE;
6500     }
6501     // no sense asking what we must promote to if it is going to explode...
6502     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6503         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6504         return FALSE;
6505     }
6506     // give caller the default choice even if we will not make it
6507     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6508     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6509     if(        sweepSelect && gameInfo.variant != VariantGreat
6510                            && gameInfo.variant != VariantGrand
6511                            && gameInfo.variant != VariantSuper) return FALSE;
6512     if(autoQueen) return FALSE; // predetermined
6513
6514     // suppress promotion popup on illegal moves that are not premoves
6515     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6516               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6517     if(appData.testLegality && !premove) {
6518         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6519                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6520         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6521             return FALSE;
6522     }
6523
6524     return TRUE;
6525 }
6526
6527 int
6528 InPalace (int row, int column)
6529 {   /* [HGM] for Xiangqi */
6530     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6531          column < (BOARD_WIDTH + 4)/2 &&
6532          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6533     return FALSE;
6534 }
6535
6536 int
6537 PieceForSquare (int x, int y)
6538 {
6539   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6540      return -1;
6541   else
6542      return boards[currentMove][y][x];
6543 }
6544
6545 int
6546 OKToStartUserMove (int x, int y)
6547 {
6548     ChessSquare from_piece;
6549     int white_piece;
6550
6551     if (matchMode) return FALSE;
6552     if (gameMode == EditPosition) return TRUE;
6553
6554     if (x >= 0 && y >= 0)
6555       from_piece = boards[currentMove][y][x];
6556     else
6557       from_piece = EmptySquare;
6558
6559     if (from_piece == EmptySquare) return FALSE;
6560
6561     white_piece = (int)from_piece >= (int)WhitePawn &&
6562       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6563
6564     switch (gameMode) {
6565       case AnalyzeFile:
6566       case TwoMachinesPlay:
6567       case EndOfGame:
6568         return FALSE;
6569
6570       case IcsObserving:
6571       case IcsIdle:
6572         return FALSE;
6573
6574       case MachinePlaysWhite:
6575       case IcsPlayingBlack:
6576         if (appData.zippyPlay) return FALSE;
6577         if (white_piece) {
6578             DisplayMoveError(_("You are playing Black"));
6579             return FALSE;
6580         }
6581         break;
6582
6583       case MachinePlaysBlack:
6584       case IcsPlayingWhite:
6585         if (appData.zippyPlay) return FALSE;
6586         if (!white_piece) {
6587             DisplayMoveError(_("You are playing White"));
6588             return FALSE;
6589         }
6590         break;
6591
6592       case PlayFromGameFile:
6593             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6594       case EditGame:
6595         if (!white_piece && WhiteOnMove(currentMove)) {
6596             DisplayMoveError(_("It is White's turn"));
6597             return FALSE;
6598         }
6599         if (white_piece && !WhiteOnMove(currentMove)) {
6600             DisplayMoveError(_("It is Black's turn"));
6601             return FALSE;
6602         }
6603         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6604             /* Editing correspondence game history */
6605             /* Could disallow this or prompt for confirmation */
6606             cmailOldMove = -1;
6607         }
6608         break;
6609
6610       case BeginningOfGame:
6611         if (appData.icsActive) return FALSE;
6612         if (!appData.noChessProgram) {
6613             if (!white_piece) {
6614                 DisplayMoveError(_("You are playing White"));
6615                 return FALSE;
6616             }
6617         }
6618         break;
6619
6620       case Training:
6621         if (!white_piece && WhiteOnMove(currentMove)) {
6622             DisplayMoveError(_("It is White's turn"));
6623             return FALSE;
6624         }
6625         if (white_piece && !WhiteOnMove(currentMove)) {
6626             DisplayMoveError(_("It is Black's turn"));
6627             return FALSE;
6628         }
6629         break;
6630
6631       default:
6632       case IcsExamining:
6633         break;
6634     }
6635     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6636         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6637         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6638         && gameMode != AnalyzeFile && gameMode != Training) {
6639         DisplayMoveError(_("Displayed position is not current"));
6640         return FALSE;
6641     }
6642     return TRUE;
6643 }
6644
6645 Boolean
6646 OnlyMove (int *x, int *y, Boolean captures)
6647 {
6648     DisambiguateClosure cl;
6649     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6650     switch(gameMode) {
6651       case MachinePlaysBlack:
6652       case IcsPlayingWhite:
6653       case BeginningOfGame:
6654         if(!WhiteOnMove(currentMove)) return FALSE;
6655         break;
6656       case MachinePlaysWhite:
6657       case IcsPlayingBlack:
6658         if(WhiteOnMove(currentMove)) return FALSE;
6659         break;
6660       case EditGame:
6661         break;
6662       default:
6663         return FALSE;
6664     }
6665     cl.pieceIn = EmptySquare;
6666     cl.rfIn = *y;
6667     cl.ffIn = *x;
6668     cl.rtIn = -1;
6669     cl.ftIn = -1;
6670     cl.promoCharIn = NULLCHAR;
6671     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6672     if( cl.kind == NormalMove ||
6673         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6674         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6675         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6676       fromX = cl.ff;
6677       fromY = cl.rf;
6678       *x = cl.ft;
6679       *y = cl.rt;
6680       return TRUE;
6681     }
6682     if(cl.kind != ImpossibleMove) return FALSE;
6683     cl.pieceIn = EmptySquare;
6684     cl.rfIn = -1;
6685     cl.ffIn = -1;
6686     cl.rtIn = *y;
6687     cl.ftIn = *x;
6688     cl.promoCharIn = NULLCHAR;
6689     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6690     if( cl.kind == NormalMove ||
6691         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6692         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6693         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6694       fromX = cl.ff;
6695       fromY = cl.rf;
6696       *x = cl.ft;
6697       *y = cl.rt;
6698       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6699       return TRUE;
6700     }
6701     return FALSE;
6702 }
6703
6704 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6705 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6706 int lastLoadGameUseList = FALSE;
6707 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6708 ChessMove lastLoadGameStart = EndOfFile;
6709 int doubleClick;
6710
6711 void
6712 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6713 {
6714     ChessMove moveType;
6715     ChessSquare pup;
6716     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6717
6718     /* Check if the user is playing in turn.  This is complicated because we
6719        let the user "pick up" a piece before it is his turn.  So the piece he
6720        tried to pick up may have been captured by the time he puts it down!
6721        Therefore we use the color the user is supposed to be playing in this
6722        test, not the color of the piece that is currently on the starting
6723        square---except in EditGame mode, where the user is playing both
6724        sides; fortunately there the capture race can't happen.  (It can
6725        now happen in IcsExamining mode, but that's just too bad.  The user
6726        will get a somewhat confusing message in that case.)
6727        */
6728
6729     switch (gameMode) {
6730       case AnalyzeFile:
6731       case TwoMachinesPlay:
6732       case EndOfGame:
6733       case IcsObserving:
6734       case IcsIdle:
6735         /* We switched into a game mode where moves are not accepted,
6736            perhaps while the mouse button was down. */
6737         return;
6738
6739       case MachinePlaysWhite:
6740         /* User is moving for Black */
6741         if (WhiteOnMove(currentMove)) {
6742             DisplayMoveError(_("It is White's turn"));
6743             return;
6744         }
6745         break;
6746
6747       case MachinePlaysBlack:
6748         /* User is moving for White */
6749         if (!WhiteOnMove(currentMove)) {
6750             DisplayMoveError(_("It is Black's turn"));
6751             return;
6752         }
6753         break;
6754
6755       case PlayFromGameFile:
6756             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6757       case EditGame:
6758       case IcsExamining:
6759       case BeginningOfGame:
6760       case AnalyzeMode:
6761       case Training:
6762         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6763         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6764             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6765             /* User is moving for Black */
6766             if (WhiteOnMove(currentMove)) {
6767                 DisplayMoveError(_("It is White's turn"));
6768                 return;
6769             }
6770         } else {
6771             /* User is moving for White */
6772             if (!WhiteOnMove(currentMove)) {
6773                 DisplayMoveError(_("It is Black's turn"));
6774                 return;
6775             }
6776         }
6777         break;
6778
6779       case IcsPlayingBlack:
6780         /* User is moving for Black */
6781         if (WhiteOnMove(currentMove)) {
6782             if (!appData.premove) {
6783                 DisplayMoveError(_("It is White's turn"));
6784             } else if (toX >= 0 && toY >= 0) {
6785                 premoveToX = toX;
6786                 premoveToY = toY;
6787                 premoveFromX = fromX;
6788                 premoveFromY = fromY;
6789                 premovePromoChar = promoChar;
6790                 gotPremove = 1;
6791                 if (appData.debugMode)
6792                     fprintf(debugFP, "Got premove: fromX %d,"
6793                             "fromY %d, toX %d, toY %d\n",
6794                             fromX, fromY, toX, toY);
6795             }
6796             return;
6797         }
6798         break;
6799
6800       case IcsPlayingWhite:
6801         /* User is moving for White */
6802         if (!WhiteOnMove(currentMove)) {
6803             if (!appData.premove) {
6804                 DisplayMoveError(_("It is Black's turn"));
6805             } else if (toX >= 0 && toY >= 0) {
6806                 premoveToX = toX;
6807                 premoveToY = toY;
6808                 premoveFromX = fromX;
6809                 premoveFromY = fromY;
6810                 premovePromoChar = promoChar;
6811                 gotPremove = 1;
6812                 if (appData.debugMode)
6813                     fprintf(debugFP, "Got premove: fromX %d,"
6814                             "fromY %d, toX %d, toY %d\n",
6815                             fromX, fromY, toX, toY);
6816             }
6817             return;
6818         }
6819         break;
6820
6821       default:
6822         break;
6823
6824       case EditPosition:
6825         /* EditPosition, empty square, or different color piece;
6826            click-click move is possible */
6827         if (toX == -2 || toY == -2) {
6828             boards[0][fromY][fromX] = EmptySquare;
6829             DrawPosition(FALSE, boards[currentMove]);
6830             return;
6831         } else if (toX >= 0 && toY >= 0) {
6832             boards[0][toY][toX] = boards[0][fromY][fromX];
6833             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6834                 if(boards[0][fromY][0] != EmptySquare) {
6835                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6836                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6837                 }
6838             } else
6839             if(fromX == BOARD_RGHT+1) {
6840                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6841                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6842                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6843                 }
6844             } else
6845             boards[0][fromY][fromX] = gatingPiece;
6846             DrawPosition(FALSE, boards[currentMove]);
6847             return;
6848         }
6849         return;
6850     }
6851
6852     if(toX < 0 || toY < 0) return;
6853     pup = boards[currentMove][toY][toX];
6854
6855     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6856     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6857          if( pup != EmptySquare ) return;
6858          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6859            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6860                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6861            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6862            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6863            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6864            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6865          fromY = DROP_RANK;
6866     }
6867
6868     /* [HGM] always test for legality, to get promotion info */
6869     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6870                                          fromY, fromX, toY, toX, promoChar);
6871
6872     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6873
6874     /* [HGM] but possibly ignore an IllegalMove result */
6875     if (appData.testLegality) {
6876         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6877             DisplayMoveError(_("Illegal move"));
6878             return;
6879         }
6880     }
6881
6882     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6883         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6884              ClearPremoveHighlights(); // was included
6885         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6886         return;
6887     }
6888
6889     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6890 }
6891
6892 /* Common tail of UserMoveEvent and DropMenuEvent */
6893 int
6894 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6895 {
6896     char *bookHit = 0;
6897
6898     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6899         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6900         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6901         if(WhiteOnMove(currentMove)) {
6902             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6903         } else {
6904             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6905         }
6906     }
6907
6908     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6909        move type in caller when we know the move is a legal promotion */
6910     if(moveType == NormalMove && promoChar)
6911         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6912
6913     /* [HGM] <popupFix> The following if has been moved here from
6914        UserMoveEvent(). Because it seemed to belong here (why not allow
6915        piece drops in training games?), and because it can only be
6916        performed after it is known to what we promote. */
6917     if (gameMode == Training) {
6918       /* compare the move played on the board to the next move in the
6919        * game. If they match, display the move and the opponent's response.
6920        * If they don't match, display an error message.
6921        */
6922       int saveAnimate;
6923       Board testBoard;
6924       CopyBoard(testBoard, boards[currentMove]);
6925       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6926
6927       if (CompareBoards(testBoard, boards[currentMove+1])) {
6928         ForwardInner(currentMove+1);
6929
6930         /* Autoplay the opponent's response.
6931          * if appData.animate was TRUE when Training mode was entered,
6932          * the response will be animated.
6933          */
6934         saveAnimate = appData.animate;
6935         appData.animate = animateTraining;
6936         ForwardInner(currentMove+1);
6937         appData.animate = saveAnimate;
6938
6939         /* check for the end of the game */
6940         if (currentMove >= forwardMostMove) {
6941           gameMode = PlayFromGameFile;
6942           ModeHighlight();
6943           SetTrainingModeOff();
6944           DisplayInformation(_("End of game"));
6945         }
6946       } else {
6947         DisplayError(_("Incorrect move"), 0);
6948       }
6949       return 1;
6950     }
6951
6952   /* Ok, now we know that the move is good, so we can kill
6953      the previous line in Analysis Mode */
6954   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6955                                 && currentMove < forwardMostMove) {
6956     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6957     else forwardMostMove = currentMove;
6958   }
6959
6960   ClearMap();
6961
6962   /* If we need the chess program but it's dead, restart it */
6963   ResurrectChessProgram();
6964
6965   /* A user move restarts a paused game*/
6966   if (pausing)
6967     PauseEvent();
6968
6969   thinkOutput[0] = NULLCHAR;
6970
6971   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6972
6973   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6974     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6975     return 1;
6976   }
6977
6978   if (gameMode == BeginningOfGame) {
6979     if (appData.noChessProgram) {
6980       gameMode = EditGame;
6981       SetGameInfo();
6982     } else {
6983       char buf[MSG_SIZ];
6984       gameMode = MachinePlaysBlack;
6985       StartClocks();
6986       SetGameInfo();
6987       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6988       DisplayTitle(buf);
6989       if (first.sendName) {
6990         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6991         SendToProgram(buf, &first);
6992       }
6993       StartClocks();
6994     }
6995     ModeHighlight();
6996   }
6997
6998   /* Relay move to ICS or chess engine */
6999   if (appData.icsActive) {
7000     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7001         gameMode == IcsExamining) {
7002       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7003         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7004         SendToICS("draw ");
7005         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7006       }
7007       // also send plain move, in case ICS does not understand atomic claims
7008       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7009       ics_user_moved = 1;
7010     }
7011   } else {
7012     if (first.sendTime && (gameMode == BeginningOfGame ||
7013                            gameMode == MachinePlaysWhite ||
7014                            gameMode == MachinePlaysBlack)) {
7015       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7016     }
7017     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7018          // [HGM] book: if program might be playing, let it use book
7019         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7020         first.maybeThinking = TRUE;
7021     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7022         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7023         SendBoard(&first, currentMove+1);
7024         if(second.analyzing) {
7025             if(!second.useSetboard) SendToProgram("undo\n", &second);
7026             SendBoard(&second, currentMove+1);
7027         }
7028     } else {
7029         SendMoveToProgram(forwardMostMove-1, &first);
7030         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7031     }
7032     if (currentMove == cmailOldMove + 1) {
7033       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7034     }
7035   }
7036
7037   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7038
7039   switch (gameMode) {
7040   case EditGame:
7041     if(appData.testLegality)
7042     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7043     case MT_NONE:
7044     case MT_CHECK:
7045       break;
7046     case MT_CHECKMATE:
7047     case MT_STAINMATE:
7048       if (WhiteOnMove(currentMove)) {
7049         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7050       } else {
7051         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7052       }
7053       break;
7054     case MT_STALEMATE:
7055       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7056       break;
7057     }
7058     break;
7059
7060   case MachinePlaysBlack:
7061   case MachinePlaysWhite:
7062     /* disable certain menu options while machine is thinking */
7063     SetMachineThinkingEnables();
7064     break;
7065
7066   default:
7067     break;
7068   }
7069
7070   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7071   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7072
7073   if(bookHit) { // [HGM] book: simulate book reply
7074         static char bookMove[MSG_SIZ]; // a bit generous?
7075
7076         programStats.nodes = programStats.depth = programStats.time =
7077         programStats.score = programStats.got_only_move = 0;
7078         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7079
7080         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7081         strcat(bookMove, bookHit);
7082         HandleMachineMove(bookMove, &first);
7083   }
7084   return 1;
7085 }
7086
7087 void
7088 MarkByFEN(char *fen)
7089 {
7090         int r, f;
7091         if(!appData.markers || !appData.highlightDragging) return;
7092         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7093         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7094         while(*fen) {
7095             int s = 0;
7096             marker[r][f] = 0;
7097             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7098             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7099             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7100             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7101             if(*fen == 'T') marker[r][f++] = 0; else
7102             if(*fen == 'Y') marker[r][f++] = 1; else
7103             if(*fen == 'G') marker[r][f++] = 3; else
7104             if(*fen == 'B') marker[r][f++] = 4; else
7105             if(*fen == 'C') marker[r][f++] = 5; else
7106             if(*fen == 'M') marker[r][f++] = 6; else
7107             if(*fen == 'W') marker[r][f++] = 7; else
7108             if(*fen == 'D') marker[r][f++] = 8; else
7109             if(*fen == 'R') marker[r][f++] = 2; else {
7110                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7111               f += s; fen -= s>0;
7112             }
7113             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7114             if(r < 0) break;
7115             fen++;
7116         }
7117         DrawPosition(TRUE, NULL);
7118 }
7119
7120 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7121
7122 void
7123 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7124 {
7125     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7126     Markers *m = (Markers *) closure;
7127     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7128         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7129                          || kind == WhiteCapturesEnPassant
7130                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7131     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7132 }
7133
7134 void
7135 MarkTargetSquares (int clear)
7136 {
7137   int x, y, sum=0;
7138   if(clear) { // no reason to ever suppress clearing
7139     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7140     if(!sum) return; // nothing was cleared,no redraw needed
7141   } else {
7142     int capt = 0;
7143     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7144        !appData.testLegality || gameMode == EditPosition) return;
7145     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7146     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7147       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7148       if(capt)
7149       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7150     }
7151   }
7152   DrawPosition(FALSE, NULL);
7153 }
7154
7155 int
7156 Explode (Board board, int fromX, int fromY, int toX, int toY)
7157 {
7158     if(gameInfo.variant == VariantAtomic &&
7159        (board[toY][toX] != EmptySquare ||                     // capture?
7160         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7161                          board[fromY][fromX] == BlackPawn   )
7162       )) {
7163         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7164         return TRUE;
7165     }
7166     return FALSE;
7167 }
7168
7169 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7170
7171 int
7172 CanPromote (ChessSquare piece, int y)
7173 {
7174         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7175         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7176         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7177            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7178            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7179          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7180         return (piece == BlackPawn && y == 1 ||
7181                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7182                 piece == BlackLance && y == 1 ||
7183                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7184 }
7185
7186 void
7187 HoverEvent (int xPix, int yPix, int x, int y)
7188 {
7189         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7190         int r, f;
7191         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7192         if(!first.highlight) return;
7193         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7194         if(x == oldX && y == oldY) return; // only do something if we enter new square
7195         oldFromX = fromX; oldFromY = fromY;
7196         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7197           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7198             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7199         else if(oldX != x || oldY != y) {
7200           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7201           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7202             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7203           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7204             char buf[MSG_SIZ];
7205             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7206             SendToProgram(buf, &first);
7207           }
7208           oldX = x; oldY = y;
7209 //        SetHighlights(fromX, fromY, x, y);
7210         }
7211 }
7212
7213 void ReportClick(char *action, int x, int y)
7214 {
7215         char buf[MSG_SIZ]; // Inform engine of what user does
7216         int r, f;
7217         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7218           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7219         if(!first.highlight || gameMode == EditPosition) return;
7220         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7221         SendToProgram(buf, &first);
7222 }
7223
7224 void
7225 LeftClick (ClickType clickType, int xPix, int yPix)
7226 {
7227     int x, y;
7228     Boolean saveAnimate;
7229     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7230     char promoChoice = NULLCHAR;
7231     ChessSquare piece;
7232     static TimeMark lastClickTime, prevClickTime;
7233
7234     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7235
7236     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7237
7238     if (clickType == Press) ErrorPopDown();
7239     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7240
7241     x = EventToSquare(xPix, BOARD_WIDTH);
7242     y = EventToSquare(yPix, BOARD_HEIGHT);
7243     if (!flipView && y >= 0) {
7244         y = BOARD_HEIGHT - 1 - y;
7245     }
7246     if (flipView && x >= 0) {
7247         x = BOARD_WIDTH - 1 - x;
7248     }
7249
7250     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7251         defaultPromoChoice = promoSweep;
7252         promoSweep = EmptySquare;   // terminate sweep
7253         promoDefaultAltered = TRUE;
7254         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7255     }
7256
7257     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7258         if(clickType == Release) return; // ignore upclick of click-click destination
7259         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7260         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7261         if(gameInfo.holdingsWidth &&
7262                 (WhiteOnMove(currentMove)
7263                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7264                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7265             // click in right holdings, for determining promotion piece
7266             ChessSquare p = boards[currentMove][y][x];
7267             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7268             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7269             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7270                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7271                 fromX = fromY = -1;
7272                 return;
7273             }
7274         }
7275         DrawPosition(FALSE, boards[currentMove]);
7276         return;
7277     }
7278
7279     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7280     if(clickType == Press
7281             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7282               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7283               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7284         return;
7285
7286     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7287         // could be static click on premove from-square: abort premove
7288         gotPremove = 0;
7289         ClearPremoveHighlights();
7290     }
7291
7292     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7293         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7294
7295     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7296         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7297                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7298         defaultPromoChoice = DefaultPromoChoice(side);
7299     }
7300
7301     autoQueen = appData.alwaysPromoteToQueen;
7302
7303     if (fromX == -1) {
7304       int originalY = y;
7305       gatingPiece = EmptySquare;
7306       if (clickType != Press) {
7307         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7308             DragPieceEnd(xPix, yPix); dragging = 0;
7309             DrawPosition(FALSE, NULL);
7310         }
7311         return;
7312       }
7313       doubleClick = FALSE;
7314       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7315         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7316       }
7317       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7318       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7319          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7320          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7321             /* First square */
7322             if (OKToStartUserMove(fromX, fromY)) {
7323                 second = 0;
7324                 ReportClick("lift", x, y);
7325                 MarkTargetSquares(0);
7326                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7327                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7328                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7329                     promoSweep = defaultPromoChoice;
7330                     selectFlag = 0; lastX = xPix; lastY = yPix;
7331                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7332                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7333                 }
7334                 if (appData.highlightDragging) {
7335                     SetHighlights(fromX, fromY, -1, -1);
7336                 } else {
7337                     ClearHighlights();
7338                 }
7339             } else fromX = fromY = -1;
7340             return;
7341         }
7342     }
7343
7344     /* fromX != -1 */
7345     if (clickType == Press && gameMode != EditPosition) {
7346         ChessSquare fromP;
7347         ChessSquare toP;
7348         int frc;
7349
7350         // ignore off-board to clicks
7351         if(y < 0 || x < 0) return;
7352
7353         /* Check if clicking again on the same color piece */
7354         fromP = boards[currentMove][fromY][fromX];
7355         toP = boards[currentMove][y][x];
7356         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7357         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7358            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7359              WhitePawn <= toP && toP <= WhiteKing &&
7360              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7361              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7362             (BlackPawn <= fromP && fromP <= BlackKing &&
7363              BlackPawn <= toP && toP <= BlackKing &&
7364              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7365              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7366             /* Clicked again on same color piece -- changed his mind */
7367             second = (x == fromX && y == fromY);
7368             killX = killY = -1;
7369             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7370                 second = FALSE; // first double-click rather than scond click
7371                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7372             }
7373             promoDefaultAltered = FALSE;
7374             MarkTargetSquares(1);
7375            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7376             if (appData.highlightDragging) {
7377                 SetHighlights(x, y, -1, -1);
7378             } else {
7379                 ClearHighlights();
7380             }
7381             if (OKToStartUserMove(x, y)) {
7382                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7383                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7384                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7385                  gatingPiece = boards[currentMove][fromY][fromX];
7386                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7387                 fromX = x;
7388                 fromY = y; dragging = 1;
7389                 ReportClick("lift", x, y);
7390                 MarkTargetSquares(0);
7391                 DragPieceBegin(xPix, yPix, FALSE);
7392                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7393                     promoSweep = defaultPromoChoice;
7394                     selectFlag = 0; lastX = xPix; lastY = yPix;
7395                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7396                 }
7397             }
7398            }
7399            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7400            second = FALSE;
7401         }
7402         // ignore clicks on holdings
7403         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7404     }
7405
7406     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7407         DragPieceEnd(xPix, yPix); dragging = 0;
7408         if(clearFlag) {
7409             // a deferred attempt to click-click move an empty square on top of a piece
7410             boards[currentMove][y][x] = EmptySquare;
7411             ClearHighlights();
7412             DrawPosition(FALSE, boards[currentMove]);
7413             fromX = fromY = -1; clearFlag = 0;
7414             return;
7415         }
7416         if (appData.animateDragging) {
7417             /* Undo animation damage if any */
7418             DrawPosition(FALSE, NULL);
7419         }
7420         if (second || sweepSelecting) {
7421             /* Second up/down in same square; just abort move */
7422             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7423             second = sweepSelecting = 0;
7424             fromX = fromY = -1;
7425             gatingPiece = EmptySquare;
7426             MarkTargetSquares(1);
7427             ClearHighlights();
7428             gotPremove = 0;
7429             ClearPremoveHighlights();
7430         } else {
7431             /* First upclick in same square; start click-click mode */
7432             SetHighlights(x, y, -1, -1);
7433         }
7434         return;
7435     }
7436
7437     clearFlag = 0;
7438
7439     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7440         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7441         DisplayMessage(_("only marked squares are legal"),"");
7442         DrawPosition(TRUE, NULL);
7443         return; // ignore to-click
7444     }
7445
7446     /* we now have a different from- and (possibly off-board) to-square */
7447     /* Completed move */
7448     if(!sweepSelecting) {
7449         toX = x;
7450         toY = y;
7451     }
7452
7453     saveAnimate = appData.animate;
7454     if (clickType == Press) {
7455         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7456             // must be Edit Position mode with empty-square selected
7457             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7458             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7459             return;
7460         }
7461         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7462             dragging = 1;
7463             return;
7464         }
7465         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7466             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7467         } else
7468         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7469         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7470           if(appData.sweepSelect) {
7471             ChessSquare piece = boards[currentMove][fromY][fromX];
7472             promoSweep = defaultPromoChoice;
7473             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7474             selectFlag = 0; lastX = xPix; lastY = yPix;
7475             Sweep(0); // Pawn that is going to promote: preview promotion piece
7476             sweepSelecting = 1;
7477             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7478             MarkTargetSquares(1);
7479           }
7480           return; // promo popup appears on up-click
7481         }
7482         /* Finish clickclick move */
7483         if (appData.animate || appData.highlightLastMove) {
7484             SetHighlights(fromX, fromY, toX, toY);
7485         } else {
7486             ClearHighlights();
7487         }
7488     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7489         sweepSelecting = 0;
7490         if (appData.animate || appData.highlightLastMove) {
7491             SetHighlights(fromX, fromY, toX, toY);
7492         } else {
7493             ClearHighlights();
7494         }
7495     } else {
7496 #if 0
7497 // [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
7498         /* Finish drag move */
7499         if (appData.highlightLastMove) {
7500             SetHighlights(fromX, fromY, toX, toY);
7501         } else {
7502             ClearHighlights();
7503         }
7504 #endif
7505         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7506           dragging *= 2;            // flag button-less dragging if we are dragging
7507           MarkTargetSquares(1);
7508           if(x == killX && y == killY) killX = killY = -1; else {
7509             killX = x; killY = y;     //remeber this square as intermediate
7510             MarkTargetSquares(0);
7511             ReportClick("put", x, y); // and inform engine
7512             ReportClick("lift", x, y);
7513             return;
7514           }
7515         }
7516         DragPieceEnd(xPix, yPix); dragging = 0;
7517         /* Don't animate move and drag both */
7518         appData.animate = FALSE;
7519     }
7520
7521     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7522     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7523         ChessSquare piece = boards[currentMove][fromY][fromX];
7524         if(gameMode == EditPosition && piece != EmptySquare &&
7525            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7526             int n;
7527
7528             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7529                 n = PieceToNumber(piece - (int)BlackPawn);
7530                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7531                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7532                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7533             } else
7534             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7535                 n = PieceToNumber(piece);
7536                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7537                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7538                 boards[currentMove][n][BOARD_WIDTH-2]++;
7539             }
7540             boards[currentMove][fromY][fromX] = EmptySquare;
7541         }
7542         ClearHighlights();
7543         fromX = fromY = -1;
7544         MarkTargetSquares(1);
7545         DrawPosition(TRUE, boards[currentMove]);
7546         return;
7547     }
7548
7549     // off-board moves should not be highlighted
7550     if(x < 0 || y < 0) ClearHighlights();
7551     else ReportClick("put", x, y);
7552
7553     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7554
7555     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7556         SetHighlights(fromX, fromY, toX, toY);
7557         MarkTargetSquares(1);
7558         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7559             // [HGM] super: promotion to captured piece selected from holdings
7560             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7561             promotionChoice = TRUE;
7562             // kludge follows to temporarily execute move on display, without promoting yet
7563             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7564             boards[currentMove][toY][toX] = p;
7565             DrawPosition(FALSE, boards[currentMove]);
7566             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7567             boards[currentMove][toY][toX] = q;
7568             DisplayMessage("Click in holdings to choose piece", "");
7569             return;
7570         }
7571         PromotionPopUp();
7572     } else {
7573         int oldMove = currentMove;
7574         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7575         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7576         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7577         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7578            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7579             DrawPosition(TRUE, boards[currentMove]);
7580         MarkTargetSquares(1);
7581         fromX = fromY = -1;
7582     }
7583     appData.animate = saveAnimate;
7584     if (appData.animate || appData.animateDragging) {
7585         /* Undo animation damage if needed */
7586         DrawPosition(FALSE, NULL);
7587     }
7588 }
7589
7590 int
7591 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7592 {   // front-end-free part taken out of PieceMenuPopup
7593     int whichMenu; int xSqr, ySqr;
7594
7595     if(seekGraphUp) { // [HGM] seekgraph
7596         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7597         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7598         return -2;
7599     }
7600
7601     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7602          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7603         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7604         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7605         if(action == Press)   {
7606             originalFlip = flipView;
7607             flipView = !flipView; // temporarily flip board to see game from partners perspective
7608             DrawPosition(TRUE, partnerBoard);
7609             DisplayMessage(partnerStatus, "");
7610             partnerUp = TRUE;
7611         } else if(action == Release) {
7612             flipView = originalFlip;
7613             DrawPosition(TRUE, boards[currentMove]);
7614             partnerUp = FALSE;
7615         }
7616         return -2;
7617     }
7618
7619     xSqr = EventToSquare(x, BOARD_WIDTH);
7620     ySqr = EventToSquare(y, BOARD_HEIGHT);
7621     if (action == Release) {
7622         if(pieceSweep != EmptySquare) {
7623             EditPositionMenuEvent(pieceSweep, toX, toY);
7624             pieceSweep = EmptySquare;
7625         } else UnLoadPV(); // [HGM] pv
7626     }
7627     if (action != Press) return -2; // return code to be ignored
7628     switch (gameMode) {
7629       case IcsExamining:
7630         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7631       case EditPosition:
7632         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7633         if (xSqr < 0 || ySqr < 0) return -1;
7634         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7635         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7636         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7637         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7638         NextPiece(0);
7639         return 2; // grab
7640       case IcsObserving:
7641         if(!appData.icsEngineAnalyze) return -1;
7642       case IcsPlayingWhite:
7643       case IcsPlayingBlack:
7644         if(!appData.zippyPlay) goto noZip;
7645       case AnalyzeMode:
7646       case AnalyzeFile:
7647       case MachinePlaysWhite:
7648       case MachinePlaysBlack:
7649       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7650         if (!appData.dropMenu) {
7651           LoadPV(x, y);
7652           return 2; // flag front-end to grab mouse events
7653         }
7654         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7655            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7656       case EditGame:
7657       noZip:
7658         if (xSqr < 0 || ySqr < 0) return -1;
7659         if (!appData.dropMenu || appData.testLegality &&
7660             gameInfo.variant != VariantBughouse &&
7661             gameInfo.variant != VariantCrazyhouse) return -1;
7662         whichMenu = 1; // drop menu
7663         break;
7664       default:
7665         return -1;
7666     }
7667
7668     if (((*fromX = xSqr) < 0) ||
7669         ((*fromY = ySqr) < 0)) {
7670         *fromX = *fromY = -1;
7671         return -1;
7672     }
7673     if (flipView)
7674       *fromX = BOARD_WIDTH - 1 - *fromX;
7675     else
7676       *fromY = BOARD_HEIGHT - 1 - *fromY;
7677
7678     return whichMenu;
7679 }
7680
7681 void
7682 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7683 {
7684 //    char * hint = lastHint;
7685     FrontEndProgramStats stats;
7686
7687     stats.which = cps == &first ? 0 : 1;
7688     stats.depth = cpstats->depth;
7689     stats.nodes = cpstats->nodes;
7690     stats.score = cpstats->score;
7691     stats.time = cpstats->time;
7692     stats.pv = cpstats->movelist;
7693     stats.hint = lastHint;
7694     stats.an_move_index = 0;
7695     stats.an_move_count = 0;
7696
7697     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7698         stats.hint = cpstats->move_name;
7699         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7700         stats.an_move_count = cpstats->nr_moves;
7701     }
7702
7703     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
7704
7705     SetProgramStats( &stats );
7706 }
7707
7708 void
7709 ClearEngineOutputPane (int which)
7710 {
7711     static FrontEndProgramStats dummyStats;
7712     dummyStats.which = which;
7713     dummyStats.pv = "#";
7714     SetProgramStats( &dummyStats );
7715 }
7716
7717 #define MAXPLAYERS 500
7718
7719 char *
7720 TourneyStandings (int display)
7721 {
7722     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7723     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7724     char result, *p, *names[MAXPLAYERS];
7725
7726     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7727         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7728     names[0] = p = strdup(appData.participants);
7729     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7730
7731     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7732
7733     while(result = appData.results[nr]) {
7734         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7735         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7736         wScore = bScore = 0;
7737         switch(result) {
7738           case '+': wScore = 2; break;
7739           case '-': bScore = 2; break;
7740           case '=': wScore = bScore = 1; break;
7741           case ' ':
7742           case '*': return strdup("busy"); // tourney not finished
7743         }
7744         score[w] += wScore;
7745         score[b] += bScore;
7746         games[w]++;
7747         games[b]++;
7748         nr++;
7749     }
7750     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7751     for(w=0; w<nPlayers; w++) {
7752         bScore = -1;
7753         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7754         ranking[w] = b; points[w] = bScore; score[b] = -2;
7755     }
7756     p = malloc(nPlayers*34+1);
7757     for(w=0; w<nPlayers && w<display; w++)
7758         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7759     free(names[0]);
7760     return p;
7761 }
7762
7763 void
7764 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7765 {       // count all piece types
7766         int p, f, r;
7767         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7768         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7769         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7770                 p = board[r][f];
7771                 pCnt[p]++;
7772                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7773                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7774                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7775                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7776                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7777                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7778         }
7779 }
7780
7781 int
7782 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7783 {
7784         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7785         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7786
7787         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7788         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7789         if(myPawns == 2 && nMine == 3) // KPP
7790             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7791         if(myPawns == 1 && nMine == 2) // KP
7792             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7793         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7794             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7795         if(myPawns) return FALSE;
7796         if(pCnt[WhiteRook+side])
7797             return pCnt[BlackRook-side] ||
7798                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7799                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7800                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7801         if(pCnt[WhiteCannon+side]) {
7802             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7803             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7804         }
7805         if(pCnt[WhiteKnight+side])
7806             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7807         return FALSE;
7808 }
7809
7810 int
7811 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7812 {
7813         VariantClass v = gameInfo.variant;
7814
7815         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7816         if(v == VariantShatranj) return TRUE; // always winnable through baring
7817         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7818         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7819
7820         if(v == VariantXiangqi) {
7821                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7822
7823                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7824                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7825                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7826                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7827                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7828                 if(stale) // we have at least one last-rank P plus perhaps C
7829                     return majors // KPKX
7830                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7831                 else // KCA*E*
7832                     return pCnt[WhiteFerz+side] // KCAK
7833                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7834                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7835                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7836
7837         } else if(v == VariantKnightmate) {
7838                 if(nMine == 1) return FALSE;
7839                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7840         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7841                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7842
7843                 if(nMine == 1) return FALSE; // bare King
7844                 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
7845                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7846                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7847                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7848                 if(pCnt[WhiteKnight+side])
7849                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7850                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7851                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7852                 if(nBishops)
7853                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7854                 if(pCnt[WhiteAlfil+side])
7855                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7856                 if(pCnt[WhiteWazir+side])
7857                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7858         }
7859
7860         return TRUE;
7861 }
7862
7863 int
7864 CompareWithRights (Board b1, Board b2)
7865 {
7866     int rights = 0;
7867     if(!CompareBoards(b1, b2)) return FALSE;
7868     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7869     /* compare castling rights */
7870     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7871            rights++; /* King lost rights, while rook still had them */
7872     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7873         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7874            rights++; /* but at least one rook lost them */
7875     }
7876     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7877            rights++;
7878     if( b1[CASTLING][5] != NoRights ) {
7879         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7880            rights++;
7881     }
7882     return rights == 0;
7883 }
7884
7885 int
7886 Adjudicate (ChessProgramState *cps)
7887 {       // [HGM] some adjudications useful with buggy engines
7888         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7889         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7890         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7891         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7892         int k, drop, count = 0; static int bare = 1;
7893         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7894         Boolean canAdjudicate = !appData.icsActive;
7895
7896         // most tests only when we understand the game, i.e. legality-checking on
7897             if( appData.testLegality )
7898             {   /* [HGM] Some more adjudications for obstinate engines */
7899                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7900                 static int moveCount = 6;
7901                 ChessMove result;
7902                 char *reason = NULL;
7903
7904                 /* Count what is on board. */
7905                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7906
7907                 /* Some material-based adjudications that have to be made before stalemate test */
7908                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7909                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7910                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7911                      if(canAdjudicate && appData.checkMates) {
7912                          if(engineOpponent)
7913                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7914                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7915                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7916                          return 1;
7917                      }
7918                 }
7919
7920                 /* Bare King in Shatranj (loses) or Losers (wins) */
7921                 if( nrW == 1 || nrB == 1) {
7922                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7923                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7924                      if(canAdjudicate && appData.checkMates) {
7925                          if(engineOpponent)
7926                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7927                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7928                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7929                          return 1;
7930                      }
7931                   } else
7932                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7933                   {    /* bare King */
7934                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7935                         if(canAdjudicate && appData.checkMates) {
7936                             /* but only adjudicate if adjudication enabled */
7937                             if(engineOpponent)
7938                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7939                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7940                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7941                             return 1;
7942                         }
7943                   }
7944                 } else bare = 1;
7945
7946
7947             // don't wait for engine to announce game end if we can judge ourselves
7948             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7949               case MT_CHECK:
7950                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7951                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7952                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7953                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7954                             checkCnt++;
7955                         if(checkCnt >= 2) {
7956                             reason = "Xboard adjudication: 3rd check";
7957                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7958                             break;
7959                         }
7960                     }
7961                 }
7962               case MT_NONE:
7963               default:
7964                 break;
7965               case MT_STALEMATE:
7966               case MT_STAINMATE:
7967                 reason = "Xboard adjudication: Stalemate";
7968                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7969                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7970                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7971                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7972                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7973                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7974                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7975                                                                         EP_CHECKMATE : EP_WINS);
7976                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7977                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7978                 }
7979                 break;
7980               case MT_CHECKMATE:
7981                 reason = "Xboard adjudication: Checkmate";
7982                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7983                 if(gameInfo.variant == VariantShogi) {
7984                     if(forwardMostMove > backwardMostMove
7985                        && moveList[forwardMostMove-1][1] == '@'
7986                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7987                         reason = "XBoard adjudication: pawn-drop mate";
7988                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7989                     }
7990                 }
7991                 break;
7992             }
7993
7994                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7995                     case EP_STALEMATE:
7996                         result = GameIsDrawn; break;
7997                     case EP_CHECKMATE:
7998                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7999                     case EP_WINS:
8000                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8001                     default:
8002                         result = EndOfFile;
8003                 }
8004                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8005                     if(engineOpponent)
8006                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8007                     GameEnds( result, reason, GE_XBOARD );
8008                     return 1;
8009                 }
8010
8011                 /* Next absolutely insufficient mating material. */
8012                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8013                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8014                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8015
8016                      /* always flag draws, for judging claims */
8017                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8018
8019                      if(canAdjudicate && appData.materialDraws) {
8020                          /* but only adjudicate them if adjudication enabled */
8021                          if(engineOpponent) {
8022                            SendToProgram("force\n", engineOpponent); // suppress reply
8023                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8024                          }
8025                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8026                          return 1;
8027                      }
8028                 }
8029
8030                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8031                 if(gameInfo.variant == VariantXiangqi ?
8032                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8033                  : nrW + nrB == 4 &&
8034                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8035                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8036                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8037                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8038                    ) ) {
8039                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8040                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8041                           if(engineOpponent) {
8042                             SendToProgram("force\n", engineOpponent); // suppress reply
8043                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8044                           }
8045                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8046                           return 1;
8047                      }
8048                 } else moveCount = 6;
8049             }
8050
8051         // Repetition draws and 50-move rule can be applied independently of legality testing
8052
8053                 /* Check for rep-draws */
8054                 count = 0;
8055                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8056                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8057                 for(k = forwardMostMove-2;
8058                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8059                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8060                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8061                     k-=2)
8062                 {   int rights=0;
8063                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8064                         /* compare castling rights */
8065                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8066                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8067                                 rights++; /* King lost rights, while rook still had them */
8068                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8069                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8070                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8071                                    rights++; /* but at least one rook lost them */
8072                         }
8073                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8074                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8075                                 rights++;
8076                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8077                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8078                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8079                                    rights++;
8080                         }
8081                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8082                             && appData.drawRepeats > 1) {
8083                              /* adjudicate after user-specified nr of repeats */
8084                              int result = GameIsDrawn;
8085                              char *details = "XBoard adjudication: repetition draw";
8086                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8087                                 // [HGM] xiangqi: check for forbidden perpetuals
8088                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8089                                 for(m=forwardMostMove; m>k; m-=2) {
8090                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8091                                         ourPerpetual = 0; // the current mover did not always check
8092                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8093                                         hisPerpetual = 0; // the opponent did not always check
8094                                 }
8095                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8096                                                                         ourPerpetual, hisPerpetual);
8097                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8098                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8099                                     details = "Xboard adjudication: perpetual checking";
8100                                 } else
8101                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8102                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8103                                 } else
8104                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8105                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8106                                         result = BlackWins;
8107                                         details = "Xboard adjudication: repetition";
8108                                     }
8109                                 } else // it must be XQ
8110                                 // Now check for perpetual chases
8111                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8112                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8113                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8114                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8115                                         static char resdet[MSG_SIZ];
8116                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8117                                         details = resdet;
8118                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8119                                     } else
8120                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8121                                         break; // Abort repetition-checking loop.
8122                                 }
8123                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8124                              }
8125                              if(engineOpponent) {
8126                                SendToProgram("force\n", engineOpponent); // suppress reply
8127                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8128                              }
8129                              GameEnds( result, details, GE_XBOARD );
8130                              return 1;
8131                         }
8132                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8133                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8134                     }
8135                 }
8136
8137                 /* Now we test for 50-move draws. Determine ply count */
8138                 count = forwardMostMove;
8139                 /* look for last irreversble move */
8140                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8141                     count--;
8142                 /* if we hit starting position, add initial plies */
8143                 if( count == backwardMostMove )
8144                     count -= initialRulePlies;
8145                 count = forwardMostMove - count;
8146                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8147                         // adjust reversible move counter for checks in Xiangqi
8148                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8149                         if(i < backwardMostMove) i = backwardMostMove;
8150                         while(i <= forwardMostMove) {
8151                                 lastCheck = inCheck; // check evasion does not count
8152                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8153                                 if(inCheck || lastCheck) count--; // check does not count
8154                                 i++;
8155                         }
8156                 }
8157                 if( count >= 100)
8158                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8159                          /* this is used to judge if draw claims are legal */
8160                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8161                          if(engineOpponent) {
8162                            SendToProgram("force\n", engineOpponent); // suppress reply
8163                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8164                          }
8165                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8166                          return 1;
8167                 }
8168
8169                 /* if draw offer is pending, treat it as a draw claim
8170                  * when draw condition present, to allow engines a way to
8171                  * claim draws before making their move to avoid a race
8172                  * condition occurring after their move
8173                  */
8174                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8175                          char *p = NULL;
8176                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8177                              p = "Draw claim: 50-move rule";
8178                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8179                              p = "Draw claim: 3-fold repetition";
8180                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8181                              p = "Draw claim: insufficient mating material";
8182                          if( p != NULL && canAdjudicate) {
8183                              if(engineOpponent) {
8184                                SendToProgram("force\n", engineOpponent); // suppress reply
8185                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8186                              }
8187                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8188                              return 1;
8189                          }
8190                 }
8191
8192                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8193                     if(engineOpponent) {
8194                       SendToProgram("force\n", engineOpponent); // suppress reply
8195                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8196                     }
8197                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8198                     return 1;
8199                 }
8200         return 0;
8201 }
8202
8203 char *
8204 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8205 {   // [HGM] book: this routine intercepts moves to simulate book replies
8206     char *bookHit = NULL;
8207
8208     //first determine if the incoming move brings opponent into his book
8209     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8210         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8211     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8212     if(bookHit != NULL && !cps->bookSuspend) {
8213         // make sure opponent is not going to reply after receiving move to book position
8214         SendToProgram("force\n", cps);
8215         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8216     }
8217     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8218     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8219     // now arrange restart after book miss
8220     if(bookHit) {
8221         // after a book hit we never send 'go', and the code after the call to this routine
8222         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8223         char buf[MSG_SIZ], *move = bookHit;
8224         if(cps->useSAN) {
8225             int fromX, fromY, toX, toY;
8226             char promoChar;
8227             ChessMove moveType;
8228             move = buf + 30;
8229             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8230                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8231                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8232                                     PosFlags(forwardMostMove),
8233                                     fromY, fromX, toY, toX, promoChar, move);
8234             } else {
8235                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8236                 bookHit = NULL;
8237             }
8238         }
8239         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8240         SendToProgram(buf, cps);
8241         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8242     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8243         SendToProgram("go\n", cps);
8244         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8245     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8246         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8247             SendToProgram("go\n", cps);
8248         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8249     }
8250     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8251 }
8252
8253 int
8254 LoadError (char *errmess, ChessProgramState *cps)
8255 {   // unloads engine and switches back to -ncp mode if it was first
8256     if(cps->initDone) return FALSE;
8257     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8258     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8259     cps->pr = NoProc;
8260     if(cps == &first) {
8261         appData.noChessProgram = TRUE;
8262         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8263         gameMode = BeginningOfGame; ModeHighlight();
8264         SetNCPMode();
8265     }
8266     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8267     DisplayMessage("", ""); // erase waiting message
8268     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8269     return TRUE;
8270 }
8271
8272 char *savedMessage;
8273 ChessProgramState *savedState;
8274 void
8275 DeferredBookMove (void)
8276 {
8277         if(savedState->lastPing != savedState->lastPong)
8278                     ScheduleDelayedEvent(DeferredBookMove, 10);
8279         else
8280         HandleMachineMove(savedMessage, savedState);
8281 }
8282
8283 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8284 static ChessProgramState *stalledEngine;
8285 static char stashedInputMove[MSG_SIZ];
8286
8287 void
8288 HandleMachineMove (char *message, ChessProgramState *cps)
8289 {
8290     static char firstLeg[20];
8291     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8292     char realname[MSG_SIZ];
8293     int fromX, fromY, toX, toY;
8294     ChessMove moveType;
8295     char promoChar, roar;
8296     char *p, *pv=buf1;
8297     int machineWhite, oldError;
8298     char *bookHit;
8299
8300     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8301         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8302         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8303             DisplayError(_("Invalid pairing from pairing engine"), 0);
8304             return;
8305         }
8306         pairingReceived = 1;
8307         NextMatchGame();
8308         return; // Skim the pairing messages here.
8309     }
8310
8311     oldError = cps->userError; cps->userError = 0;
8312
8313 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8314     /*
8315      * Kludge to ignore BEL characters
8316      */
8317     while (*message == '\007') message++;
8318
8319     /*
8320      * [HGM] engine debug message: ignore lines starting with '#' character
8321      */
8322     if(cps->debug && *message == '#') return;
8323
8324     /*
8325      * Look for book output
8326      */
8327     if (cps == &first && bookRequested) {
8328         if (message[0] == '\t' || message[0] == ' ') {
8329             /* Part of the book output is here; append it */
8330             strcat(bookOutput, message);
8331             strcat(bookOutput, "  \n");
8332             return;
8333         } else if (bookOutput[0] != NULLCHAR) {
8334             /* All of book output has arrived; display it */
8335             char *p = bookOutput;
8336             while (*p != NULLCHAR) {
8337                 if (*p == '\t') *p = ' ';
8338                 p++;
8339             }
8340             DisplayInformation(bookOutput);
8341             bookRequested = FALSE;
8342             /* Fall through to parse the current output */
8343         }
8344     }
8345
8346     /*
8347      * Look for machine move.
8348      */
8349     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8350         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8351     {
8352         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8353             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8354             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8355             stalledEngine = cps;
8356             if(appData.ponderNextMove) { // bring opponent out of ponder
8357                 if(gameMode == TwoMachinesPlay) {
8358                     if(cps->other->pause)
8359                         PauseEngine(cps->other);
8360                     else
8361                         SendToProgram("easy\n", cps->other);
8362                 }
8363             }
8364             StopClocks();
8365             return;
8366         }
8367
8368         /* This method is only useful on engines that support ping */
8369         if (cps->lastPing != cps->lastPong) {
8370           if (gameMode == BeginningOfGame) {
8371             /* Extra move from before last new; ignore */
8372             if (appData.debugMode) {
8373                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8374             }
8375           } else {
8376             if (appData.debugMode) {
8377                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8378                         cps->which, gameMode);
8379             }
8380
8381             SendToProgram("undo\n", cps);
8382           }
8383           return;
8384         }
8385
8386         switch (gameMode) {
8387           case BeginningOfGame:
8388             /* Extra move from before last reset; ignore */
8389             if (appData.debugMode) {
8390                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8391             }
8392             return;
8393
8394           case EndOfGame:
8395           case IcsIdle:
8396           default:
8397             /* Extra move after we tried to stop.  The mode test is
8398                not a reliable way of detecting this problem, but it's
8399                the best we can do on engines that don't support ping.
8400             */
8401             if (appData.debugMode) {
8402                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8403                         cps->which, gameMode);
8404             }
8405             SendToProgram("undo\n", cps);
8406             return;
8407
8408           case MachinePlaysWhite:
8409           case IcsPlayingWhite:
8410             machineWhite = TRUE;
8411             break;
8412
8413           case MachinePlaysBlack:
8414           case IcsPlayingBlack:
8415             machineWhite = FALSE;
8416             break;
8417
8418           case TwoMachinesPlay:
8419             machineWhite = (cps->twoMachinesColor[0] == 'w');
8420             break;
8421         }
8422         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8423             if (appData.debugMode) {
8424                 fprintf(debugFP,
8425                         "Ignoring move out of turn by %s, gameMode %d"
8426                         ", forwardMost %d\n",
8427                         cps->which, gameMode, forwardMostMove);
8428             }
8429             return;
8430         }
8431
8432         if(cps->alphaRank) AlphaRank(machineMove, 4);
8433
8434         // [HGM] lion: (some very limited) support for Alien protocol
8435         killX = killY = -1;
8436         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8437             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8438             return;
8439         } else if(firstLeg[0]) { // there was a previous leg;
8440             // only support case where same piece makes two step (and don't even test that!)
8441             char buf[20], *p = machineMove+1, *q = buf+1, f;
8442             safeStrCpy(buf, machineMove, 20);
8443             while(isdigit(*q)) q++; // find start of to-square
8444             safeStrCpy(machineMove, firstLeg, 20);
8445             while(isdigit(*p)) p++;
8446             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8447             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8448             firstLeg[0] = NULLCHAR;
8449         }
8450
8451         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8452                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8453             /* Machine move could not be parsed; ignore it. */
8454           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8455                     machineMove, _(cps->which));
8456             DisplayMoveError(buf1);
8457             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8458                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8459             if (gameMode == TwoMachinesPlay) {
8460               GameEnds(machineWhite ? BlackWins : WhiteWins,
8461                        buf1, GE_XBOARD);
8462             }
8463             return;
8464         }
8465
8466         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8467         /* So we have to redo legality test with true e.p. status here,  */
8468         /* to make sure an illegal e.p. capture does not slip through,   */
8469         /* to cause a forfeit on a justified illegal-move complaint      */
8470         /* of the opponent.                                              */
8471         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8472            ChessMove moveType;
8473            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8474                              fromY, fromX, toY, toX, promoChar);
8475             if(moveType == IllegalMove) {
8476               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8477                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8478                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8479                            buf1, GE_XBOARD);
8480                 return;
8481            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8482            /* [HGM] Kludge to handle engines that send FRC-style castling
8483               when they shouldn't (like TSCP-Gothic) */
8484            switch(moveType) {
8485              case WhiteASideCastleFR:
8486              case BlackASideCastleFR:
8487                toX+=2;
8488                currentMoveString[2]++;
8489                break;
8490              case WhiteHSideCastleFR:
8491              case BlackHSideCastleFR:
8492                toX--;
8493                currentMoveString[2]--;
8494                break;
8495              default: ; // nothing to do, but suppresses warning of pedantic compilers
8496            }
8497         }
8498         hintRequested = FALSE;
8499         lastHint[0] = NULLCHAR;
8500         bookRequested = FALSE;
8501         /* Program may be pondering now */
8502         cps->maybeThinking = TRUE;
8503         if (cps->sendTime == 2) cps->sendTime = 1;
8504         if (cps->offeredDraw) cps->offeredDraw--;
8505
8506         /* [AS] Save move info*/
8507         pvInfoList[ forwardMostMove ].score = programStats.score;
8508         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8509         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8510
8511         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8512
8513         /* Test suites abort the 'game' after one move */
8514         if(*appData.finger) {
8515            static FILE *f;
8516            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8517            if(!f) f = fopen(appData.finger, "w");
8518            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8519            else { DisplayFatalError("Bad output file", errno, 0); return; }
8520            free(fen);
8521            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8522         }
8523
8524         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8525         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8526             int count = 0;
8527
8528             while( count < adjudicateLossPlies ) {
8529                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8530
8531                 if( count & 1 ) {
8532                     score = -score; /* Flip score for winning side */
8533                 }
8534
8535                 if( score > adjudicateLossThreshold ) {
8536                     break;
8537                 }
8538
8539                 count++;
8540             }
8541
8542             if( count >= adjudicateLossPlies ) {
8543                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8544
8545                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8546                     "Xboard adjudication",
8547                     GE_XBOARD );
8548
8549                 return;
8550             }
8551         }
8552
8553         if(Adjudicate(cps)) {
8554             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8555             return; // [HGM] adjudicate: for all automatic game ends
8556         }
8557
8558 #if ZIPPY
8559         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8560             first.initDone) {
8561           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8562                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8563                 SendToICS("draw ");
8564                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8565           }
8566           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8567           ics_user_moved = 1;
8568           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8569                 char buf[3*MSG_SIZ];
8570
8571                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8572                         programStats.score / 100.,
8573                         programStats.depth,
8574                         programStats.time / 100.,
8575                         (unsigned int)programStats.nodes,
8576                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8577                         programStats.movelist);
8578                 SendToICS(buf);
8579 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8580           }
8581         }
8582 #endif
8583
8584         /* [AS] Clear stats for next move */
8585         ClearProgramStats();
8586         thinkOutput[0] = NULLCHAR;
8587         hiddenThinkOutputState = 0;
8588
8589         bookHit = NULL;
8590         if (gameMode == TwoMachinesPlay) {
8591             /* [HGM] relaying draw offers moved to after reception of move */
8592             /* and interpreting offer as claim if it brings draw condition */
8593             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8594                 SendToProgram("draw\n", cps->other);
8595             }
8596             if (cps->other->sendTime) {
8597                 SendTimeRemaining(cps->other,
8598                                   cps->other->twoMachinesColor[0] == 'w');
8599             }
8600             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8601             if (firstMove && !bookHit) {
8602                 firstMove = FALSE;
8603                 if (cps->other->useColors) {
8604                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8605                 }
8606                 SendToProgram("go\n", cps->other);
8607             }
8608             cps->other->maybeThinking = TRUE;
8609         }
8610
8611         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8612
8613         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8614
8615         if (!pausing && appData.ringBellAfterMoves) {
8616             if(!roar) RingBell();
8617         }
8618
8619         /*
8620          * Reenable menu items that were disabled while
8621          * machine was thinking
8622          */
8623         if (gameMode != TwoMachinesPlay)
8624             SetUserThinkingEnables();
8625
8626         // [HGM] book: after book hit opponent has received move and is now in force mode
8627         // force the book reply into it, and then fake that it outputted this move by jumping
8628         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8629         if(bookHit) {
8630                 static char bookMove[MSG_SIZ]; // a bit generous?
8631
8632                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8633                 strcat(bookMove, bookHit);
8634                 message = bookMove;
8635                 cps = cps->other;
8636                 programStats.nodes = programStats.depth = programStats.time =
8637                 programStats.score = programStats.got_only_move = 0;
8638                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8639
8640                 if(cps->lastPing != cps->lastPong) {
8641                     savedMessage = message; // args for deferred call
8642                     savedState = cps;
8643                     ScheduleDelayedEvent(DeferredBookMove, 10);
8644                     return;
8645                 }
8646                 goto FakeBookMove;
8647         }
8648
8649         return;
8650     }
8651
8652     /* Set special modes for chess engines.  Later something general
8653      *  could be added here; for now there is just one kludge feature,
8654      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8655      *  when "xboard" is given as an interactive command.
8656      */
8657     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8658         cps->useSigint = FALSE;
8659         cps->useSigterm = FALSE;
8660     }
8661     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8662       ParseFeatures(message+8, cps);
8663       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8664     }
8665
8666     if (!strncmp(message, "setup ", 6) && 
8667         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8668                                         ) { // [HGM] allow first engine to define opening position
8669       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8670       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8671       *buf = NULLCHAR;
8672       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8673       if(startedFromSetupPosition) return;
8674       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8675       if(dummy >= 3) {
8676         while(message[s] && message[s++] != ' ');
8677         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8678            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8679             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8680             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8681           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8682           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8683         }
8684       }
8685       ParseFEN(boards[0], &dummy, message+s, FALSE);
8686       DrawPosition(TRUE, boards[0]);
8687       startedFromSetupPosition = TRUE;
8688       return;
8689     }
8690     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8691      * want this, I was asked to put it in, and obliged.
8692      */
8693     if (!strncmp(message, "setboard ", 9)) {
8694         Board initial_position;
8695
8696         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8697
8698         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8699             DisplayError(_("Bad FEN received from engine"), 0);
8700             return ;
8701         } else {
8702            Reset(TRUE, FALSE);
8703            CopyBoard(boards[0], initial_position);
8704            initialRulePlies = FENrulePlies;
8705            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8706            else gameMode = MachinePlaysBlack;
8707            DrawPosition(FALSE, boards[currentMove]);
8708         }
8709         return;
8710     }
8711
8712     /*
8713      * Look for communication commands
8714      */
8715     if (!strncmp(message, "telluser ", 9)) {
8716         if(message[9] == '\\' && message[10] == '\\')
8717             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8718         PlayTellSound();
8719         DisplayNote(message + 9);
8720         return;
8721     }
8722     if (!strncmp(message, "tellusererror ", 14)) {
8723         cps->userError = 1;
8724         if(message[14] == '\\' && message[15] == '\\')
8725             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8726         PlayTellSound();
8727         DisplayError(message + 14, 0);
8728         return;
8729     }
8730     if (!strncmp(message, "tellopponent ", 13)) {
8731       if (appData.icsActive) {
8732         if (loggedOn) {
8733           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8734           SendToICS(buf1);
8735         }
8736       } else {
8737         DisplayNote(message + 13);
8738       }
8739       return;
8740     }
8741     if (!strncmp(message, "tellothers ", 11)) {
8742       if (appData.icsActive) {
8743         if (loggedOn) {
8744           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8745           SendToICS(buf1);
8746         }
8747       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8748       return;
8749     }
8750     if (!strncmp(message, "tellall ", 8)) {
8751       if (appData.icsActive) {
8752         if (loggedOn) {
8753           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8754           SendToICS(buf1);
8755         }
8756       } else {
8757         DisplayNote(message + 8);
8758       }
8759       return;
8760     }
8761     if (strncmp(message, "warning", 7) == 0) {
8762         /* Undocumented feature, use tellusererror in new code */
8763         DisplayError(message, 0);
8764         return;
8765     }
8766     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8767         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8768         strcat(realname, " query");
8769         AskQuestion(realname, buf2, buf1, cps->pr);
8770         return;
8771     }
8772     /* Commands from the engine directly to ICS.  We don't allow these to be
8773      *  sent until we are logged on. Crafty kibitzes have been known to
8774      *  interfere with the login process.
8775      */
8776     if (loggedOn) {
8777         if (!strncmp(message, "tellics ", 8)) {
8778             SendToICS(message + 8);
8779             SendToICS("\n");
8780             return;
8781         }
8782         if (!strncmp(message, "tellicsnoalias ", 15)) {
8783             SendToICS(ics_prefix);
8784             SendToICS(message + 15);
8785             SendToICS("\n");
8786             return;
8787         }
8788         /* The following are for backward compatibility only */
8789         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8790             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8791             SendToICS(ics_prefix);
8792             SendToICS(message);
8793             SendToICS("\n");
8794             return;
8795         }
8796     }
8797     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8798         return;
8799     }
8800     if(!strncmp(message, "highlight ", 10)) {
8801         if(appData.testLegality && appData.markers) return;
8802         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8803         return;
8804     }
8805     if(!strncmp(message, "click ", 6)) {
8806         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8807         if(appData.testLegality || !appData.oneClick) return;
8808         sscanf(message+6, "%c%d%c", &f, &y, &c);
8809         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8810         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8811         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8812         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8813         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8814         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8815             LeftClick(Release, lastLeftX, lastLeftY);
8816         controlKey  = (c == ',');
8817         LeftClick(Press, x, y);
8818         LeftClick(Release, x, y);
8819         first.highlight = f;
8820         return;
8821     }
8822     /*
8823      * If the move is illegal, cancel it and redraw the board.
8824      * Also deal with other error cases.  Matching is rather loose
8825      * here to accommodate engines written before the spec.
8826      */
8827     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8828         strncmp(message, "Error", 5) == 0) {
8829         if (StrStr(message, "name") ||
8830             StrStr(message, "rating") || StrStr(message, "?") ||
8831             StrStr(message, "result") || StrStr(message, "board") ||
8832             StrStr(message, "bk") || StrStr(message, "computer") ||
8833             StrStr(message, "variant") || StrStr(message, "hint") ||
8834             StrStr(message, "random") || StrStr(message, "depth") ||
8835             StrStr(message, "accepted")) {
8836             return;
8837         }
8838         if (StrStr(message, "protover")) {
8839           /* Program is responding to input, so it's apparently done
8840              initializing, and this error message indicates it is
8841              protocol version 1.  So we don't need to wait any longer
8842              for it to initialize and send feature commands. */
8843           FeatureDone(cps, 1);
8844           cps->protocolVersion = 1;
8845           return;
8846         }
8847         cps->maybeThinking = FALSE;
8848
8849         if (StrStr(message, "draw")) {
8850             /* Program doesn't have "draw" command */
8851             cps->sendDrawOffers = 0;
8852             return;
8853         }
8854         if (cps->sendTime != 1 &&
8855             (StrStr(message, "time") || StrStr(message, "otim"))) {
8856           /* Program apparently doesn't have "time" or "otim" command */
8857           cps->sendTime = 0;
8858           return;
8859         }
8860         if (StrStr(message, "analyze")) {
8861             cps->analysisSupport = FALSE;
8862             cps->analyzing = FALSE;
8863 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8864             EditGameEvent(); // [HGM] try to preserve loaded game
8865             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8866             DisplayError(buf2, 0);
8867             return;
8868         }
8869         if (StrStr(message, "(no matching move)st")) {
8870           /* Special kludge for GNU Chess 4 only */
8871           cps->stKludge = TRUE;
8872           SendTimeControl(cps, movesPerSession, timeControl,
8873                           timeIncrement, appData.searchDepth,
8874                           searchTime);
8875           return;
8876         }
8877         if (StrStr(message, "(no matching move)sd")) {
8878           /* Special kludge for GNU Chess 4 only */
8879           cps->sdKludge = TRUE;
8880           SendTimeControl(cps, movesPerSession, timeControl,
8881                           timeIncrement, appData.searchDepth,
8882                           searchTime);
8883           return;
8884         }
8885         if (!StrStr(message, "llegal")) {
8886             return;
8887         }
8888         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8889             gameMode == IcsIdle) return;
8890         if (forwardMostMove <= backwardMostMove) return;
8891         if (pausing) PauseEvent();
8892       if(appData.forceIllegal) {
8893             // [HGM] illegal: machine refused move; force position after move into it
8894           SendToProgram("force\n", cps);
8895           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8896                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8897                 // when black is to move, while there might be nothing on a2 or black
8898                 // might already have the move. So send the board as if white has the move.
8899                 // But first we must change the stm of the engine, as it refused the last move
8900                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8901                 if(WhiteOnMove(forwardMostMove)) {
8902                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8903                     SendBoard(cps, forwardMostMove); // kludgeless board
8904                 } else {
8905                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8906                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8907                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8908                 }
8909           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8910             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8911                  gameMode == TwoMachinesPlay)
8912               SendToProgram("go\n", cps);
8913             return;
8914       } else
8915         if (gameMode == PlayFromGameFile) {
8916             /* Stop reading this game file */
8917             gameMode = EditGame;
8918             ModeHighlight();
8919         }
8920         /* [HGM] illegal-move claim should forfeit game when Xboard */
8921         /* only passes fully legal moves                            */
8922         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8923             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8924                                 "False illegal-move claim", GE_XBOARD );
8925             return; // do not take back move we tested as valid
8926         }
8927         currentMove = forwardMostMove-1;
8928         DisplayMove(currentMove-1); /* before DisplayMoveError */
8929         SwitchClocks(forwardMostMove-1); // [HGM] race
8930         DisplayBothClocks();
8931         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8932                 parseList[currentMove], _(cps->which));
8933         DisplayMoveError(buf1);
8934         DrawPosition(FALSE, boards[currentMove]);
8935
8936         SetUserThinkingEnables();
8937         return;
8938     }
8939     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8940         /* Program has a broken "time" command that
8941            outputs a string not ending in newline.
8942            Don't use it. */
8943         cps->sendTime = 0;
8944     }
8945
8946     /*
8947      * If chess program startup fails, exit with an error message.
8948      * Attempts to recover here are futile. [HGM] Well, we try anyway
8949      */
8950     if ((StrStr(message, "unknown host") != NULL)
8951         || (StrStr(message, "No remote directory") != NULL)
8952         || (StrStr(message, "not found") != NULL)
8953         || (StrStr(message, "No such file") != NULL)
8954         || (StrStr(message, "can't alloc") != NULL)
8955         || (StrStr(message, "Permission denied") != NULL)) {
8956
8957         cps->maybeThinking = FALSE;
8958         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8959                 _(cps->which), cps->program, cps->host, message);
8960         RemoveInputSource(cps->isr);
8961         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8962             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8963             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8964         }
8965         return;
8966     }
8967
8968     /*
8969      * Look for hint output
8970      */
8971     if (sscanf(message, "Hint: %s", buf1) == 1) {
8972         if (cps == &first && hintRequested) {
8973             hintRequested = FALSE;
8974             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8975                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8976                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8977                                     PosFlags(forwardMostMove),
8978                                     fromY, fromX, toY, toX, promoChar, buf1);
8979                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8980                 DisplayInformation(buf2);
8981             } else {
8982                 /* Hint move could not be parsed!? */
8983               snprintf(buf2, sizeof(buf2),
8984                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8985                         buf1, _(cps->which));
8986                 DisplayError(buf2, 0);
8987             }
8988         } else {
8989           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8990         }
8991         return;
8992     }
8993
8994     /*
8995      * Ignore other messages if game is not in progress
8996      */
8997     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8998         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8999
9000     /*
9001      * look for win, lose, draw, or draw offer
9002      */
9003     if (strncmp(message, "1-0", 3) == 0) {
9004         char *p, *q, *r = "";
9005         p = strchr(message, '{');
9006         if (p) {
9007             q = strchr(p, '}');
9008             if (q) {
9009                 *q = NULLCHAR;
9010                 r = p + 1;
9011             }
9012         }
9013         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9014         return;
9015     } else if (strncmp(message, "0-1", 3) == 0) {
9016         char *p, *q, *r = "";
9017         p = strchr(message, '{');
9018         if (p) {
9019             q = strchr(p, '}');
9020             if (q) {
9021                 *q = NULLCHAR;
9022                 r = p + 1;
9023             }
9024         }
9025         /* Kludge for Arasan 4.1 bug */
9026         if (strcmp(r, "Black resigns") == 0) {
9027             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9028             return;
9029         }
9030         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9031         return;
9032     } else if (strncmp(message, "1/2", 3) == 0) {
9033         char *p, *q, *r = "";
9034         p = strchr(message, '{');
9035         if (p) {
9036             q = strchr(p, '}');
9037             if (q) {
9038                 *q = NULLCHAR;
9039                 r = p + 1;
9040             }
9041         }
9042
9043         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9044         return;
9045
9046     } else if (strncmp(message, "White resign", 12) == 0) {
9047         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9048         return;
9049     } else if (strncmp(message, "Black resign", 12) == 0) {
9050         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9051         return;
9052     } else if (strncmp(message, "White matches", 13) == 0 ||
9053                strncmp(message, "Black matches", 13) == 0   ) {
9054         /* [HGM] ignore GNUShogi noises */
9055         return;
9056     } else if (strncmp(message, "White", 5) == 0 &&
9057                message[5] != '(' &&
9058                StrStr(message, "Black") == NULL) {
9059         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9060         return;
9061     } else if (strncmp(message, "Black", 5) == 0 &&
9062                message[5] != '(') {
9063         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9064         return;
9065     } else if (strcmp(message, "resign") == 0 ||
9066                strcmp(message, "computer resigns") == 0) {
9067         switch (gameMode) {
9068           case MachinePlaysBlack:
9069           case IcsPlayingBlack:
9070             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9071             break;
9072           case MachinePlaysWhite:
9073           case IcsPlayingWhite:
9074             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9075             break;
9076           case TwoMachinesPlay:
9077             if (cps->twoMachinesColor[0] == 'w')
9078               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9079             else
9080               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9081             break;
9082           default:
9083             /* can't happen */
9084             break;
9085         }
9086         return;
9087     } else if (strncmp(message, "opponent mates", 14) == 0) {
9088         switch (gameMode) {
9089           case MachinePlaysBlack:
9090           case IcsPlayingBlack:
9091             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9092             break;
9093           case MachinePlaysWhite:
9094           case IcsPlayingWhite:
9095             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9096             break;
9097           case TwoMachinesPlay:
9098             if (cps->twoMachinesColor[0] == 'w')
9099               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9100             else
9101               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9102             break;
9103           default:
9104             /* can't happen */
9105             break;
9106         }
9107         return;
9108     } else if (strncmp(message, "computer mates", 14) == 0) {
9109         switch (gameMode) {
9110           case MachinePlaysBlack:
9111           case IcsPlayingBlack:
9112             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9113             break;
9114           case MachinePlaysWhite:
9115           case IcsPlayingWhite:
9116             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9117             break;
9118           case TwoMachinesPlay:
9119             if (cps->twoMachinesColor[0] == 'w')
9120               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9121             else
9122               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9123             break;
9124           default:
9125             /* can't happen */
9126             break;
9127         }
9128         return;
9129     } else if (strncmp(message, "checkmate", 9) == 0) {
9130         if (WhiteOnMove(forwardMostMove)) {
9131             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9132         } else {
9133             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9134         }
9135         return;
9136     } else if (strstr(message, "Draw") != NULL ||
9137                strstr(message, "game is a draw") != NULL) {
9138         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9139         return;
9140     } else if (strstr(message, "offer") != NULL &&
9141                strstr(message, "draw") != NULL) {
9142 #if ZIPPY
9143         if (appData.zippyPlay && first.initDone) {
9144             /* Relay offer to ICS */
9145             SendToICS(ics_prefix);
9146             SendToICS("draw\n");
9147         }
9148 #endif
9149         cps->offeredDraw = 2; /* valid until this engine moves twice */
9150         if (gameMode == TwoMachinesPlay) {
9151             if (cps->other->offeredDraw) {
9152                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9153             /* [HGM] in two-machine mode we delay relaying draw offer      */
9154             /* until after we also have move, to see if it is really claim */
9155             }
9156         } else if (gameMode == MachinePlaysWhite ||
9157                    gameMode == MachinePlaysBlack) {
9158           if (userOfferedDraw) {
9159             DisplayInformation(_("Machine accepts your draw offer"));
9160             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9161           } else {
9162             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9163           }
9164         }
9165     }
9166
9167
9168     /*
9169      * Look for thinking output
9170      */
9171     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9172           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9173                                 ) {
9174         int plylev, mvleft, mvtot, curscore, time;
9175         char mvname[MOVE_LEN];
9176         u64 nodes; // [DM]
9177         char plyext;
9178         int ignore = FALSE;
9179         int prefixHint = FALSE;
9180         mvname[0] = NULLCHAR;
9181
9182         switch (gameMode) {
9183           case MachinePlaysBlack:
9184           case IcsPlayingBlack:
9185             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9186             break;
9187           case MachinePlaysWhite:
9188           case IcsPlayingWhite:
9189             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9190             break;
9191           case AnalyzeMode:
9192           case AnalyzeFile:
9193             break;
9194           case IcsObserving: /* [DM] icsEngineAnalyze */
9195             if (!appData.icsEngineAnalyze) ignore = TRUE;
9196             break;
9197           case TwoMachinesPlay:
9198             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9199                 ignore = TRUE;
9200             }
9201             break;
9202           default:
9203             ignore = TRUE;
9204             break;
9205         }
9206
9207         if (!ignore) {
9208             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9209             buf1[0] = NULLCHAR;
9210             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9211                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9212
9213                 if (plyext != ' ' && plyext != '\t') {
9214                     time *= 100;
9215                 }
9216
9217                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9218                 if( cps->scoreIsAbsolute &&
9219                     ( gameMode == MachinePlaysBlack ||
9220                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9221                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9222                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9223                      !WhiteOnMove(currentMove)
9224                     ) )
9225                 {
9226                     curscore = -curscore;
9227                 }
9228
9229                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9230
9231                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9232                         char buf[MSG_SIZ];
9233                         FILE *f;
9234                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9235                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9236                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9237                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9238                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9239                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9240                                 fclose(f);
9241                         }
9242                         else
9243                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9244                           DisplayError(_("failed writing PV"), 0);
9245                 }
9246
9247                 tempStats.depth = plylev;
9248                 tempStats.nodes = nodes;
9249                 tempStats.time = time;
9250                 tempStats.score = curscore;
9251                 tempStats.got_only_move = 0;
9252
9253                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9254                         int ticklen;
9255
9256                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9257                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9258                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9259                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9260                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9261                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9262                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9263                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9264                 }
9265
9266                 /* Buffer overflow protection */
9267                 if (pv[0] != NULLCHAR) {
9268                     if (strlen(pv) >= sizeof(tempStats.movelist)
9269                         && appData.debugMode) {
9270                         fprintf(debugFP,
9271                                 "PV is too long; using the first %u bytes.\n",
9272                                 (unsigned) sizeof(tempStats.movelist) - 1);
9273                     }
9274
9275                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9276                 } else {
9277                     sprintf(tempStats.movelist, " no PV\n");
9278                 }
9279
9280                 if (tempStats.seen_stat) {
9281                     tempStats.ok_to_send = 1;
9282                 }
9283
9284                 if (strchr(tempStats.movelist, '(') != NULL) {
9285                     tempStats.line_is_book = 1;
9286                     tempStats.nr_moves = 0;
9287                     tempStats.moves_left = 0;
9288                 } else {
9289                     tempStats.line_is_book = 0;
9290                 }
9291
9292                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9293                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9294
9295                 SendProgramStatsToFrontend( cps, &tempStats );
9296
9297                 /*
9298                     [AS] Protect the thinkOutput buffer from overflow... this
9299                     is only useful if buf1 hasn't overflowed first!
9300                 */
9301                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9302                          plylev,
9303                          (gameMode == TwoMachinesPlay ?
9304                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9305                          ((double) curscore) / 100.0,
9306                          prefixHint ? lastHint : "",
9307                          prefixHint ? " " : "" );
9308
9309                 if( buf1[0] != NULLCHAR ) {
9310                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9311
9312                     if( strlen(pv) > max_len ) {
9313                         if( appData.debugMode) {
9314                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9315                         }
9316                         pv[max_len+1] = '\0';
9317                     }
9318
9319                     strcat( thinkOutput, pv);
9320                 }
9321
9322                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9323                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9324                     DisplayMove(currentMove - 1);
9325                 }
9326                 return;
9327
9328             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9329                 /* crafty (9.25+) says "(only move) <move>"
9330                  * if there is only 1 legal move
9331                  */
9332                 sscanf(p, "(only move) %s", buf1);
9333                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9334                 sprintf(programStats.movelist, "%s (only move)", buf1);
9335                 programStats.depth = 1;
9336                 programStats.nr_moves = 1;
9337                 programStats.moves_left = 1;
9338                 programStats.nodes = 1;
9339                 programStats.time = 1;
9340                 programStats.got_only_move = 1;
9341
9342                 /* Not really, but we also use this member to
9343                    mean "line isn't going to change" (Crafty
9344                    isn't searching, so stats won't change) */
9345                 programStats.line_is_book = 1;
9346
9347                 SendProgramStatsToFrontend( cps, &programStats );
9348
9349                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9350                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9351                     DisplayMove(currentMove - 1);
9352                 }
9353                 return;
9354             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9355                               &time, &nodes, &plylev, &mvleft,
9356                               &mvtot, mvname) >= 5) {
9357                 /* The stat01: line is from Crafty (9.29+) in response
9358                    to the "." command */
9359                 programStats.seen_stat = 1;
9360                 cps->maybeThinking = TRUE;
9361
9362                 if (programStats.got_only_move || !appData.periodicUpdates)
9363                   return;
9364
9365                 programStats.depth = plylev;
9366                 programStats.time = time;
9367                 programStats.nodes = nodes;
9368                 programStats.moves_left = mvleft;
9369                 programStats.nr_moves = mvtot;
9370                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9371                 programStats.ok_to_send = 1;
9372                 programStats.movelist[0] = '\0';
9373
9374                 SendProgramStatsToFrontend( cps, &programStats );
9375
9376                 return;
9377
9378             } else if (strncmp(message,"++",2) == 0) {
9379                 /* Crafty 9.29+ outputs this */
9380                 programStats.got_fail = 2;
9381                 return;
9382
9383             } else if (strncmp(message,"--",2) == 0) {
9384                 /* Crafty 9.29+ outputs this */
9385                 programStats.got_fail = 1;
9386                 return;
9387
9388             } else if (thinkOutput[0] != NULLCHAR &&
9389                        strncmp(message, "    ", 4) == 0) {
9390                 unsigned message_len;
9391
9392                 p = message;
9393                 while (*p && *p == ' ') p++;
9394
9395                 message_len = strlen( p );
9396
9397                 /* [AS] Avoid buffer overflow */
9398                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9399                     strcat(thinkOutput, " ");
9400                     strcat(thinkOutput, p);
9401                 }
9402
9403                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9404                     strcat(programStats.movelist, " ");
9405                     strcat(programStats.movelist, p);
9406                 }
9407
9408                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9409                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9410                     DisplayMove(currentMove - 1);
9411                 }
9412                 return;
9413             }
9414         }
9415         else {
9416             buf1[0] = NULLCHAR;
9417
9418             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9419                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9420             {
9421                 ChessProgramStats cpstats;
9422
9423                 if (plyext != ' ' && plyext != '\t') {
9424                     time *= 100;
9425                 }
9426
9427                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9428                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9429                     curscore = -curscore;
9430                 }
9431
9432                 cpstats.depth = plylev;
9433                 cpstats.nodes = nodes;
9434                 cpstats.time = time;
9435                 cpstats.score = curscore;
9436                 cpstats.got_only_move = 0;
9437                 cpstats.movelist[0] = '\0';
9438
9439                 if (buf1[0] != NULLCHAR) {
9440                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9441                 }
9442
9443                 cpstats.ok_to_send = 0;
9444                 cpstats.line_is_book = 0;
9445                 cpstats.nr_moves = 0;
9446                 cpstats.moves_left = 0;
9447
9448                 SendProgramStatsToFrontend( cps, &cpstats );
9449             }
9450         }
9451     }
9452 }
9453
9454
9455 /* Parse a game score from the character string "game", and
9456    record it as the history of the current game.  The game
9457    score is NOT assumed to start from the standard position.
9458    The display is not updated in any way.
9459    */
9460 void
9461 ParseGameHistory (char *game)
9462 {
9463     ChessMove moveType;
9464     int fromX, fromY, toX, toY, boardIndex;
9465     char promoChar;
9466     char *p, *q;
9467     char buf[MSG_SIZ];
9468
9469     if (appData.debugMode)
9470       fprintf(debugFP, "Parsing game history: %s\n", game);
9471
9472     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9473     gameInfo.site = StrSave(appData.icsHost);
9474     gameInfo.date = PGNDate();
9475     gameInfo.round = StrSave("-");
9476
9477     /* Parse out names of players */
9478     while (*game == ' ') game++;
9479     p = buf;
9480     while (*game != ' ') *p++ = *game++;
9481     *p = NULLCHAR;
9482     gameInfo.white = StrSave(buf);
9483     while (*game == ' ') game++;
9484     p = buf;
9485     while (*game != ' ' && *game != '\n') *p++ = *game++;
9486     *p = NULLCHAR;
9487     gameInfo.black = StrSave(buf);
9488
9489     /* Parse moves */
9490     boardIndex = blackPlaysFirst ? 1 : 0;
9491     yynewstr(game);
9492     for (;;) {
9493         yyboardindex = boardIndex;
9494         moveType = (ChessMove) Myylex();
9495         switch (moveType) {
9496           case IllegalMove:             /* maybe suicide chess, etc. */
9497   if (appData.debugMode) {
9498     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9499     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9500     setbuf(debugFP, NULL);
9501   }
9502           case WhitePromotion:
9503           case BlackPromotion:
9504           case WhiteNonPromotion:
9505           case BlackNonPromotion:
9506           case NormalMove:
9507           case FirstLeg:
9508           case WhiteCapturesEnPassant:
9509           case BlackCapturesEnPassant:
9510           case WhiteKingSideCastle:
9511           case WhiteQueenSideCastle:
9512           case BlackKingSideCastle:
9513           case BlackQueenSideCastle:
9514           case WhiteKingSideCastleWild:
9515           case WhiteQueenSideCastleWild:
9516           case BlackKingSideCastleWild:
9517           case BlackQueenSideCastleWild:
9518           /* PUSH Fabien */
9519           case WhiteHSideCastleFR:
9520           case WhiteASideCastleFR:
9521           case BlackHSideCastleFR:
9522           case BlackASideCastleFR:
9523           /* POP Fabien */
9524             fromX = currentMoveString[0] - AAA;
9525             fromY = currentMoveString[1] - ONE;
9526             toX = currentMoveString[2] - AAA;
9527             toY = currentMoveString[3] - ONE;
9528             promoChar = currentMoveString[4];
9529             break;
9530           case WhiteDrop:
9531           case BlackDrop:
9532             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9533             fromX = moveType == WhiteDrop ?
9534               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9535             (int) CharToPiece(ToLower(currentMoveString[0]));
9536             fromY = DROP_RANK;
9537             toX = currentMoveString[2] - AAA;
9538             toY = currentMoveString[3] - ONE;
9539             promoChar = NULLCHAR;
9540             break;
9541           case AmbiguousMove:
9542             /* bug? */
9543             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9544   if (appData.debugMode) {
9545     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9546     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9547     setbuf(debugFP, NULL);
9548   }
9549             DisplayError(buf, 0);
9550             return;
9551           case ImpossibleMove:
9552             /* bug? */
9553             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9554   if (appData.debugMode) {
9555     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9556     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9557     setbuf(debugFP, NULL);
9558   }
9559             DisplayError(buf, 0);
9560             return;
9561           case EndOfFile:
9562             if (boardIndex < backwardMostMove) {
9563                 /* Oops, gap.  How did that happen? */
9564                 DisplayError(_("Gap in move list"), 0);
9565                 return;
9566             }
9567             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9568             if (boardIndex > forwardMostMove) {
9569                 forwardMostMove = boardIndex;
9570             }
9571             return;
9572           case ElapsedTime:
9573             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9574                 strcat(parseList[boardIndex-1], " ");
9575                 strcat(parseList[boardIndex-1], yy_text);
9576             }
9577             continue;
9578           case Comment:
9579           case PGNTag:
9580           case NAG:
9581           default:
9582             /* ignore */
9583             continue;
9584           case WhiteWins:
9585           case BlackWins:
9586           case GameIsDrawn:
9587           case GameUnfinished:
9588             if (gameMode == IcsExamining) {
9589                 if (boardIndex < backwardMostMove) {
9590                     /* Oops, gap.  How did that happen? */
9591                     return;
9592                 }
9593                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9594                 return;
9595             }
9596             gameInfo.result = moveType;
9597             p = strchr(yy_text, '{');
9598             if (p == NULL) p = strchr(yy_text, '(');
9599             if (p == NULL) {
9600                 p = yy_text;
9601                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9602             } else {
9603                 q = strchr(p, *p == '{' ? '}' : ')');
9604                 if (q != NULL) *q = NULLCHAR;
9605                 p++;
9606             }
9607             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9608             gameInfo.resultDetails = StrSave(p);
9609             continue;
9610         }
9611         if (boardIndex >= forwardMostMove &&
9612             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9613             backwardMostMove = blackPlaysFirst ? 1 : 0;
9614             return;
9615         }
9616         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9617                                  fromY, fromX, toY, toX, promoChar,
9618                                  parseList[boardIndex]);
9619         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9620         /* currentMoveString is set as a side-effect of yylex */
9621         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9622         strcat(moveList[boardIndex], "\n");
9623         boardIndex++;
9624         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9625         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9626           case MT_NONE:
9627           case MT_STALEMATE:
9628           default:
9629             break;
9630           case MT_CHECK:
9631             if(gameInfo.variant != VariantShogi)
9632                 strcat(parseList[boardIndex - 1], "+");
9633             break;
9634           case MT_CHECKMATE:
9635           case MT_STAINMATE:
9636             strcat(parseList[boardIndex - 1], "#");
9637             break;
9638         }
9639     }
9640 }
9641
9642
9643 /* Apply a move to the given board  */
9644 void
9645 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9646 {
9647   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9648   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9649
9650     /* [HGM] compute & store e.p. status and castling rights for new position */
9651     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9652
9653       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9654       oldEP = (signed char)board[EP_STATUS];
9655       board[EP_STATUS] = EP_NONE;
9656
9657   if (fromY == DROP_RANK) {
9658         /* must be first */
9659         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9660             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9661             return;
9662         }
9663         piece = board[toY][toX] = (ChessSquare) fromX;
9664   } else {
9665       ChessSquare victim;
9666       int i;
9667
9668       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9669            victim = board[killY][killX],
9670            board[killY][killX] = EmptySquare,
9671            board[EP_STATUS] = EP_CAPTURE;
9672
9673       if( board[toY][toX] != EmptySquare ) {
9674            board[EP_STATUS] = EP_CAPTURE;
9675            if( (fromX != toX || fromY != toY) && // not igui!
9676                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9677                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9678                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9679            }
9680       }
9681
9682       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9683            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9684                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9685       } else
9686       if( board[fromY][fromX] == WhitePawn ) {
9687            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9688                board[EP_STATUS] = EP_PAWN_MOVE;
9689            if( toY-fromY==2) {
9690                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9691                         gameInfo.variant != VariantBerolina || toX < fromX)
9692                       board[EP_STATUS] = toX | berolina;
9693                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9694                         gameInfo.variant != VariantBerolina || toX > fromX)
9695                       board[EP_STATUS] = toX;
9696            }
9697       } else
9698       if( board[fromY][fromX] == BlackPawn ) {
9699            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9700                board[EP_STATUS] = EP_PAWN_MOVE;
9701            if( toY-fromY== -2) {
9702                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9703                         gameInfo.variant != VariantBerolina || toX < fromX)
9704                       board[EP_STATUS] = toX | berolina;
9705                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9706                         gameInfo.variant != VariantBerolina || toX > fromX)
9707                       board[EP_STATUS] = toX;
9708            }
9709        }
9710
9711        for(i=0; i<nrCastlingRights; i++) {
9712            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9713               board[CASTLING][i] == toX   && castlingRank[i] == toY
9714              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9715        }
9716
9717        if(gameInfo.variant == VariantSChess) { // update virginity
9718            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9719            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9720            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9721            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9722        }
9723
9724      if (fromX == toX && fromY == toY) return;
9725
9726      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9727      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9728      if(gameInfo.variant == VariantKnightmate)
9729          king += (int) WhiteUnicorn - (int) WhiteKing;
9730
9731     /* Code added by Tord: */
9732     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9733     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9734         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9735       board[fromY][fromX] = EmptySquare;
9736       board[toY][toX] = EmptySquare;
9737       if((toX > fromX) != (piece == WhiteRook)) {
9738         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9739       } else {
9740         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9741       }
9742     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9743                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9744       board[fromY][fromX] = EmptySquare;
9745       board[toY][toX] = EmptySquare;
9746       if((toX > fromX) != (piece == BlackRook)) {
9747         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9748       } else {
9749         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9750       }
9751     /* End of code added by Tord */
9752
9753     } else if (board[fromY][fromX] == king
9754         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9755         && toY == fromY && toX > fromX+1) {
9756         board[fromY][fromX] = EmptySquare;
9757         board[toY][toX] = king;
9758         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9759         board[fromY][BOARD_RGHT-1] = EmptySquare;
9760     } else if (board[fromY][fromX] == king
9761         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9762                && toY == fromY && toX < fromX-1) {
9763         board[fromY][fromX] = EmptySquare;
9764         board[toY][toX] = king;
9765         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9766         board[fromY][BOARD_LEFT] = EmptySquare;
9767     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9768                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9769                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9770                ) {
9771         /* white pawn promotion */
9772         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9773         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9774             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9775         board[fromY][fromX] = EmptySquare;
9776     } else if ((fromY >= BOARD_HEIGHT>>1)
9777                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9778                && (toX != fromX)
9779                && gameInfo.variant != VariantXiangqi
9780                && gameInfo.variant != VariantBerolina
9781                && (board[fromY][fromX] == WhitePawn)
9782                && (board[toY][toX] == EmptySquare)) {
9783         board[fromY][fromX] = EmptySquare;
9784         board[toY][toX] = WhitePawn;
9785         captured = board[toY - 1][toX];
9786         board[toY - 1][toX] = EmptySquare;
9787     } else if ((fromY == BOARD_HEIGHT-4)
9788                && (toX == fromX)
9789                && gameInfo.variant == VariantBerolina
9790                && (board[fromY][fromX] == WhitePawn)
9791                && (board[toY][toX] == EmptySquare)) {
9792         board[fromY][fromX] = EmptySquare;
9793         board[toY][toX] = WhitePawn;
9794         if(oldEP & EP_BEROLIN_A) {
9795                 captured = board[fromY][fromX-1];
9796                 board[fromY][fromX-1] = EmptySquare;
9797         }else{  captured = board[fromY][fromX+1];
9798                 board[fromY][fromX+1] = EmptySquare;
9799         }
9800     } else if (board[fromY][fromX] == king
9801         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9802                && toY == fromY && toX > fromX+1) {
9803         board[fromY][fromX] = EmptySquare;
9804         board[toY][toX] = king;
9805         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9806         board[fromY][BOARD_RGHT-1] = EmptySquare;
9807     } else if (board[fromY][fromX] == king
9808         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9809                && toY == fromY && toX < fromX-1) {
9810         board[fromY][fromX] = EmptySquare;
9811         board[toY][toX] = king;
9812         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9813         board[fromY][BOARD_LEFT] = EmptySquare;
9814     } else if (fromY == 7 && fromX == 3
9815                && board[fromY][fromX] == BlackKing
9816                && toY == 7 && toX == 5) {
9817         board[fromY][fromX] = EmptySquare;
9818         board[toY][toX] = BlackKing;
9819         board[fromY][7] = EmptySquare;
9820         board[toY][4] = BlackRook;
9821     } else if (fromY == 7 && fromX == 3
9822                && board[fromY][fromX] == BlackKing
9823                && toY == 7 && toX == 1) {
9824         board[fromY][fromX] = EmptySquare;
9825         board[toY][toX] = BlackKing;
9826         board[fromY][0] = EmptySquare;
9827         board[toY][2] = BlackRook;
9828     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9829                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9830                && toY < promoRank && promoChar
9831                ) {
9832         /* black pawn promotion */
9833         board[toY][toX] = CharToPiece(ToLower(promoChar));
9834         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9835             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9836         board[fromY][fromX] = EmptySquare;
9837     } else if ((fromY < BOARD_HEIGHT>>1)
9838                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9839                && (toX != fromX)
9840                && gameInfo.variant != VariantXiangqi
9841                && gameInfo.variant != VariantBerolina
9842                && (board[fromY][fromX] == BlackPawn)
9843                && (board[toY][toX] == EmptySquare)) {
9844         board[fromY][fromX] = EmptySquare;
9845         board[toY][toX] = BlackPawn;
9846         captured = board[toY + 1][toX];
9847         board[toY + 1][toX] = EmptySquare;
9848     } else if ((fromY == 3)
9849                && (toX == fromX)
9850                && gameInfo.variant == VariantBerolina
9851                && (board[fromY][fromX] == BlackPawn)
9852                && (board[toY][toX] == EmptySquare)) {
9853         board[fromY][fromX] = EmptySquare;
9854         board[toY][toX] = BlackPawn;
9855         if(oldEP & EP_BEROLIN_A) {
9856                 captured = board[fromY][fromX-1];
9857                 board[fromY][fromX-1] = EmptySquare;
9858         }else{  captured = board[fromY][fromX+1];
9859                 board[fromY][fromX+1] = EmptySquare;
9860         }
9861     } else {
9862         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9863         board[fromY][fromX] = EmptySquare;
9864         board[toY][toX] = piece;
9865     }
9866   }
9867
9868     if (gameInfo.holdingsWidth != 0) {
9869
9870       /* !!A lot more code needs to be written to support holdings  */
9871       /* [HGM] OK, so I have written it. Holdings are stored in the */
9872       /* penultimate board files, so they are automaticlly stored   */
9873       /* in the game history.                                       */
9874       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9875                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9876         /* Delete from holdings, by decreasing count */
9877         /* and erasing image if necessary            */
9878         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9879         if(p < (int) BlackPawn) { /* white drop */
9880              p -= (int)WhitePawn;
9881                  p = PieceToNumber((ChessSquare)p);
9882              if(p >= gameInfo.holdingsSize) p = 0;
9883              if(--board[p][BOARD_WIDTH-2] <= 0)
9884                   board[p][BOARD_WIDTH-1] = EmptySquare;
9885              if((int)board[p][BOARD_WIDTH-2] < 0)
9886                         board[p][BOARD_WIDTH-2] = 0;
9887         } else {                  /* black drop */
9888              p -= (int)BlackPawn;
9889                  p = PieceToNumber((ChessSquare)p);
9890              if(p >= gameInfo.holdingsSize) p = 0;
9891              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9892                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9893              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9894                         board[BOARD_HEIGHT-1-p][1] = 0;
9895         }
9896       }
9897       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9898           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9899         /* [HGM] holdings: Add to holdings, if holdings exist */
9900         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9901                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9902                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9903         }
9904         p = (int) captured;
9905         if (p >= (int) BlackPawn) {
9906           p -= (int)BlackPawn;
9907           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9908                   /* in Shogi restore piece to its original  first */
9909                   captured = (ChessSquare) (DEMOTED captured);
9910                   p = DEMOTED p;
9911           }
9912           p = PieceToNumber((ChessSquare)p);
9913           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9914           board[p][BOARD_WIDTH-2]++;
9915           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9916         } else {
9917           p -= (int)WhitePawn;
9918           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9919                   captured = (ChessSquare) (DEMOTED captured);
9920                   p = DEMOTED p;
9921           }
9922           p = PieceToNumber((ChessSquare)p);
9923           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9924           board[BOARD_HEIGHT-1-p][1]++;
9925           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9926         }
9927       }
9928     } else if (gameInfo.variant == VariantAtomic) {
9929       if (captured != EmptySquare) {
9930         int y, x;
9931         for (y = toY-1; y <= toY+1; y++) {
9932           for (x = toX-1; x <= toX+1; x++) {
9933             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9934                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9935               board[y][x] = EmptySquare;
9936             }
9937           }
9938         }
9939         board[toY][toX] = EmptySquare;
9940       }
9941     }
9942     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9943         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9944     } else
9945     if(promoChar == '+') {
9946         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9947         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9948     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9949         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9950         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9951            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9952         board[toY][toX] = newPiece;
9953     }
9954     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9955                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9956         // [HGM] superchess: take promotion piece out of holdings
9957         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9958         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9959             if(!--board[k][BOARD_WIDTH-2])
9960                 board[k][BOARD_WIDTH-1] = EmptySquare;
9961         } else {
9962             if(!--board[BOARD_HEIGHT-1-k][1])
9963                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9964         }
9965     }
9966 }
9967
9968 /* Updates forwardMostMove */
9969 void
9970 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9971 {
9972     int x = toX, y = toY;
9973     char *s = parseList[forwardMostMove];
9974     ChessSquare p = boards[forwardMostMove][toY][toX];
9975 //    forwardMostMove++; // [HGM] bare: moved downstream
9976
9977     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9978     (void) CoordsToAlgebraic(boards[forwardMostMove],
9979                              PosFlags(forwardMostMove),
9980                              fromY, fromX, y, x, promoChar,
9981                              s);
9982     if(killX >= 0 && killY >= 0)
9983         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9984
9985     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9986         int timeLeft; static int lastLoadFlag=0; int king, piece;
9987         piece = boards[forwardMostMove][fromY][fromX];
9988         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9989         if(gameInfo.variant == VariantKnightmate)
9990             king += (int) WhiteUnicorn - (int) WhiteKing;
9991         if(forwardMostMove == 0) {
9992             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9993                 fprintf(serverMoves, "%s;", UserName());
9994             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9995                 fprintf(serverMoves, "%s;", second.tidy);
9996             fprintf(serverMoves, "%s;", first.tidy);
9997             if(gameMode == MachinePlaysWhite)
9998                 fprintf(serverMoves, "%s;", UserName());
9999             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10000                 fprintf(serverMoves, "%s;", second.tidy);
10001         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10002         lastLoadFlag = loadFlag;
10003         // print base move
10004         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10005         // print castling suffix
10006         if( toY == fromY && piece == king ) {
10007             if(toX-fromX > 1)
10008                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10009             if(fromX-toX >1)
10010                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10011         }
10012         // e.p. suffix
10013         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10014              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10015              boards[forwardMostMove][toY][toX] == EmptySquare
10016              && fromX != toX && fromY != toY)
10017                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10018         // promotion suffix
10019         if(promoChar != NULLCHAR) {
10020             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10021                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10022                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10023             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10024         }
10025         if(!loadFlag) {
10026                 char buf[MOVE_LEN*2], *p; int len;
10027             fprintf(serverMoves, "/%d/%d",
10028                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10029             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10030             else                      timeLeft = blackTimeRemaining/1000;
10031             fprintf(serverMoves, "/%d", timeLeft);
10032                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10033                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10034                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10035                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10036             fprintf(serverMoves, "/%s", buf);
10037         }
10038         fflush(serverMoves);
10039     }
10040
10041     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10042         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10043       return;
10044     }
10045     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10046     if (commentList[forwardMostMove+1] != NULL) {
10047         free(commentList[forwardMostMove+1]);
10048         commentList[forwardMostMove+1] = NULL;
10049     }
10050     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10051     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10052     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10053     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10054     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10055     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10056     adjustedClock = FALSE;
10057     gameInfo.result = GameUnfinished;
10058     if (gameInfo.resultDetails != NULL) {
10059         free(gameInfo.resultDetails);
10060         gameInfo.resultDetails = NULL;
10061     }
10062     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10063                               moveList[forwardMostMove - 1]);
10064     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10065       case MT_NONE:
10066       case MT_STALEMATE:
10067       default:
10068         break;
10069       case MT_CHECK:
10070         if(gameInfo.variant != VariantShogi)
10071             strcat(parseList[forwardMostMove - 1], "+");
10072         break;
10073       case MT_CHECKMATE:
10074       case MT_STAINMATE:
10075         strcat(parseList[forwardMostMove - 1], "#");
10076         break;
10077     }
10078 }
10079
10080 /* Updates currentMove if not pausing */
10081 void
10082 ShowMove (int fromX, int fromY, int toX, int toY)
10083 {
10084     int instant = (gameMode == PlayFromGameFile) ?
10085         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10086     if(appData.noGUI) return;
10087     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10088         if (!instant) {
10089             if (forwardMostMove == currentMove + 1) {
10090                 AnimateMove(boards[forwardMostMove - 1],
10091                             fromX, fromY, toX, toY);
10092             }
10093         }
10094         currentMove = forwardMostMove;
10095     }
10096
10097     killX = killY = -1; // [HGM] lion: used up
10098
10099     if (instant) return;
10100
10101     DisplayMove(currentMove - 1);
10102     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10103             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10104                 SetHighlights(fromX, fromY, toX, toY);
10105             }
10106     }
10107     DrawPosition(FALSE, boards[currentMove]);
10108     DisplayBothClocks();
10109     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10110 }
10111
10112 void
10113 SendEgtPath (ChessProgramState *cps)
10114 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10115         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10116
10117         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10118
10119         while(*p) {
10120             char c, *q = name+1, *r, *s;
10121
10122             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10123             while(*p && *p != ',') *q++ = *p++;
10124             *q++ = ':'; *q = 0;
10125             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10126                 strcmp(name, ",nalimov:") == 0 ) {
10127                 // take nalimov path from the menu-changeable option first, if it is defined
10128               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10129                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10130             } else
10131             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10132                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10133                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10134                 s = r = StrStr(s, ":") + 1; // beginning of path info
10135                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10136                 c = *r; *r = 0;             // temporarily null-terminate path info
10137                     *--q = 0;               // strip of trailig ':' from name
10138                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10139                 *r = c;
10140                 SendToProgram(buf,cps);     // send egtbpath command for this format
10141             }
10142             if(*p == ',') p++; // read away comma to position for next format name
10143         }
10144 }
10145
10146 static int
10147 NonStandardBoardSize ()
10148 {
10149       /* [HGM] Awkward testing. Should really be a table */
10150       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10151       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10152       if( gameInfo.variant == VariantXiangqi )
10153            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10154       if( gameInfo.variant == VariantShogi )
10155            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10156       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10157            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10158       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10159           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10160            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10161       if( gameInfo.variant == VariantCourier )
10162            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10163       if( gameInfo.variant == VariantSuper )
10164            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10165       if( gameInfo.variant == VariantGreat )
10166            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10167       if( gameInfo.variant == VariantSChess )
10168            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10169       if( gameInfo.variant == VariantGrand )
10170            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10171       if( gameInfo.variant == VariantChu )
10172            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10173       return overruled;
10174 }
10175
10176 void
10177 InitChessProgram (ChessProgramState *cps, int setup)
10178 /* setup needed to setup FRC opening position */
10179 {
10180     char buf[MSG_SIZ], b[MSG_SIZ];
10181     if (appData.noChessProgram) return;
10182     hintRequested = FALSE;
10183     bookRequested = FALSE;
10184
10185     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10186     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10187     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10188     if(cps->memSize) { /* [HGM] memory */
10189       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10190         SendToProgram(buf, cps);
10191     }
10192     SendEgtPath(cps); /* [HGM] EGT */
10193     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10194       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10195         SendToProgram(buf, cps);
10196     }
10197
10198     SendToProgram(cps->initString, cps);
10199     if (gameInfo.variant != VariantNormal &&
10200         gameInfo.variant != VariantLoadable
10201         /* [HGM] also send variant if board size non-standard */
10202         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10203                                             ) {
10204       char *v = VariantName(gameInfo.variant);
10205       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10206         /* [HGM] in protocol 1 we have to assume all variants valid */
10207         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10208         DisplayFatalError(buf, 0, 1);
10209         return;
10210       }
10211
10212       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10213         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10214                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10215            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10216            if(StrStr(cps->variants, b) == NULL) {
10217                // specific sized variant not known, check if general sizing allowed
10218                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10219                    if(StrStr(cps->variants, "boardsize") == NULL) {
10220                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10221                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10222                        DisplayFatalError(buf, 0, 1);
10223                        return;
10224                    }
10225                    /* [HGM] here we really should compare with the maximum supported board size */
10226                }
10227            }
10228       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10229       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10230       SendToProgram(buf, cps);
10231     }
10232     currentlyInitializedVariant = gameInfo.variant;
10233
10234     /* [HGM] send opening position in FRC to first engine */
10235     if(setup) {
10236           SendToProgram("force\n", cps);
10237           SendBoard(cps, 0);
10238           /* engine is now in force mode! Set flag to wake it up after first move. */
10239           setboardSpoiledMachineBlack = 1;
10240     }
10241
10242     if (cps->sendICS) {
10243       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10244       SendToProgram(buf, cps);
10245     }
10246     cps->maybeThinking = FALSE;
10247     cps->offeredDraw = 0;
10248     if (!appData.icsActive) {
10249         SendTimeControl(cps, movesPerSession, timeControl,
10250                         timeIncrement, appData.searchDepth,
10251                         searchTime);
10252     }
10253     if (appData.showThinking
10254         // [HGM] thinking: four options require thinking output to be sent
10255         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10256                                 ) {
10257         SendToProgram("post\n", cps);
10258     }
10259     SendToProgram("hard\n", cps);
10260     if (!appData.ponderNextMove) {
10261         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10262            it without being sure what state we are in first.  "hard"
10263            is not a toggle, so that one is OK.
10264          */
10265         SendToProgram("easy\n", cps);
10266     }
10267     if (cps->usePing) {
10268       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10269       SendToProgram(buf, cps);
10270     }
10271     cps->initDone = TRUE;
10272     ClearEngineOutputPane(cps == &second);
10273 }
10274
10275
10276 void
10277 ResendOptions (ChessProgramState *cps)
10278 { // send the stored value of the options
10279   int i;
10280   char buf[MSG_SIZ];
10281   Option *opt = cps->option;
10282   for(i=0; i<cps->nrOptions; i++, opt++) {
10283       switch(opt->type) {
10284         case Spin:
10285         case Slider:
10286         case CheckBox:
10287             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10288           break;
10289         case ComboBox:
10290           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10291           break;
10292         default:
10293             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10294           break;
10295         case Button:
10296         case SaveButton:
10297           continue;
10298       }
10299       SendToProgram(buf, cps);
10300   }
10301 }
10302
10303 void
10304 StartChessProgram (ChessProgramState *cps)
10305 {
10306     char buf[MSG_SIZ];
10307     int err;
10308
10309     if (appData.noChessProgram) return;
10310     cps->initDone = FALSE;
10311
10312     if (strcmp(cps->host, "localhost") == 0) {
10313         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10314     } else if (*appData.remoteShell == NULLCHAR) {
10315         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10316     } else {
10317         if (*appData.remoteUser == NULLCHAR) {
10318           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10319                     cps->program);
10320         } else {
10321           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10322                     cps->host, appData.remoteUser, cps->program);
10323         }
10324         err = StartChildProcess(buf, "", &cps->pr);
10325     }
10326
10327     if (err != 0) {
10328       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10329         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10330         if(cps != &first) return;
10331         appData.noChessProgram = TRUE;
10332         ThawUI();
10333         SetNCPMode();
10334 //      DisplayFatalError(buf, err, 1);
10335 //      cps->pr = NoProc;
10336 //      cps->isr = NULL;
10337         return;
10338     }
10339
10340     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10341     if (cps->protocolVersion > 1) {
10342       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10343       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10344         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10345         cps->comboCnt = 0;  //                and values of combo boxes
10346       }
10347       SendToProgram(buf, cps);
10348       if(cps->reload) ResendOptions(cps);
10349     } else {
10350       SendToProgram("xboard\n", cps);
10351     }
10352 }
10353
10354 void
10355 TwoMachinesEventIfReady P((void))
10356 {
10357   static int curMess = 0;
10358   if (first.lastPing != first.lastPong) {
10359     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10360     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10361     return;
10362   }
10363   if (second.lastPing != second.lastPong) {
10364     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10365     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10366     return;
10367   }
10368   DisplayMessage("", ""); curMess = 0;
10369   TwoMachinesEvent();
10370 }
10371
10372 char *
10373 MakeName (char *template)
10374 {
10375     time_t clock;
10376     struct tm *tm;
10377     static char buf[MSG_SIZ];
10378     char *p = buf;
10379     int i;
10380
10381     clock = time((time_t *)NULL);
10382     tm = localtime(&clock);
10383
10384     while(*p++ = *template++) if(p[-1] == '%') {
10385         switch(*template++) {
10386           case 0:   *p = 0; return buf;
10387           case 'Y': i = tm->tm_year+1900; break;
10388           case 'y': i = tm->tm_year-100; break;
10389           case 'M': i = tm->tm_mon+1; break;
10390           case 'd': i = tm->tm_mday; break;
10391           case 'h': i = tm->tm_hour; break;
10392           case 'm': i = tm->tm_min; break;
10393           case 's': i = tm->tm_sec; break;
10394           default:  i = 0;
10395         }
10396         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10397     }
10398     return buf;
10399 }
10400
10401 int
10402 CountPlayers (char *p)
10403 {
10404     int n = 0;
10405     while(p = strchr(p, '\n')) p++, n++; // count participants
10406     return n;
10407 }
10408
10409 FILE *
10410 WriteTourneyFile (char *results, FILE *f)
10411 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10412     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10413     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10414         // create a file with tournament description
10415         fprintf(f, "-participants {%s}\n", appData.participants);
10416         fprintf(f, "-seedBase %d\n", appData.seedBase);
10417         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10418         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10419         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10420         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10421         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10422         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10423         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10424         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10425         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10426         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10427         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10428         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10429         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10430         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10431         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10432         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10433         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10434         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10435         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10436         fprintf(f, "-smpCores %d\n", appData.smpCores);
10437         if(searchTime > 0)
10438                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10439         else {
10440                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10441                 fprintf(f, "-tc %s\n", appData.timeControl);
10442                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10443         }
10444         fprintf(f, "-results \"%s\"\n", results);
10445     }
10446     return f;
10447 }
10448
10449 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10450
10451 void
10452 Substitute (char *participants, int expunge)
10453 {
10454     int i, changed, changes=0, nPlayers=0;
10455     char *p, *q, *r, buf[MSG_SIZ];
10456     if(participants == NULL) return;
10457     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10458     r = p = participants; q = appData.participants;
10459     while(*p && *p == *q) {
10460         if(*p == '\n') r = p+1, nPlayers++;
10461         p++; q++;
10462     }
10463     if(*p) { // difference
10464         while(*p && *p++ != '\n');
10465         while(*q && *q++ != '\n');
10466       changed = nPlayers;
10467         changes = 1 + (strcmp(p, q) != 0);
10468     }
10469     if(changes == 1) { // a single engine mnemonic was changed
10470         q = r; while(*q) nPlayers += (*q++ == '\n');
10471         p = buf; while(*r && (*p = *r++) != '\n') p++;
10472         *p = NULLCHAR;
10473         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10474         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10475         if(mnemonic[i]) { // The substitute is valid
10476             FILE *f;
10477             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10478                 flock(fileno(f), LOCK_EX);
10479                 ParseArgsFromFile(f);
10480                 fseek(f, 0, SEEK_SET);
10481                 FREE(appData.participants); appData.participants = participants;
10482                 if(expunge) { // erase results of replaced engine
10483                     int len = strlen(appData.results), w, b, dummy;
10484                     for(i=0; i<len; i++) {
10485                         Pairing(i, nPlayers, &w, &b, &dummy);
10486                         if((w == changed || b == changed) && appData.results[i] == '*') {
10487                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10488                             fclose(f);
10489                             return;
10490                         }
10491                     }
10492                     for(i=0; i<len; i++) {
10493                         Pairing(i, nPlayers, &w, &b, &dummy);
10494                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10495                     }
10496                 }
10497                 WriteTourneyFile(appData.results, f);
10498                 fclose(f); // release lock
10499                 return;
10500             }
10501         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10502     }
10503     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10504     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10505     free(participants);
10506     return;
10507 }
10508
10509 int
10510 CheckPlayers (char *participants)
10511 {
10512         int i;
10513         char buf[MSG_SIZ], *p;
10514         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10515         while(p = strchr(participants, '\n')) {
10516             *p = NULLCHAR;
10517             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10518             if(!mnemonic[i]) {
10519                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10520                 *p = '\n';
10521                 DisplayError(buf, 0);
10522                 return 1;
10523             }
10524             *p = '\n';
10525             participants = p + 1;
10526         }
10527         return 0;
10528 }
10529
10530 int
10531 CreateTourney (char *name)
10532 {
10533         FILE *f;
10534         if(matchMode && strcmp(name, appData.tourneyFile)) {
10535              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10536         }
10537         if(name[0] == NULLCHAR) {
10538             if(appData.participants[0])
10539                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10540             return 0;
10541         }
10542         f = fopen(name, "r");
10543         if(f) { // file exists
10544             ASSIGN(appData.tourneyFile, name);
10545             ParseArgsFromFile(f); // parse it
10546         } else {
10547             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10548             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10549                 DisplayError(_("Not enough participants"), 0);
10550                 return 0;
10551             }
10552             if(CheckPlayers(appData.participants)) return 0;
10553             ASSIGN(appData.tourneyFile, name);
10554             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10555             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10556         }
10557         fclose(f);
10558         appData.noChessProgram = FALSE;
10559         appData.clockMode = TRUE;
10560         SetGNUMode();
10561         return 1;
10562 }
10563
10564 int
10565 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10566 {
10567     char buf[MSG_SIZ], *p, *q;
10568     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10569     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10570     skip = !all && group[0]; // if group requested, we start in skip mode
10571     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10572         p = names; q = buf; header = 0;
10573         while(*p && *p != '\n') *q++ = *p++;
10574         *q = 0;
10575         if(*p == '\n') p++;
10576         if(buf[0] == '#') {
10577             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10578             depth++; // we must be entering a new group
10579             if(all) continue; // suppress printing group headers when complete list requested
10580             header = 1;
10581             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10582         }
10583         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10584         if(engineList[i]) free(engineList[i]);
10585         engineList[i] = strdup(buf);
10586         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10587         if(engineMnemonic[i]) free(engineMnemonic[i]);
10588         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10589             strcat(buf, " (");
10590             sscanf(q + 8, "%s", buf + strlen(buf));
10591             strcat(buf, ")");
10592         }
10593         engineMnemonic[i] = strdup(buf);
10594         i++;
10595     }
10596     engineList[i] = engineMnemonic[i] = NULL;
10597     return i;
10598 }
10599
10600 // following implemented as macro to avoid type limitations
10601 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10602
10603 void
10604 SwapEngines (int n)
10605 {   // swap settings for first engine and other engine (so far only some selected options)
10606     int h;
10607     char *p;
10608     if(n == 0) return;
10609     SWAP(directory, p)
10610     SWAP(chessProgram, p)
10611     SWAP(isUCI, h)
10612     SWAP(hasOwnBookUCI, h)
10613     SWAP(protocolVersion, h)
10614     SWAP(reuse, h)
10615     SWAP(scoreIsAbsolute, h)
10616     SWAP(timeOdds, h)
10617     SWAP(logo, p)
10618     SWAP(pgnName, p)
10619     SWAP(pvSAN, h)
10620     SWAP(engOptions, p)
10621     SWAP(engInitString, p)
10622     SWAP(computerString, p)
10623     SWAP(features, p)
10624     SWAP(fenOverride, p)
10625     SWAP(NPS, h)
10626     SWAP(accumulateTC, h)
10627     SWAP(host, p)
10628 }
10629
10630 int
10631 GetEngineLine (char *s, int n)
10632 {
10633     int i;
10634     char buf[MSG_SIZ];
10635     extern char *icsNames;
10636     if(!s || !*s) return 0;
10637     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10638     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10639     if(!mnemonic[i]) return 0;
10640     if(n == 11) return 1; // just testing if there was a match
10641     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10642     if(n == 1) SwapEngines(n);
10643     ParseArgsFromString(buf);
10644     if(n == 1) SwapEngines(n);
10645     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10646         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10647         ParseArgsFromString(buf);
10648     }
10649     return 1;
10650 }
10651
10652 int
10653 SetPlayer (int player, char *p)
10654 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10655     int i;
10656     char buf[MSG_SIZ], *engineName;
10657     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10658     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10659     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10660     if(mnemonic[i]) {
10661         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10662         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10663         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10664         ParseArgsFromString(buf);
10665     } else { // no engine with this nickname is installed!
10666         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10667         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10668         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10669         ModeHighlight();
10670         DisplayError(buf, 0);
10671         return 0;
10672     }
10673     free(engineName);
10674     return i;
10675 }
10676
10677 char *recentEngines;
10678
10679 void
10680 RecentEngineEvent (int nr)
10681 {
10682     int n;
10683 //    SwapEngines(1); // bump first to second
10684 //    ReplaceEngine(&second, 1); // and load it there
10685     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10686     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10687     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10688         ReplaceEngine(&first, 0);
10689         FloatToFront(&appData.recentEngineList, command[n]);
10690     }
10691 }
10692
10693 int
10694 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10695 {   // determine players from game number
10696     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10697
10698     if(appData.tourneyType == 0) {
10699         roundsPerCycle = (nPlayers - 1) | 1;
10700         pairingsPerRound = nPlayers / 2;
10701     } else if(appData.tourneyType > 0) {
10702         roundsPerCycle = nPlayers - appData.tourneyType;
10703         pairingsPerRound = appData.tourneyType;
10704     }
10705     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10706     gamesPerCycle = gamesPerRound * roundsPerCycle;
10707     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10708     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10709     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10710     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10711     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10712     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10713
10714     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10715     if(appData.roundSync) *syncInterval = gamesPerRound;
10716
10717     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10718
10719     if(appData.tourneyType == 0) {
10720         if(curPairing == (nPlayers-1)/2 ) {
10721             *whitePlayer = curRound;
10722             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10723         } else {
10724             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10725             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10726             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10727             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10728         }
10729     } else if(appData.tourneyType > 1) {
10730         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10731         *whitePlayer = curRound + appData.tourneyType;
10732     } else if(appData.tourneyType > 0) {
10733         *whitePlayer = curPairing;
10734         *blackPlayer = curRound + appData.tourneyType;
10735     }
10736
10737     // take care of white/black alternation per round.
10738     // For cycles and games this is already taken care of by default, derived from matchGame!
10739     return curRound & 1;
10740 }
10741
10742 int
10743 NextTourneyGame (int nr, int *swapColors)
10744 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10745     char *p, *q;
10746     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10747     FILE *tf;
10748     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10749     tf = fopen(appData.tourneyFile, "r");
10750     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10751     ParseArgsFromFile(tf); fclose(tf);
10752     InitTimeControls(); // TC might be altered from tourney file
10753
10754     nPlayers = CountPlayers(appData.participants); // count participants
10755     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10756     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10757
10758     if(syncInterval) {
10759         p = q = appData.results;
10760         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10761         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10762             DisplayMessage(_("Waiting for other game(s)"),"");
10763             waitingForGame = TRUE;
10764             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10765             return 0;
10766         }
10767         waitingForGame = FALSE;
10768     }
10769
10770     if(appData.tourneyType < 0) {
10771         if(nr>=0 && !pairingReceived) {
10772             char buf[1<<16];
10773             if(pairing.pr == NoProc) {
10774                 if(!appData.pairingEngine[0]) {
10775                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10776                     return 0;
10777                 }
10778                 StartChessProgram(&pairing); // starts the pairing engine
10779             }
10780             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10781             SendToProgram(buf, &pairing);
10782             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10783             SendToProgram(buf, &pairing);
10784             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10785         }
10786         pairingReceived = 0;                              // ... so we continue here
10787         *swapColors = 0;
10788         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10789         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10790         matchGame = 1; roundNr = nr / syncInterval + 1;
10791     }
10792
10793     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10794
10795     // redefine engines, engine dir, etc.
10796     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10797     if(first.pr == NoProc) {
10798       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10799       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10800     }
10801     if(second.pr == NoProc) {
10802       SwapEngines(1);
10803       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10804       SwapEngines(1);         // and make that valid for second engine by swapping
10805       InitEngine(&second, 1);
10806     }
10807     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10808     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10809     return OK;
10810 }
10811
10812 void
10813 NextMatchGame ()
10814 {   // performs game initialization that does not invoke engines, and then tries to start the game
10815     int res, firstWhite, swapColors = 0;
10816     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10817     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
10818         char buf[MSG_SIZ];
10819         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10820         if(strcmp(buf, currentDebugFile)) { // name has changed
10821             FILE *f = fopen(buf, "w");
10822             if(f) { // if opening the new file failed, just keep using the old one
10823                 ASSIGN(currentDebugFile, buf);
10824                 fclose(debugFP);
10825                 debugFP = f;
10826             }
10827             if(appData.serverFileName) {
10828                 if(serverFP) fclose(serverFP);
10829                 serverFP = fopen(appData.serverFileName, "w");
10830                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10831                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10832             }
10833         }
10834     }
10835     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10836     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10837     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10838     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10839     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10840     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10841     Reset(FALSE, first.pr != NoProc);
10842     res = LoadGameOrPosition(matchGame); // setup game
10843     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10844     if(!res) return; // abort when bad game/pos file
10845     TwoMachinesEvent();
10846 }
10847
10848 void
10849 UserAdjudicationEvent (int result)
10850 {
10851     ChessMove gameResult = GameIsDrawn;
10852
10853     if( result > 0 ) {
10854         gameResult = WhiteWins;
10855     }
10856     else if( result < 0 ) {
10857         gameResult = BlackWins;
10858     }
10859
10860     if( gameMode == TwoMachinesPlay ) {
10861         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10862     }
10863 }
10864
10865
10866 // [HGM] save: calculate checksum of game to make games easily identifiable
10867 int
10868 StringCheckSum (char *s)
10869 {
10870         int i = 0;
10871         if(s==NULL) return 0;
10872         while(*s) i = i*259 + *s++;
10873         return i;
10874 }
10875
10876 int
10877 GameCheckSum ()
10878 {
10879         int i, sum=0;
10880         for(i=backwardMostMove; i<forwardMostMove; i++) {
10881                 sum += pvInfoList[i].depth;
10882                 sum += StringCheckSum(parseList[i]);
10883                 sum += StringCheckSum(commentList[i]);
10884                 sum *= 261;
10885         }
10886         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10887         return sum + StringCheckSum(commentList[i]);
10888 } // end of save patch
10889
10890 void
10891 GameEnds (ChessMove result, char *resultDetails, int whosays)
10892 {
10893     GameMode nextGameMode;
10894     int isIcsGame;
10895     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10896
10897     if(endingGame) return; /* [HGM] crash: forbid recursion */
10898     endingGame = 1;
10899     if(twoBoards) { // [HGM] dual: switch back to one board
10900         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10901         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10902     }
10903     if (appData.debugMode) {
10904       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10905               result, resultDetails ? resultDetails : "(null)", whosays);
10906     }
10907
10908     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10909
10910     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10911
10912     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10913         /* If we are playing on ICS, the server decides when the
10914            game is over, but the engine can offer to draw, claim
10915            a draw, or resign.
10916          */
10917 #if ZIPPY
10918         if (appData.zippyPlay && first.initDone) {
10919             if (result == GameIsDrawn) {
10920                 /* In case draw still needs to be claimed */
10921                 SendToICS(ics_prefix);
10922                 SendToICS("draw\n");
10923             } else if (StrCaseStr(resultDetails, "resign")) {
10924                 SendToICS(ics_prefix);
10925                 SendToICS("resign\n");
10926             }
10927         }
10928 #endif
10929         endingGame = 0; /* [HGM] crash */
10930         return;
10931     }
10932
10933     /* If we're loading the game from a file, stop */
10934     if (whosays == GE_FILE) {
10935       (void) StopLoadGameTimer();
10936       gameFileFP = NULL;
10937     }
10938
10939     /* Cancel draw offers */
10940     first.offeredDraw = second.offeredDraw = 0;
10941
10942     /* If this is an ICS game, only ICS can really say it's done;
10943        if not, anyone can. */
10944     isIcsGame = (gameMode == IcsPlayingWhite ||
10945                  gameMode == IcsPlayingBlack ||
10946                  gameMode == IcsObserving    ||
10947                  gameMode == IcsExamining);
10948
10949     if (!isIcsGame || whosays == GE_ICS) {
10950         /* OK -- not an ICS game, or ICS said it was done */
10951         StopClocks();
10952         if (!isIcsGame && !appData.noChessProgram)
10953           SetUserThinkingEnables();
10954
10955         /* [HGM] if a machine claims the game end we verify this claim */
10956         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10957             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10958                 char claimer;
10959                 ChessMove trueResult = (ChessMove) -1;
10960
10961                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10962                                             first.twoMachinesColor[0] :
10963                                             second.twoMachinesColor[0] ;
10964
10965                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10967                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10968                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10969                 } else
10970                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10971                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10972                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10973                 } else
10974                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10975                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10976                 }
10977
10978                 // now verify win claims, but not in drop games, as we don't understand those yet
10979                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10980                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10981                     (result == WhiteWins && claimer == 'w' ||
10982                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10983                       if (appData.debugMode) {
10984                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10985                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10986                       }
10987                       if(result != trueResult) {
10988                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10989                               result = claimer == 'w' ? BlackWins : WhiteWins;
10990                               resultDetails = buf;
10991                       }
10992                 } else
10993                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10994                     && (forwardMostMove <= backwardMostMove ||
10995                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10996                         (claimer=='b')==(forwardMostMove&1))
10997                                                                                   ) {
10998                       /* [HGM] verify: draws that were not flagged are false claims */
10999                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11000                       result = claimer == 'w' ? BlackWins : WhiteWins;
11001                       resultDetails = buf;
11002                 }
11003                 /* (Claiming a loss is accepted no questions asked!) */
11004             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11005                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11006                 result = GameUnfinished;
11007                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11008             }
11009             /* [HGM] bare: don't allow bare King to win */
11010             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11011                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11012                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11013                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11014                && result != GameIsDrawn)
11015             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11016                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11017                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11018                         if(p >= 0 && p <= (int)WhiteKing) k++;
11019                 }
11020                 if (appData.debugMode) {
11021                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11022                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11023                 }
11024                 if(k <= 1) {
11025                         result = GameIsDrawn;
11026                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11027                         resultDetails = buf;
11028                 }
11029             }
11030         }
11031
11032
11033         if(serverMoves != NULL && !loadFlag) { char c = '=';
11034             if(result==WhiteWins) c = '+';
11035             if(result==BlackWins) c = '-';
11036             if(resultDetails != NULL)
11037                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11038         }
11039         if (resultDetails != NULL) {
11040             gameInfo.result = result;
11041             gameInfo.resultDetails = StrSave(resultDetails);
11042
11043             /* display last move only if game was not loaded from file */
11044             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11045                 DisplayMove(currentMove - 1);
11046
11047             if (forwardMostMove != 0) {
11048                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11049                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11050                                                                 ) {
11051                     if (*appData.saveGameFile != NULLCHAR) {
11052                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11053                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11054                         else
11055                         SaveGameToFile(appData.saveGameFile, TRUE);
11056                     } else if (appData.autoSaveGames) {
11057                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11058                     }
11059                     if (*appData.savePositionFile != NULLCHAR) {
11060                         SavePositionToFile(appData.savePositionFile);
11061                     }
11062                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11063                 }
11064             }
11065
11066             /* Tell program how game ended in case it is learning */
11067             /* [HGM] Moved this to after saving the PGN, just in case */
11068             /* engine died and we got here through time loss. In that */
11069             /* case we will get a fatal error writing the pipe, which */
11070             /* would otherwise lose us the PGN.                       */
11071             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11072             /* output during GameEnds should never be fatal anymore   */
11073             if (gameMode == MachinePlaysWhite ||
11074                 gameMode == MachinePlaysBlack ||
11075                 gameMode == TwoMachinesPlay ||
11076                 gameMode == IcsPlayingWhite ||
11077                 gameMode == IcsPlayingBlack ||
11078                 gameMode == BeginningOfGame) {
11079                 char buf[MSG_SIZ];
11080                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11081                         resultDetails);
11082                 if (first.pr != NoProc) {
11083                     SendToProgram(buf, &first);
11084                 }
11085                 if (second.pr != NoProc &&
11086                     gameMode == TwoMachinesPlay) {
11087                     SendToProgram(buf, &second);
11088                 }
11089             }
11090         }
11091
11092         if (appData.icsActive) {
11093             if (appData.quietPlay &&
11094                 (gameMode == IcsPlayingWhite ||
11095                  gameMode == IcsPlayingBlack)) {
11096                 SendToICS(ics_prefix);
11097                 SendToICS("set shout 1\n");
11098             }
11099             nextGameMode = IcsIdle;
11100             ics_user_moved = FALSE;
11101             /* clean up premove.  It's ugly when the game has ended and the
11102              * premove highlights are still on the board.
11103              */
11104             if (gotPremove) {
11105               gotPremove = FALSE;
11106               ClearPremoveHighlights();
11107               DrawPosition(FALSE, boards[currentMove]);
11108             }
11109             if (whosays == GE_ICS) {
11110                 switch (result) {
11111                 case WhiteWins:
11112                     if (gameMode == IcsPlayingWhite)
11113                         PlayIcsWinSound();
11114                     else if(gameMode == IcsPlayingBlack)
11115                         PlayIcsLossSound();
11116                     break;
11117                 case BlackWins:
11118                     if (gameMode == IcsPlayingBlack)
11119                         PlayIcsWinSound();
11120                     else if(gameMode == IcsPlayingWhite)
11121                         PlayIcsLossSound();
11122                     break;
11123                 case GameIsDrawn:
11124                     PlayIcsDrawSound();
11125                     break;
11126                 default:
11127                     PlayIcsUnfinishedSound();
11128                 }
11129             }
11130             if(appData.quitNext) { ExitEvent(0); return; }
11131         } else if (gameMode == EditGame ||
11132                    gameMode == PlayFromGameFile ||
11133                    gameMode == AnalyzeMode ||
11134                    gameMode == AnalyzeFile) {
11135             nextGameMode = gameMode;
11136         } else {
11137             nextGameMode = EndOfGame;
11138         }
11139         pausing = FALSE;
11140         ModeHighlight();
11141     } else {
11142         nextGameMode = gameMode;
11143     }
11144
11145     if (appData.noChessProgram) {
11146         gameMode = nextGameMode;
11147         ModeHighlight();
11148         endingGame = 0; /* [HGM] crash */
11149         return;
11150     }
11151
11152     if (first.reuse) {
11153         /* Put first chess program into idle state */
11154         if (first.pr != NoProc &&
11155             (gameMode == MachinePlaysWhite ||
11156              gameMode == MachinePlaysBlack ||
11157              gameMode == TwoMachinesPlay ||
11158              gameMode == IcsPlayingWhite ||
11159              gameMode == IcsPlayingBlack ||
11160              gameMode == BeginningOfGame)) {
11161             SendToProgram("force\n", &first);
11162             if (first.usePing) {
11163               char buf[MSG_SIZ];
11164               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11165               SendToProgram(buf, &first);
11166             }
11167         }
11168     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11169         /* Kill off first chess program */
11170         if (first.isr != NULL)
11171           RemoveInputSource(first.isr);
11172         first.isr = NULL;
11173
11174         if (first.pr != NoProc) {
11175             ExitAnalyzeMode();
11176             DoSleep( appData.delayBeforeQuit );
11177             SendToProgram("quit\n", &first);
11178             DoSleep( appData.delayAfterQuit );
11179             DestroyChildProcess(first.pr, first.useSigterm);
11180             first.reload = TRUE;
11181         }
11182         first.pr = NoProc;
11183     }
11184     if (second.reuse) {
11185         /* Put second chess program into idle state */
11186         if (second.pr != NoProc &&
11187             gameMode == TwoMachinesPlay) {
11188             SendToProgram("force\n", &second);
11189             if (second.usePing) {
11190               char buf[MSG_SIZ];
11191               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11192               SendToProgram(buf, &second);
11193             }
11194         }
11195     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11196         /* Kill off second chess program */
11197         if (second.isr != NULL)
11198           RemoveInputSource(second.isr);
11199         second.isr = NULL;
11200
11201         if (second.pr != NoProc) {
11202             DoSleep( appData.delayBeforeQuit );
11203             SendToProgram("quit\n", &second);
11204             DoSleep( appData.delayAfterQuit );
11205             DestroyChildProcess(second.pr, second.useSigterm);
11206             second.reload = TRUE;
11207         }
11208         second.pr = NoProc;
11209     }
11210
11211     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11212         char resChar = '=';
11213         switch (result) {
11214         case WhiteWins:
11215           resChar = '+';
11216           if (first.twoMachinesColor[0] == 'w') {
11217             first.matchWins++;
11218           } else {
11219             second.matchWins++;
11220           }
11221           break;
11222         case BlackWins:
11223           resChar = '-';
11224           if (first.twoMachinesColor[0] == 'b') {
11225             first.matchWins++;
11226           } else {
11227             second.matchWins++;
11228           }
11229           break;
11230         case GameUnfinished:
11231           resChar = ' ';
11232         default:
11233           break;
11234         }
11235
11236         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11237         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11238             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11239             ReserveGame(nextGame, resChar); // sets nextGame
11240             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11241             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11242         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11243
11244         if (nextGame <= appData.matchGames && !abortMatch) {
11245             gameMode = nextGameMode;
11246             matchGame = nextGame; // this will be overruled in tourney mode!
11247             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11248             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11249             endingGame = 0; /* [HGM] crash */
11250             return;
11251         } else {
11252             gameMode = nextGameMode;
11253             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11254                      first.tidy, second.tidy,
11255                      first.matchWins, second.matchWins,
11256                      appData.matchGames - (first.matchWins + second.matchWins));
11257             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11258             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11259             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11260             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11261                 first.twoMachinesColor = "black\n";
11262                 second.twoMachinesColor = "white\n";
11263             } else {
11264                 first.twoMachinesColor = "white\n";
11265                 second.twoMachinesColor = "black\n";
11266             }
11267         }
11268     }
11269     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11270         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11271       ExitAnalyzeMode();
11272     gameMode = nextGameMode;
11273     ModeHighlight();
11274     endingGame = 0;  /* [HGM] crash */
11275     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11276         if(matchMode == TRUE) { // match through command line: exit with or without popup
11277             if(ranking) {
11278                 ToNrEvent(forwardMostMove);
11279                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11280                 else ExitEvent(0);
11281             } else DisplayFatalError(buf, 0, 0);
11282         } else { // match through menu; just stop, with or without popup
11283             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11284             ModeHighlight();
11285             if(ranking){
11286                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11287             } else DisplayNote(buf);
11288       }
11289       if(ranking) free(ranking);
11290     }
11291 }
11292
11293 /* Assumes program was just initialized (initString sent).
11294    Leaves program in force mode. */
11295 void
11296 FeedMovesToProgram (ChessProgramState *cps, int upto)
11297 {
11298     int i;
11299
11300     if (appData.debugMode)
11301       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11302               startedFromSetupPosition ? "position and " : "",
11303               backwardMostMove, upto, cps->which);
11304     if(currentlyInitializedVariant != gameInfo.variant) {
11305       char buf[MSG_SIZ];
11306         // [HGM] variantswitch: make engine aware of new variant
11307         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11308                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11309         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11310         SendToProgram(buf, cps);
11311         currentlyInitializedVariant = gameInfo.variant;
11312     }
11313     SendToProgram("force\n", cps);
11314     if (startedFromSetupPosition) {
11315         SendBoard(cps, backwardMostMove);
11316     if (appData.debugMode) {
11317         fprintf(debugFP, "feedMoves\n");
11318     }
11319     }
11320     for (i = backwardMostMove; i < upto; i++) {
11321         SendMoveToProgram(i, cps);
11322     }
11323 }
11324
11325
11326 int
11327 ResurrectChessProgram ()
11328 {
11329      /* The chess program may have exited.
11330         If so, restart it and feed it all the moves made so far. */
11331     static int doInit = 0;
11332
11333     if (appData.noChessProgram) return 1;
11334
11335     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11336         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11337         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11338         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11339     } else {
11340         if (first.pr != NoProc) return 1;
11341         StartChessProgram(&first);
11342     }
11343     InitChessProgram(&first, FALSE);
11344     FeedMovesToProgram(&first, currentMove);
11345
11346     if (!first.sendTime) {
11347         /* can't tell gnuchess what its clock should read,
11348            so we bow to its notion. */
11349         ResetClocks();
11350         timeRemaining[0][currentMove] = whiteTimeRemaining;
11351         timeRemaining[1][currentMove] = blackTimeRemaining;
11352     }
11353
11354     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11355                 appData.icsEngineAnalyze) && first.analysisSupport) {
11356       SendToProgram("analyze\n", &first);
11357       first.analyzing = TRUE;
11358     }
11359     return 1;
11360 }
11361
11362 /*
11363  * Button procedures
11364  */
11365 void
11366 Reset (int redraw, int init)
11367 {
11368     int i;
11369
11370     if (appData.debugMode) {
11371         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11372                 redraw, init, gameMode);
11373     }
11374     CleanupTail(); // [HGM] vari: delete any stored variations
11375     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11376     pausing = pauseExamInvalid = FALSE;
11377     startedFromSetupPosition = blackPlaysFirst = FALSE;
11378     firstMove = TRUE;
11379     whiteFlag = blackFlag = FALSE;
11380     userOfferedDraw = FALSE;
11381     hintRequested = bookRequested = FALSE;
11382     first.maybeThinking = FALSE;
11383     second.maybeThinking = FALSE;
11384     first.bookSuspend = FALSE; // [HGM] book
11385     second.bookSuspend = FALSE;
11386     thinkOutput[0] = NULLCHAR;
11387     lastHint[0] = NULLCHAR;
11388     ClearGameInfo(&gameInfo);
11389     gameInfo.variant = StringToVariant(appData.variant);
11390     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11391     ics_user_moved = ics_clock_paused = FALSE;
11392     ics_getting_history = H_FALSE;
11393     ics_gamenum = -1;
11394     white_holding[0] = black_holding[0] = NULLCHAR;
11395     ClearProgramStats();
11396     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11397
11398     ResetFrontEnd();
11399     ClearHighlights();
11400     flipView = appData.flipView;
11401     ClearPremoveHighlights();
11402     gotPremove = FALSE;
11403     alarmSounded = FALSE;
11404     killX = killY = -1; // [HGM] lion
11405
11406     GameEnds(EndOfFile, NULL, GE_PLAYER);
11407     if(appData.serverMovesName != NULL) {
11408         /* [HGM] prepare to make moves file for broadcasting */
11409         clock_t t = clock();
11410         if(serverMoves != NULL) fclose(serverMoves);
11411         serverMoves = fopen(appData.serverMovesName, "r");
11412         if(serverMoves != NULL) {
11413             fclose(serverMoves);
11414             /* delay 15 sec before overwriting, so all clients can see end */
11415             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11416         }
11417         serverMoves = fopen(appData.serverMovesName, "w");
11418     }
11419
11420     ExitAnalyzeMode();
11421     gameMode = BeginningOfGame;
11422     ModeHighlight();
11423     if(appData.icsActive) gameInfo.variant = VariantNormal;
11424     currentMove = forwardMostMove = backwardMostMove = 0;
11425     MarkTargetSquares(1);
11426     InitPosition(redraw);
11427     for (i = 0; i < MAX_MOVES; i++) {
11428         if (commentList[i] != NULL) {
11429             free(commentList[i]);
11430             commentList[i] = NULL;
11431         }
11432     }
11433     ResetClocks();
11434     timeRemaining[0][0] = whiteTimeRemaining;
11435     timeRemaining[1][0] = blackTimeRemaining;
11436
11437     if (first.pr == NoProc) {
11438         StartChessProgram(&first);
11439     }
11440     if (init) {
11441             InitChessProgram(&first, startedFromSetupPosition);
11442     }
11443     DisplayTitle("");
11444     DisplayMessage("", "");
11445     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11446     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11447     ClearMap();        // [HGM] exclude: invalidate map
11448 }
11449
11450 void
11451 AutoPlayGameLoop ()
11452 {
11453     for (;;) {
11454         if (!AutoPlayOneMove())
11455           return;
11456         if (matchMode || appData.timeDelay == 0)
11457           continue;
11458         if (appData.timeDelay < 0)
11459           return;
11460         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11461         break;
11462     }
11463 }
11464
11465 void
11466 AnalyzeNextGame()
11467 {
11468     ReloadGame(1); // next game
11469 }
11470
11471 int
11472 AutoPlayOneMove ()
11473 {
11474     int fromX, fromY, toX, toY;
11475
11476     if (appData.debugMode) {
11477       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11478     }
11479
11480     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11481       return FALSE;
11482
11483     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11484       pvInfoList[currentMove].depth = programStats.depth;
11485       pvInfoList[currentMove].score = programStats.score;
11486       pvInfoList[currentMove].time  = 0;
11487       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11488       else { // append analysis of final position as comment
11489         char buf[MSG_SIZ];
11490         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11491         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11492       }
11493       programStats.depth = 0;
11494     }
11495
11496     if (currentMove >= forwardMostMove) {
11497       if(gameMode == AnalyzeFile) {
11498           if(appData.loadGameIndex == -1) {
11499             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11500           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11501           } else {
11502           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11503         }
11504       }
11505 //      gameMode = EndOfGame;
11506 //      ModeHighlight();
11507
11508       /* [AS] Clear current move marker at the end of a game */
11509       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11510
11511       return FALSE;
11512     }
11513
11514     toX = moveList[currentMove][2] - AAA;
11515     toY = moveList[currentMove][3] - ONE;
11516
11517     if (moveList[currentMove][1] == '@') {
11518         if (appData.highlightLastMove) {
11519             SetHighlights(-1, -1, toX, toY);
11520         }
11521     } else {
11522         fromX = moveList[currentMove][0] - AAA;
11523         fromY = moveList[currentMove][1] - ONE;
11524
11525         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11526
11527         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11528
11529         if (appData.highlightLastMove) {
11530             SetHighlights(fromX, fromY, toX, toY);
11531         }
11532     }
11533     DisplayMove(currentMove);
11534     SendMoveToProgram(currentMove++, &first);
11535     DisplayBothClocks();
11536     DrawPosition(FALSE, boards[currentMove]);
11537     // [HGM] PV info: always display, routine tests if empty
11538     DisplayComment(currentMove - 1, commentList[currentMove]);
11539     return TRUE;
11540 }
11541
11542
11543 int
11544 LoadGameOneMove (ChessMove readAhead)
11545 {
11546     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11547     char promoChar = NULLCHAR;
11548     ChessMove moveType;
11549     char move[MSG_SIZ];
11550     char *p, *q;
11551
11552     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11553         gameMode != AnalyzeMode && gameMode != Training) {
11554         gameFileFP = NULL;
11555         return FALSE;
11556     }
11557
11558     yyboardindex = forwardMostMove;
11559     if (readAhead != EndOfFile) {
11560       moveType = readAhead;
11561     } else {
11562       if (gameFileFP == NULL)
11563           return FALSE;
11564       moveType = (ChessMove) Myylex();
11565     }
11566
11567     done = FALSE;
11568     switch (moveType) {
11569       case Comment:
11570         if (appData.debugMode)
11571           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11572         p = yy_text;
11573
11574         /* append the comment but don't display it */
11575         AppendComment(currentMove, p, FALSE);
11576         return TRUE;
11577
11578       case WhiteCapturesEnPassant:
11579       case BlackCapturesEnPassant:
11580       case WhitePromotion:
11581       case BlackPromotion:
11582       case WhiteNonPromotion:
11583       case BlackNonPromotion:
11584       case NormalMove:
11585       case FirstLeg:
11586       case WhiteKingSideCastle:
11587       case WhiteQueenSideCastle:
11588       case BlackKingSideCastle:
11589       case BlackQueenSideCastle:
11590       case WhiteKingSideCastleWild:
11591       case WhiteQueenSideCastleWild:
11592       case BlackKingSideCastleWild:
11593       case BlackQueenSideCastleWild:
11594       /* PUSH Fabien */
11595       case WhiteHSideCastleFR:
11596       case WhiteASideCastleFR:
11597       case BlackHSideCastleFR:
11598       case BlackASideCastleFR:
11599       /* POP Fabien */
11600         if (appData.debugMode)
11601           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11602         fromX = currentMoveString[0] - AAA;
11603         fromY = currentMoveString[1] - ONE;
11604         toX = currentMoveString[2] - AAA;
11605         toY = currentMoveString[3] - ONE;
11606         promoChar = currentMoveString[4];
11607         if(promoChar == ';') promoChar = NULLCHAR;
11608         break;
11609
11610       case WhiteDrop:
11611       case BlackDrop:
11612         if (appData.debugMode)
11613           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11614         fromX = moveType == WhiteDrop ?
11615           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11616         (int) CharToPiece(ToLower(currentMoveString[0]));
11617         fromY = DROP_RANK;
11618         toX = currentMoveString[2] - AAA;
11619         toY = currentMoveString[3] - ONE;
11620         break;
11621
11622       case WhiteWins:
11623       case BlackWins:
11624       case GameIsDrawn:
11625       case GameUnfinished:
11626         if (appData.debugMode)
11627           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11628         p = strchr(yy_text, '{');
11629         if (p == NULL) p = strchr(yy_text, '(');
11630         if (p == NULL) {
11631             p = yy_text;
11632             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11633         } else {
11634             q = strchr(p, *p == '{' ? '}' : ')');
11635             if (q != NULL) *q = NULLCHAR;
11636             p++;
11637         }
11638         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11639         GameEnds(moveType, p, GE_FILE);
11640         done = TRUE;
11641         if (cmailMsgLoaded) {
11642             ClearHighlights();
11643             flipView = WhiteOnMove(currentMove);
11644             if (moveType == GameUnfinished) flipView = !flipView;
11645             if (appData.debugMode)
11646               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11647         }
11648         break;
11649
11650       case EndOfFile:
11651         if (appData.debugMode)
11652           fprintf(debugFP, "Parser hit end of file\n");
11653         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11654           case MT_NONE:
11655           case MT_CHECK:
11656             break;
11657           case MT_CHECKMATE:
11658           case MT_STAINMATE:
11659             if (WhiteOnMove(currentMove)) {
11660                 GameEnds(BlackWins, "Black mates", GE_FILE);
11661             } else {
11662                 GameEnds(WhiteWins, "White mates", GE_FILE);
11663             }
11664             break;
11665           case MT_STALEMATE:
11666             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11667             break;
11668         }
11669         done = TRUE;
11670         break;
11671
11672       case MoveNumberOne:
11673         if (lastLoadGameStart == GNUChessGame) {
11674             /* GNUChessGames have numbers, but they aren't move numbers */
11675             if (appData.debugMode)
11676               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11677                       yy_text, (int) moveType);
11678             return LoadGameOneMove(EndOfFile); /* tail recursion */
11679         }
11680         /* else fall thru */
11681
11682       case XBoardGame:
11683       case GNUChessGame:
11684       case PGNTag:
11685         /* Reached start of next game in file */
11686         if (appData.debugMode)
11687           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11688         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11689           case MT_NONE:
11690           case MT_CHECK:
11691             break;
11692           case MT_CHECKMATE:
11693           case MT_STAINMATE:
11694             if (WhiteOnMove(currentMove)) {
11695                 GameEnds(BlackWins, "Black mates", GE_FILE);
11696             } else {
11697                 GameEnds(WhiteWins, "White mates", GE_FILE);
11698             }
11699             break;
11700           case MT_STALEMATE:
11701             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11702             break;
11703         }
11704         done = TRUE;
11705         break;
11706
11707       case PositionDiagram:     /* should not happen; ignore */
11708       case ElapsedTime:         /* ignore */
11709       case NAG:                 /* ignore */
11710         if (appData.debugMode)
11711           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11712                   yy_text, (int) moveType);
11713         return LoadGameOneMove(EndOfFile); /* tail recursion */
11714
11715       case IllegalMove:
11716         if (appData.testLegality) {
11717             if (appData.debugMode)
11718               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11719             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11720                     (forwardMostMove / 2) + 1,
11721                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11722             DisplayError(move, 0);
11723             done = TRUE;
11724         } else {
11725             if (appData.debugMode)
11726               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11727                       yy_text, currentMoveString);
11728             fromX = currentMoveString[0] - AAA;
11729             fromY = currentMoveString[1] - ONE;
11730             toX = currentMoveString[2] - AAA;
11731             toY = currentMoveString[3] - ONE;
11732             promoChar = currentMoveString[4];
11733         }
11734         break;
11735
11736       case AmbiguousMove:
11737         if (appData.debugMode)
11738           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11739         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11740                 (forwardMostMove / 2) + 1,
11741                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11742         DisplayError(move, 0);
11743         done = TRUE;
11744         break;
11745
11746       default:
11747       case ImpossibleMove:
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11750         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11751                 (forwardMostMove / 2) + 1,
11752                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11753         DisplayError(move, 0);
11754         done = TRUE;
11755         break;
11756     }
11757
11758     if (done) {
11759         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11760             DrawPosition(FALSE, boards[currentMove]);
11761             DisplayBothClocks();
11762             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11763               DisplayComment(currentMove - 1, commentList[currentMove]);
11764         }
11765         (void) StopLoadGameTimer();
11766         gameFileFP = NULL;
11767         cmailOldMove = forwardMostMove;
11768         return FALSE;
11769     } else {
11770         /* currentMoveString is set as a side-effect of yylex */
11771
11772         thinkOutput[0] = NULLCHAR;
11773         MakeMove(fromX, fromY, toX, toY, promoChar);
11774         killX = killY = -1; // [HGM] lion: used up
11775         currentMove = forwardMostMove;
11776         return TRUE;
11777     }
11778 }
11779
11780 /* Load the nth game from the given file */
11781 int
11782 LoadGameFromFile (char *filename, int n, char *title, int useList)
11783 {
11784     FILE *f;
11785     char buf[MSG_SIZ];
11786
11787     if (strcmp(filename, "-") == 0) {
11788         f = stdin;
11789         title = "stdin";
11790     } else {
11791         f = fopen(filename, "rb");
11792         if (f == NULL) {
11793           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11794             DisplayError(buf, errno);
11795             return FALSE;
11796         }
11797     }
11798     if (fseek(f, 0, 0) == -1) {
11799         /* f is not seekable; probably a pipe */
11800         useList = FALSE;
11801     }
11802     if (useList && n == 0) {
11803         int error = GameListBuild(f);
11804         if (error) {
11805             DisplayError(_("Cannot build game list"), error);
11806         } else if (!ListEmpty(&gameList) &&
11807                    ((ListGame *) gameList.tailPred)->number > 1) {
11808             GameListPopUp(f, title);
11809             return TRUE;
11810         }
11811         GameListDestroy();
11812         n = 1;
11813     }
11814     if (n == 0) n = 1;
11815     return LoadGame(f, n, title, FALSE);
11816 }
11817
11818
11819 void
11820 MakeRegisteredMove ()
11821 {
11822     int fromX, fromY, toX, toY;
11823     char promoChar;
11824     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11825         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11826           case CMAIL_MOVE:
11827           case CMAIL_DRAW:
11828             if (appData.debugMode)
11829               fprintf(debugFP, "Restoring %s for game %d\n",
11830                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11831
11832             thinkOutput[0] = NULLCHAR;
11833             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11834             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11835             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11836             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11837             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11838             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11839             MakeMove(fromX, fromY, toX, toY, promoChar);
11840             ShowMove(fromX, fromY, toX, toY);
11841
11842             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11843               case MT_NONE:
11844               case MT_CHECK:
11845                 break;
11846
11847               case MT_CHECKMATE:
11848               case MT_STAINMATE:
11849                 if (WhiteOnMove(currentMove)) {
11850                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11851                 } else {
11852                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11853                 }
11854                 break;
11855
11856               case MT_STALEMATE:
11857                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11858                 break;
11859             }
11860
11861             break;
11862
11863           case CMAIL_RESIGN:
11864             if (WhiteOnMove(currentMove)) {
11865                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11866             } else {
11867                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11868             }
11869             break;
11870
11871           case CMAIL_ACCEPT:
11872             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11873             break;
11874
11875           default:
11876             break;
11877         }
11878     }
11879
11880     return;
11881 }
11882
11883 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11884 int
11885 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11886 {
11887     int retVal;
11888
11889     if (gameNumber > nCmailGames) {
11890         DisplayError(_("No more games in this message"), 0);
11891         return FALSE;
11892     }
11893     if (f == lastLoadGameFP) {
11894         int offset = gameNumber - lastLoadGameNumber;
11895         if (offset == 0) {
11896             cmailMsg[0] = NULLCHAR;
11897             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11898                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11899                 nCmailMovesRegistered--;
11900             }
11901             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11902             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11903                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11904             }
11905         } else {
11906             if (! RegisterMove()) return FALSE;
11907         }
11908     }
11909
11910     retVal = LoadGame(f, gameNumber, title, useList);
11911
11912     /* Make move registered during previous look at this game, if any */
11913     MakeRegisteredMove();
11914
11915     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11916         commentList[currentMove]
11917           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11918         DisplayComment(currentMove - 1, commentList[currentMove]);
11919     }
11920
11921     return retVal;
11922 }
11923
11924 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11925 int
11926 ReloadGame (int offset)
11927 {
11928     int gameNumber = lastLoadGameNumber + offset;
11929     if (lastLoadGameFP == NULL) {
11930         DisplayError(_("No game has been loaded yet"), 0);
11931         return FALSE;
11932     }
11933     if (gameNumber <= 0) {
11934         DisplayError(_("Can't back up any further"), 0);
11935         return FALSE;
11936     }
11937     if (cmailMsgLoaded) {
11938         return CmailLoadGame(lastLoadGameFP, gameNumber,
11939                              lastLoadGameTitle, lastLoadGameUseList);
11940     } else {
11941         return LoadGame(lastLoadGameFP, gameNumber,
11942                         lastLoadGameTitle, lastLoadGameUseList);
11943     }
11944 }
11945
11946 int keys[EmptySquare+1];
11947
11948 int
11949 PositionMatches (Board b1, Board b2)
11950 {
11951     int r, f, sum=0;
11952     switch(appData.searchMode) {
11953         case 1: return CompareWithRights(b1, b2);
11954         case 2:
11955             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11956                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11957             }
11958             return TRUE;
11959         case 3:
11960             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11961               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11962                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11963             }
11964             return sum==0;
11965         case 4:
11966             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11967                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11968             }
11969             return sum==0;
11970     }
11971     return TRUE;
11972 }
11973
11974 #define Q_PROMO  4
11975 #define Q_EP     3
11976 #define Q_BCASTL 2
11977 #define Q_WCASTL 1
11978
11979 int pieceList[256], quickBoard[256];
11980 ChessSquare pieceType[256] = { EmptySquare };
11981 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11982 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11983 int soughtTotal, turn;
11984 Boolean epOK, flipSearch;
11985
11986 typedef struct {
11987     unsigned char piece, to;
11988 } Move;
11989
11990 #define DSIZE (250000)
11991
11992 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11993 Move *moveDatabase = initialSpace;
11994 unsigned int movePtr, dataSize = DSIZE;
11995
11996 int
11997 MakePieceList (Board board, int *counts)
11998 {
11999     int r, f, n=Q_PROMO, total=0;
12000     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12001     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12002         int sq = f + (r<<4);
12003         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12004             quickBoard[sq] = ++n;
12005             pieceList[n] = sq;
12006             pieceType[n] = board[r][f];
12007             counts[board[r][f]]++;
12008             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12009             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12010             total++;
12011         }
12012     }
12013     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12014     return total;
12015 }
12016
12017 void
12018 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12019 {
12020     int sq = fromX + (fromY<<4);
12021     int piece = quickBoard[sq];
12022     quickBoard[sq] = 0;
12023     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12024     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12025         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12026         moveDatabase[movePtr++].piece = Q_WCASTL;
12027         quickBoard[sq] = piece;
12028         piece = quickBoard[from]; quickBoard[from] = 0;
12029         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12030     } else
12031     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12032         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12033         moveDatabase[movePtr++].piece = Q_BCASTL;
12034         quickBoard[sq] = piece;
12035         piece = quickBoard[from]; quickBoard[from] = 0;
12036         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12037     } else
12038     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12039         quickBoard[(fromY<<4)+toX] = 0;
12040         moveDatabase[movePtr].piece = Q_EP;
12041         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12042         moveDatabase[movePtr].to = sq;
12043     } else
12044     if(promoPiece != pieceType[piece]) {
12045         moveDatabase[movePtr++].piece = Q_PROMO;
12046         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12047     }
12048     moveDatabase[movePtr].piece = piece;
12049     quickBoard[sq] = piece;
12050     movePtr++;
12051 }
12052
12053 int
12054 PackGame (Board board)
12055 {
12056     Move *newSpace = NULL;
12057     moveDatabase[movePtr].piece = 0; // terminate previous game
12058     if(movePtr > dataSize) {
12059         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12060         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12061         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12062         if(newSpace) {
12063             int i;
12064             Move *p = moveDatabase, *q = newSpace;
12065             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12066             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12067             moveDatabase = newSpace;
12068         } else { // calloc failed, we must be out of memory. Too bad...
12069             dataSize = 0; // prevent calloc events for all subsequent games
12070             return 0;     // and signal this one isn't cached
12071         }
12072     }
12073     movePtr++;
12074     MakePieceList(board, counts);
12075     return movePtr;
12076 }
12077
12078 int
12079 QuickCompare (Board board, int *minCounts, int *maxCounts)
12080 {   // compare according to search mode
12081     int r, f;
12082     switch(appData.searchMode)
12083     {
12084       case 1: // exact position match
12085         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12086         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12087             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12088         }
12089         break;
12090       case 2: // can have extra material on empty squares
12091         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12092             if(board[r][f] == EmptySquare) continue;
12093             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12094         }
12095         break;
12096       case 3: // material with exact Pawn structure
12097         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12098             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12099             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12100         } // fall through to material comparison
12101       case 4: // exact material
12102         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12103         break;
12104       case 6: // material range with given imbalance
12105         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12106         // fall through to range comparison
12107       case 5: // material range
12108         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12109     }
12110     return TRUE;
12111 }
12112
12113 int
12114 QuickScan (Board board, Move *move)
12115 {   // reconstruct game,and compare all positions in it
12116     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12117     do {
12118         int piece = move->piece;
12119         int to = move->to, from = pieceList[piece];
12120         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12121           if(!piece) return -1;
12122           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12123             piece = (++move)->piece;
12124             from = pieceList[piece];
12125             counts[pieceType[piece]]--;
12126             pieceType[piece] = (ChessSquare) move->to;
12127             counts[move->to]++;
12128           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12129             counts[pieceType[quickBoard[to]]]--;
12130             quickBoard[to] = 0; total--;
12131             move++;
12132             continue;
12133           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12134             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12135             from  = pieceList[piece]; // so this must be King
12136             quickBoard[from] = 0;
12137             pieceList[piece] = to;
12138             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12139             quickBoard[from] = 0; // rook
12140             quickBoard[to] = piece;
12141             to = move->to; piece = move->piece;
12142             goto aftercastle;
12143           }
12144         }
12145         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12146         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12147         quickBoard[from] = 0;
12148       aftercastle:
12149         quickBoard[to] = piece;
12150         pieceList[piece] = to;
12151         cnt++; turn ^= 3;
12152         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12153            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12154            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12155                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12156           ) {
12157             static int lastCounts[EmptySquare+1];
12158             int i;
12159             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12160             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12161         } else stretch = 0;
12162         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12163         move++;
12164     } while(1);
12165 }
12166
12167 void
12168 InitSearch ()
12169 {
12170     int r, f;
12171     flipSearch = FALSE;
12172     CopyBoard(soughtBoard, boards[currentMove]);
12173     soughtTotal = MakePieceList(soughtBoard, maxSought);
12174     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12175     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12176     CopyBoard(reverseBoard, boards[currentMove]);
12177     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12178         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12179         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12180         reverseBoard[r][f] = piece;
12181     }
12182     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12183     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12184     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12185                  || (boards[currentMove][CASTLING][2] == NoRights ||
12186                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12187                  && (boards[currentMove][CASTLING][5] == NoRights ||
12188                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12189       ) {
12190         flipSearch = TRUE;
12191         CopyBoard(flipBoard, soughtBoard);
12192         CopyBoard(rotateBoard, reverseBoard);
12193         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12195             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12196         }
12197     }
12198     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12199     if(appData.searchMode >= 5) {
12200         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12201         MakePieceList(soughtBoard, minSought);
12202         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12203     }
12204     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12205         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12206 }
12207
12208 GameInfo dummyInfo;
12209 static int creatingBook;
12210
12211 int
12212 GameContainsPosition (FILE *f, ListGame *lg)
12213 {
12214     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12215     int fromX, fromY, toX, toY;
12216     char promoChar;
12217     static int initDone=FALSE;
12218
12219     // weed out games based on numerical tag comparison
12220     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12221     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12222     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12223     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12224     if(!initDone) {
12225         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12226         initDone = TRUE;
12227     }
12228     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12229     else CopyBoard(boards[scratch], initialPosition); // default start position
12230     if(lg->moves) {
12231         turn = btm + 1;
12232         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12233         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12234     }
12235     if(btm) plyNr++;
12236     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12237     fseek(f, lg->offset, 0);
12238     yynewfile(f);
12239     while(1) {
12240         yyboardindex = scratch;
12241         quickFlag = plyNr+1;
12242         next = Myylex();
12243         quickFlag = 0;
12244         switch(next) {
12245             case PGNTag:
12246                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12247             default:
12248                 continue;
12249
12250             case XBoardGame:
12251             case GNUChessGame:
12252                 if(plyNr) return -1; // after we have seen moves, this is for new game
12253               continue;
12254
12255             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12256             case ImpossibleMove:
12257             case WhiteWins: // game ends here with these four
12258             case BlackWins:
12259             case GameIsDrawn:
12260             case GameUnfinished:
12261                 return -1;
12262
12263             case IllegalMove:
12264                 if(appData.testLegality) return -1;
12265             case WhiteCapturesEnPassant:
12266             case BlackCapturesEnPassant:
12267             case WhitePromotion:
12268             case BlackPromotion:
12269             case WhiteNonPromotion:
12270             case BlackNonPromotion:
12271             case NormalMove:
12272             case FirstLeg:
12273             case WhiteKingSideCastle:
12274             case WhiteQueenSideCastle:
12275             case BlackKingSideCastle:
12276             case BlackQueenSideCastle:
12277             case WhiteKingSideCastleWild:
12278             case WhiteQueenSideCastleWild:
12279             case BlackKingSideCastleWild:
12280             case BlackQueenSideCastleWild:
12281             case WhiteHSideCastleFR:
12282             case WhiteASideCastleFR:
12283             case BlackHSideCastleFR:
12284             case BlackASideCastleFR:
12285                 fromX = currentMoveString[0] - AAA;
12286                 fromY = currentMoveString[1] - ONE;
12287                 toX = currentMoveString[2] - AAA;
12288                 toY = currentMoveString[3] - ONE;
12289                 promoChar = currentMoveString[4];
12290                 break;
12291             case WhiteDrop:
12292             case BlackDrop:
12293                 fromX = next == WhiteDrop ?
12294                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12295                   (int) CharToPiece(ToLower(currentMoveString[0]));
12296                 fromY = DROP_RANK;
12297                 toX = currentMoveString[2] - AAA;
12298                 toY = currentMoveString[3] - ONE;
12299                 promoChar = 0;
12300                 break;
12301         }
12302         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12303         plyNr++;
12304         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12305         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12306         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12307         if(appData.findMirror) {
12308             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12309             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12310         }
12311     }
12312 }
12313
12314 /* Load the nth game from open file f */
12315 int
12316 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12317 {
12318     ChessMove cm;
12319     char buf[MSG_SIZ];
12320     int gn = gameNumber;
12321     ListGame *lg = NULL;
12322     int numPGNTags = 0;
12323     int err, pos = -1;
12324     GameMode oldGameMode;
12325     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12326
12327     if (appData.debugMode)
12328         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12329
12330     if (gameMode == Training )
12331         SetTrainingModeOff();
12332
12333     oldGameMode = gameMode;
12334     if (gameMode != BeginningOfGame) {
12335       Reset(FALSE, TRUE);
12336     }
12337     killX = killY = -1; // [HGM] lion: in case we did not Reset
12338
12339     gameFileFP = f;
12340     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12341         fclose(lastLoadGameFP);
12342     }
12343
12344     if (useList) {
12345         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12346
12347         if (lg) {
12348             fseek(f, lg->offset, 0);
12349             GameListHighlight(gameNumber);
12350             pos = lg->position;
12351             gn = 1;
12352         }
12353         else {
12354             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12355               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12356             else
12357             DisplayError(_("Game number out of range"), 0);
12358             return FALSE;
12359         }
12360     } else {
12361         GameListDestroy();
12362         if (fseek(f, 0, 0) == -1) {
12363             if (f == lastLoadGameFP ?
12364                 gameNumber == lastLoadGameNumber + 1 :
12365                 gameNumber == 1) {
12366                 gn = 1;
12367             } else {
12368                 DisplayError(_("Can't seek on game file"), 0);
12369                 return FALSE;
12370             }
12371         }
12372     }
12373     lastLoadGameFP = f;
12374     lastLoadGameNumber = gameNumber;
12375     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12376     lastLoadGameUseList = useList;
12377
12378     yynewfile(f);
12379
12380     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12381       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12382                 lg->gameInfo.black);
12383             DisplayTitle(buf);
12384     } else if (*title != NULLCHAR) {
12385         if (gameNumber > 1) {
12386           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12387             DisplayTitle(buf);
12388         } else {
12389             DisplayTitle(title);
12390         }
12391     }
12392
12393     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12394         gameMode = PlayFromGameFile;
12395         ModeHighlight();
12396     }
12397
12398     currentMove = forwardMostMove = backwardMostMove = 0;
12399     CopyBoard(boards[0], initialPosition);
12400     StopClocks();
12401
12402     /*
12403      * Skip the first gn-1 games in the file.
12404      * Also skip over anything that precedes an identifiable
12405      * start of game marker, to avoid being confused by
12406      * garbage at the start of the file.  Currently
12407      * recognized start of game markers are the move number "1",
12408      * the pattern "gnuchess .* game", the pattern
12409      * "^[#;%] [^ ]* game file", and a PGN tag block.
12410      * A game that starts with one of the latter two patterns
12411      * will also have a move number 1, possibly
12412      * following a position diagram.
12413      * 5-4-02: Let's try being more lenient and allowing a game to
12414      * start with an unnumbered move.  Does that break anything?
12415      */
12416     cm = lastLoadGameStart = EndOfFile;
12417     while (gn > 0) {
12418         yyboardindex = forwardMostMove;
12419         cm = (ChessMove) Myylex();
12420         switch (cm) {
12421           case EndOfFile:
12422             if (cmailMsgLoaded) {
12423                 nCmailGames = CMAIL_MAX_GAMES - gn;
12424             } else {
12425                 Reset(TRUE, TRUE);
12426                 DisplayError(_("Game not found in file"), 0);
12427             }
12428             return FALSE;
12429
12430           case GNUChessGame:
12431           case XBoardGame:
12432             gn--;
12433             lastLoadGameStart = cm;
12434             break;
12435
12436           case MoveNumberOne:
12437             switch (lastLoadGameStart) {
12438               case GNUChessGame:
12439               case XBoardGame:
12440               case PGNTag:
12441                 break;
12442               case MoveNumberOne:
12443               case EndOfFile:
12444                 gn--;           /* count this game */
12445                 lastLoadGameStart = cm;
12446                 break;
12447               default:
12448                 /* impossible */
12449                 break;
12450             }
12451             break;
12452
12453           case PGNTag:
12454             switch (lastLoadGameStart) {
12455               case GNUChessGame:
12456               case PGNTag:
12457               case MoveNumberOne:
12458               case EndOfFile:
12459                 gn--;           /* count this game */
12460                 lastLoadGameStart = cm;
12461                 break;
12462               case XBoardGame:
12463                 lastLoadGameStart = cm; /* game counted already */
12464                 break;
12465               default:
12466                 /* impossible */
12467                 break;
12468             }
12469             if (gn > 0) {
12470                 do {
12471                     yyboardindex = forwardMostMove;
12472                     cm = (ChessMove) Myylex();
12473                 } while (cm == PGNTag || cm == Comment);
12474             }
12475             break;
12476
12477           case WhiteWins:
12478           case BlackWins:
12479           case GameIsDrawn:
12480             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12481                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12482                     != CMAIL_OLD_RESULT) {
12483                     nCmailResults ++ ;
12484                     cmailResult[  CMAIL_MAX_GAMES
12485                                 - gn - 1] = CMAIL_OLD_RESULT;
12486                 }
12487             }
12488             break;
12489
12490           case NormalMove:
12491           case FirstLeg:
12492             /* Only a NormalMove can be at the start of a game
12493              * without a position diagram. */
12494             if (lastLoadGameStart == EndOfFile ) {
12495               gn--;
12496               lastLoadGameStart = MoveNumberOne;
12497             }
12498             break;
12499
12500           default:
12501             break;
12502         }
12503     }
12504
12505     if (appData.debugMode)
12506       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12507
12508     if (cm == XBoardGame) {
12509         /* Skip any header junk before position diagram and/or move 1 */
12510         for (;;) {
12511             yyboardindex = forwardMostMove;
12512             cm = (ChessMove) Myylex();
12513
12514             if (cm == EndOfFile ||
12515                 cm == GNUChessGame || cm == XBoardGame) {
12516                 /* Empty game; pretend end-of-file and handle later */
12517                 cm = EndOfFile;
12518                 break;
12519             }
12520
12521             if (cm == MoveNumberOne || cm == PositionDiagram ||
12522                 cm == PGNTag || cm == Comment)
12523               break;
12524         }
12525     } else if (cm == GNUChessGame) {
12526         if (gameInfo.event != NULL) {
12527             free(gameInfo.event);
12528         }
12529         gameInfo.event = StrSave(yy_text);
12530     }
12531
12532     startedFromSetupPosition = FALSE;
12533     while (cm == PGNTag) {
12534         if (appData.debugMode)
12535           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12536         err = ParsePGNTag(yy_text, &gameInfo);
12537         if (!err) numPGNTags++;
12538
12539         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12540         if(gameInfo.variant != oldVariant) {
12541             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12542             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12543             InitPosition(TRUE);
12544             oldVariant = gameInfo.variant;
12545             if (appData.debugMode)
12546               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12547         }
12548
12549
12550         if (gameInfo.fen != NULL) {
12551           Board initial_position;
12552           startedFromSetupPosition = TRUE;
12553           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12554             Reset(TRUE, TRUE);
12555             DisplayError(_("Bad FEN position in file"), 0);
12556             return FALSE;
12557           }
12558           CopyBoard(boards[0], initial_position);
12559           if (blackPlaysFirst) {
12560             currentMove = forwardMostMove = backwardMostMove = 1;
12561             CopyBoard(boards[1], initial_position);
12562             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12563             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12564             timeRemaining[0][1] = whiteTimeRemaining;
12565             timeRemaining[1][1] = blackTimeRemaining;
12566             if (commentList[0] != NULL) {
12567               commentList[1] = commentList[0];
12568               commentList[0] = NULL;
12569             }
12570           } else {
12571             currentMove = forwardMostMove = backwardMostMove = 0;
12572           }
12573           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12574           {   int i;
12575               initialRulePlies = FENrulePlies;
12576               for( i=0; i< nrCastlingRights; i++ )
12577                   initialRights[i] = initial_position[CASTLING][i];
12578           }
12579           yyboardindex = forwardMostMove;
12580           free(gameInfo.fen);
12581           gameInfo.fen = NULL;
12582         }
12583
12584         yyboardindex = forwardMostMove;
12585         cm = (ChessMove) Myylex();
12586
12587         /* Handle comments interspersed among the tags */
12588         while (cm == Comment) {
12589             char *p;
12590             if (appData.debugMode)
12591               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12592             p = yy_text;
12593             AppendComment(currentMove, p, FALSE);
12594             yyboardindex = forwardMostMove;
12595             cm = (ChessMove) Myylex();
12596         }
12597     }
12598
12599     /* don't rely on existence of Event tag since if game was
12600      * pasted from clipboard the Event tag may not exist
12601      */
12602     if (numPGNTags > 0){
12603         char *tags;
12604         if (gameInfo.variant == VariantNormal) {
12605           VariantClass v = StringToVariant(gameInfo.event);
12606           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12607           if(v < VariantShogi) gameInfo.variant = v;
12608         }
12609         if (!matchMode) {
12610           if( appData.autoDisplayTags ) {
12611             tags = PGNTags(&gameInfo);
12612             TagsPopUp(tags, CmailMsg());
12613             free(tags);
12614           }
12615         }
12616     } else {
12617         /* Make something up, but don't display it now */
12618         SetGameInfo();
12619         TagsPopDown();
12620     }
12621
12622     if (cm == PositionDiagram) {
12623         int i, j;
12624         char *p;
12625         Board initial_position;
12626
12627         if (appData.debugMode)
12628           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12629
12630         if (!startedFromSetupPosition) {
12631             p = yy_text;
12632             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12633               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12634                 switch (*p) {
12635                   case '{':
12636                   case '[':
12637                   case '-':
12638                   case ' ':
12639                   case '\t':
12640                   case '\n':
12641                   case '\r':
12642                     break;
12643                   default:
12644                     initial_position[i][j++] = CharToPiece(*p);
12645                     break;
12646                 }
12647             while (*p == ' ' || *p == '\t' ||
12648                    *p == '\n' || *p == '\r') p++;
12649
12650             if (strncmp(p, "black", strlen("black"))==0)
12651               blackPlaysFirst = TRUE;
12652             else
12653               blackPlaysFirst = FALSE;
12654             startedFromSetupPosition = TRUE;
12655
12656             CopyBoard(boards[0], initial_position);
12657             if (blackPlaysFirst) {
12658                 currentMove = forwardMostMove = backwardMostMove = 1;
12659                 CopyBoard(boards[1], initial_position);
12660                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12661                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12662                 timeRemaining[0][1] = whiteTimeRemaining;
12663                 timeRemaining[1][1] = blackTimeRemaining;
12664                 if (commentList[0] != NULL) {
12665                     commentList[1] = commentList[0];
12666                     commentList[0] = NULL;
12667                 }
12668             } else {
12669                 currentMove = forwardMostMove = backwardMostMove = 0;
12670             }
12671         }
12672         yyboardindex = forwardMostMove;
12673         cm = (ChessMove) Myylex();
12674     }
12675
12676   if(!creatingBook) {
12677     if (first.pr == NoProc) {
12678         StartChessProgram(&first);
12679     }
12680     InitChessProgram(&first, FALSE);
12681     SendToProgram("force\n", &first);
12682     if (startedFromSetupPosition) {
12683         SendBoard(&first, forwardMostMove);
12684     if (appData.debugMode) {
12685         fprintf(debugFP, "Load Game\n");
12686     }
12687         DisplayBothClocks();
12688     }
12689   }
12690
12691     /* [HGM] server: flag to write setup moves in broadcast file as one */
12692     loadFlag = appData.suppressLoadMoves;
12693
12694     while (cm == Comment) {
12695         char *p;
12696         if (appData.debugMode)
12697           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12698         p = yy_text;
12699         AppendComment(currentMove, p, FALSE);
12700         yyboardindex = forwardMostMove;
12701         cm = (ChessMove) Myylex();
12702     }
12703
12704     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12705         cm == WhiteWins || cm == BlackWins ||
12706         cm == GameIsDrawn || cm == GameUnfinished) {
12707         DisplayMessage("", _("No moves in game"));
12708         if (cmailMsgLoaded) {
12709             if (appData.debugMode)
12710               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12711             ClearHighlights();
12712             flipView = FALSE;
12713         }
12714         DrawPosition(FALSE, boards[currentMove]);
12715         DisplayBothClocks();
12716         gameMode = EditGame;
12717         ModeHighlight();
12718         gameFileFP = NULL;
12719         cmailOldMove = 0;
12720         return TRUE;
12721     }
12722
12723     // [HGM] PV info: routine tests if comment empty
12724     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12725         DisplayComment(currentMove - 1, commentList[currentMove]);
12726     }
12727     if (!matchMode && appData.timeDelay != 0)
12728       DrawPosition(FALSE, boards[currentMove]);
12729
12730     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12731       programStats.ok_to_send = 1;
12732     }
12733
12734     /* if the first token after the PGN tags is a move
12735      * and not move number 1, retrieve it from the parser
12736      */
12737     if (cm != MoveNumberOne)
12738         LoadGameOneMove(cm);
12739
12740     /* load the remaining moves from the file */
12741     while (LoadGameOneMove(EndOfFile)) {
12742       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12743       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12744     }
12745
12746     /* rewind to the start of the game */
12747     currentMove = backwardMostMove;
12748
12749     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12750
12751     if (oldGameMode == AnalyzeFile) {
12752       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12753       AnalyzeFileEvent();
12754     } else
12755     if (oldGameMode == AnalyzeMode) {
12756       AnalyzeFileEvent();
12757     }
12758
12759     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12760         long int w, b; // [HGM] adjourn: restore saved clock times
12761         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12762         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12763             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12764             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12765         }
12766     }
12767
12768     if(creatingBook) return TRUE;
12769     if (!matchMode && pos > 0) {
12770         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12771     } else
12772     if (matchMode || appData.timeDelay == 0) {
12773       ToEndEvent();
12774     } else if (appData.timeDelay > 0) {
12775       AutoPlayGameLoop();
12776     }
12777
12778     if (appData.debugMode)
12779         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12780
12781     loadFlag = 0; /* [HGM] true game starts */
12782     return TRUE;
12783 }
12784
12785 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12786 int
12787 ReloadPosition (int offset)
12788 {
12789     int positionNumber = lastLoadPositionNumber + offset;
12790     if (lastLoadPositionFP == NULL) {
12791         DisplayError(_("No position has been loaded yet"), 0);
12792         return FALSE;
12793     }
12794     if (positionNumber <= 0) {
12795         DisplayError(_("Can't back up any further"), 0);
12796         return FALSE;
12797     }
12798     return LoadPosition(lastLoadPositionFP, positionNumber,
12799                         lastLoadPositionTitle);
12800 }
12801
12802 /* Load the nth position from the given file */
12803 int
12804 LoadPositionFromFile (char *filename, int n, char *title)
12805 {
12806     FILE *f;
12807     char buf[MSG_SIZ];
12808
12809     if (strcmp(filename, "-") == 0) {
12810         return LoadPosition(stdin, n, "stdin");
12811     } else {
12812         f = fopen(filename, "rb");
12813         if (f == NULL) {
12814             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12815             DisplayError(buf, errno);
12816             return FALSE;
12817         } else {
12818             return LoadPosition(f, n, title);
12819         }
12820     }
12821 }
12822
12823 /* Load the nth position from the given open file, and close it */
12824 int
12825 LoadPosition (FILE *f, int positionNumber, char *title)
12826 {
12827     char *p, line[MSG_SIZ];
12828     Board initial_position;
12829     int i, j, fenMode, pn;
12830
12831     if (gameMode == Training )
12832         SetTrainingModeOff();
12833
12834     if (gameMode != BeginningOfGame) {
12835         Reset(FALSE, TRUE);
12836     }
12837     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12838         fclose(lastLoadPositionFP);
12839     }
12840     if (positionNumber == 0) positionNumber = 1;
12841     lastLoadPositionFP = f;
12842     lastLoadPositionNumber = positionNumber;
12843     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12844     if (first.pr == NoProc && !appData.noChessProgram) {
12845       StartChessProgram(&first);
12846       InitChessProgram(&first, FALSE);
12847     }
12848     pn = positionNumber;
12849     if (positionNumber < 0) {
12850         /* Negative position number means to seek to that byte offset */
12851         if (fseek(f, -positionNumber, 0) == -1) {
12852             DisplayError(_("Can't seek on position file"), 0);
12853             return FALSE;
12854         };
12855         pn = 1;
12856     } else {
12857         if (fseek(f, 0, 0) == -1) {
12858             if (f == lastLoadPositionFP ?
12859                 positionNumber == lastLoadPositionNumber + 1 :
12860                 positionNumber == 1) {
12861                 pn = 1;
12862             } else {
12863                 DisplayError(_("Can't seek on position file"), 0);
12864                 return FALSE;
12865             }
12866         }
12867     }
12868     /* See if this file is FEN or old-style xboard */
12869     if (fgets(line, MSG_SIZ, f) == NULL) {
12870         DisplayError(_("Position not found in file"), 0);
12871         return FALSE;
12872     }
12873     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12874     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12875
12876     if (pn >= 2) {
12877         if (fenMode || line[0] == '#') pn--;
12878         while (pn > 0) {
12879             /* skip positions before number pn */
12880             if (fgets(line, MSG_SIZ, f) == NULL) {
12881                 Reset(TRUE, TRUE);
12882                 DisplayError(_("Position not found in file"), 0);
12883                 return FALSE;
12884             }
12885             if (fenMode || line[0] == '#') pn--;
12886         }
12887     }
12888
12889     if (fenMode) {
12890         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12891             DisplayError(_("Bad FEN position in file"), 0);
12892             return FALSE;
12893         }
12894     } else {
12895         (void) fgets(line, MSG_SIZ, f);
12896         (void) fgets(line, MSG_SIZ, f);
12897
12898         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12899             (void) fgets(line, MSG_SIZ, f);
12900             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12901                 if (*p == ' ')
12902                   continue;
12903                 initial_position[i][j++] = CharToPiece(*p);
12904             }
12905         }
12906
12907         blackPlaysFirst = FALSE;
12908         if (!feof(f)) {
12909             (void) fgets(line, MSG_SIZ, f);
12910             if (strncmp(line, "black", strlen("black"))==0)
12911               blackPlaysFirst = TRUE;
12912         }
12913     }
12914     startedFromSetupPosition = TRUE;
12915
12916     CopyBoard(boards[0], initial_position);
12917     if (blackPlaysFirst) {
12918         currentMove = forwardMostMove = backwardMostMove = 1;
12919         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12920         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12921         CopyBoard(boards[1], initial_position);
12922         DisplayMessage("", _("Black to play"));
12923     } else {
12924         currentMove = forwardMostMove = backwardMostMove = 0;
12925         DisplayMessage("", _("White to play"));
12926     }
12927     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12928     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12929         SendToProgram("force\n", &first);
12930         SendBoard(&first, forwardMostMove);
12931     }
12932     if (appData.debugMode) {
12933 int i, j;
12934   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12935   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12936         fprintf(debugFP, "Load Position\n");
12937     }
12938
12939     if (positionNumber > 1) {
12940       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12941         DisplayTitle(line);
12942     } else {
12943         DisplayTitle(title);
12944     }
12945     gameMode = EditGame;
12946     ModeHighlight();
12947     ResetClocks();
12948     timeRemaining[0][1] = whiteTimeRemaining;
12949     timeRemaining[1][1] = blackTimeRemaining;
12950     DrawPosition(FALSE, boards[currentMove]);
12951
12952     return TRUE;
12953 }
12954
12955
12956 void
12957 CopyPlayerNameIntoFileName (char **dest, char *src)
12958 {
12959     while (*src != NULLCHAR && *src != ',') {
12960         if (*src == ' ') {
12961             *(*dest)++ = '_';
12962             src++;
12963         } else {
12964             *(*dest)++ = *src++;
12965         }
12966     }
12967 }
12968
12969 char *
12970 DefaultFileName (char *ext)
12971 {
12972     static char def[MSG_SIZ];
12973     char *p;
12974
12975     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12976         p = def;
12977         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12978         *p++ = '-';
12979         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12980         *p++ = '.';
12981         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12982     } else {
12983         def[0] = NULLCHAR;
12984     }
12985     return def;
12986 }
12987
12988 /* Save the current game to the given file */
12989 int
12990 SaveGameToFile (char *filename, int append)
12991 {
12992     FILE *f;
12993     char buf[MSG_SIZ];
12994     int result, i, t,tot=0;
12995
12996     if (strcmp(filename, "-") == 0) {
12997         return SaveGame(stdout, 0, NULL);
12998     } else {
12999         for(i=0; i<10; i++) { // upto 10 tries
13000              f = fopen(filename, append ? "a" : "w");
13001              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13002              if(f || errno != 13) break;
13003              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13004              tot += t;
13005         }
13006         if (f == NULL) {
13007             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13008             DisplayError(buf, errno);
13009             return FALSE;
13010         } else {
13011             safeStrCpy(buf, lastMsg, MSG_SIZ);
13012             DisplayMessage(_("Waiting for access to save file"), "");
13013             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13014             DisplayMessage(_("Saving game"), "");
13015             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13016             result = SaveGame(f, 0, NULL);
13017             DisplayMessage(buf, "");
13018             return result;
13019         }
13020     }
13021 }
13022
13023 char *
13024 SavePart (char *str)
13025 {
13026     static char buf[MSG_SIZ];
13027     char *p;
13028
13029     p = strchr(str, ' ');
13030     if (p == NULL) return str;
13031     strncpy(buf, str, p - str);
13032     buf[p - str] = NULLCHAR;
13033     return buf;
13034 }
13035
13036 #define PGN_MAX_LINE 75
13037
13038 #define PGN_SIDE_WHITE  0
13039 #define PGN_SIDE_BLACK  1
13040
13041 static int
13042 FindFirstMoveOutOfBook (int side)
13043 {
13044     int result = -1;
13045
13046     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13047         int index = backwardMostMove;
13048         int has_book_hit = 0;
13049
13050         if( (index % 2) != side ) {
13051             index++;
13052         }
13053
13054         while( index < forwardMostMove ) {
13055             /* Check to see if engine is in book */
13056             int depth = pvInfoList[index].depth;
13057             int score = pvInfoList[index].score;
13058             int in_book = 0;
13059
13060             if( depth <= 2 ) {
13061                 in_book = 1;
13062             }
13063             else if( score == 0 && depth == 63 ) {
13064                 in_book = 1; /* Zappa */
13065             }
13066             else if( score == 2 && depth == 99 ) {
13067                 in_book = 1; /* Abrok */
13068             }
13069
13070             has_book_hit += in_book;
13071
13072             if( ! in_book ) {
13073                 result = index;
13074
13075                 break;
13076             }
13077
13078             index += 2;
13079         }
13080     }
13081
13082     return result;
13083 }
13084
13085 void
13086 GetOutOfBookInfo (char * buf)
13087 {
13088     int oob[2];
13089     int i;
13090     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13091
13092     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13093     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13094
13095     *buf = '\0';
13096
13097     if( oob[0] >= 0 || oob[1] >= 0 ) {
13098         for( i=0; i<2; i++ ) {
13099             int idx = oob[i];
13100
13101             if( idx >= 0 ) {
13102                 if( i > 0 && oob[0] >= 0 ) {
13103                     strcat( buf, "   " );
13104                 }
13105
13106                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13107                 sprintf( buf+strlen(buf), "%s%.2f",
13108                     pvInfoList[idx].score >= 0 ? "+" : "",
13109                     pvInfoList[idx].score / 100.0 );
13110             }
13111         }
13112     }
13113 }
13114
13115 /* Save game in PGN style and close the file */
13116 int
13117 SaveGamePGN (FILE *f)
13118 {
13119     int i, offset, linelen, newblock;
13120 //    char *movetext;
13121     char numtext[32];
13122     int movelen, numlen, blank;
13123     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13124
13125     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13126
13127     PrintPGNTags(f, &gameInfo);
13128
13129     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13130
13131     if (backwardMostMove > 0 || startedFromSetupPosition) {
13132         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13133         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13134         fprintf(f, "\n{--------------\n");
13135         PrintPosition(f, backwardMostMove);
13136         fprintf(f, "--------------}\n");
13137         free(fen);
13138     }
13139     else {
13140         /* [AS] Out of book annotation */
13141         if( appData.saveOutOfBookInfo ) {
13142             char buf[64];
13143
13144             GetOutOfBookInfo( buf );
13145
13146             if( buf[0] != '\0' ) {
13147                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13148             }
13149         }
13150
13151         fprintf(f, "\n");
13152     }
13153
13154     i = backwardMostMove;
13155     linelen = 0;
13156     newblock = TRUE;
13157
13158     while (i < forwardMostMove) {
13159         /* Print comments preceding this move */
13160         if (commentList[i] != NULL) {
13161             if (linelen > 0) fprintf(f, "\n");
13162             fprintf(f, "%s", commentList[i]);
13163             linelen = 0;
13164             newblock = TRUE;
13165         }
13166
13167         /* Format move number */
13168         if ((i % 2) == 0)
13169           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13170         else
13171           if (newblock)
13172             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13173           else
13174             numtext[0] = NULLCHAR;
13175
13176         numlen = strlen(numtext);
13177         newblock = FALSE;
13178
13179         /* Print move number */
13180         blank = linelen > 0 && numlen > 0;
13181         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13182             fprintf(f, "\n");
13183             linelen = 0;
13184             blank = 0;
13185         }
13186         if (blank) {
13187             fprintf(f, " ");
13188             linelen++;
13189         }
13190         fprintf(f, "%s", numtext);
13191         linelen += numlen;
13192
13193         /* Get move */
13194         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13195         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13196
13197         /* Print move */
13198         blank = linelen > 0 && movelen > 0;
13199         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13200             fprintf(f, "\n");
13201             linelen = 0;
13202             blank = 0;
13203         }
13204         if (blank) {
13205             fprintf(f, " ");
13206             linelen++;
13207         }
13208         fprintf(f, "%s", move_buffer);
13209         linelen += movelen;
13210
13211         /* [AS] Add PV info if present */
13212         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13213             /* [HGM] add time */
13214             char buf[MSG_SIZ]; int seconds;
13215
13216             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13217
13218             if( seconds <= 0)
13219               buf[0] = 0;
13220             else
13221               if( seconds < 30 )
13222                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13223               else
13224                 {
13225                   seconds = (seconds + 4)/10; // round to full seconds
13226                   if( seconds < 60 )
13227                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13228                   else
13229                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13230                 }
13231
13232             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13233                       pvInfoList[i].score >= 0 ? "+" : "",
13234                       pvInfoList[i].score / 100.0,
13235                       pvInfoList[i].depth,
13236                       buf );
13237
13238             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13239
13240             /* Print score/depth */
13241             blank = linelen > 0 && movelen > 0;
13242             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13243                 fprintf(f, "\n");
13244                 linelen = 0;
13245                 blank = 0;
13246             }
13247             if (blank) {
13248                 fprintf(f, " ");
13249                 linelen++;
13250             }
13251             fprintf(f, "%s", move_buffer);
13252             linelen += movelen;
13253         }
13254
13255         i++;
13256     }
13257
13258     /* Start a new line */
13259     if (linelen > 0) fprintf(f, "\n");
13260
13261     /* Print comments after last move */
13262     if (commentList[i] != NULL) {
13263         fprintf(f, "%s\n", commentList[i]);
13264     }
13265
13266     /* Print result */
13267     if (gameInfo.resultDetails != NULL &&
13268         gameInfo.resultDetails[0] != NULLCHAR) {
13269         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13270         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13271            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13272             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13273         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13274     } else {
13275         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13276     }
13277
13278     fclose(f);
13279     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13280     return TRUE;
13281 }
13282
13283 /* Save game in old style and close the file */
13284 int
13285 SaveGameOldStyle (FILE *f)
13286 {
13287     int i, offset;
13288     time_t tm;
13289
13290     tm = time((time_t *) NULL);
13291
13292     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13293     PrintOpponents(f);
13294
13295     if (backwardMostMove > 0 || startedFromSetupPosition) {
13296         fprintf(f, "\n[--------------\n");
13297         PrintPosition(f, backwardMostMove);
13298         fprintf(f, "--------------]\n");
13299     } else {
13300         fprintf(f, "\n");
13301     }
13302
13303     i = backwardMostMove;
13304     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13305
13306     while (i < forwardMostMove) {
13307         if (commentList[i] != NULL) {
13308             fprintf(f, "[%s]\n", commentList[i]);
13309         }
13310
13311         if ((i % 2) == 1) {
13312             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13313             i++;
13314         } else {
13315             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13316             i++;
13317             if (commentList[i] != NULL) {
13318                 fprintf(f, "\n");
13319                 continue;
13320             }
13321             if (i >= forwardMostMove) {
13322                 fprintf(f, "\n");
13323                 break;
13324             }
13325             fprintf(f, "%s\n", parseList[i]);
13326             i++;
13327         }
13328     }
13329
13330     if (commentList[i] != NULL) {
13331         fprintf(f, "[%s]\n", commentList[i]);
13332     }
13333
13334     /* This isn't really the old style, but it's close enough */
13335     if (gameInfo.resultDetails != NULL &&
13336         gameInfo.resultDetails[0] != NULLCHAR) {
13337         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13338                 gameInfo.resultDetails);
13339     } else {
13340         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13341     }
13342
13343     fclose(f);
13344     return TRUE;
13345 }
13346
13347 /* Save the current game to open file f and close the file */
13348 int
13349 SaveGame (FILE *f, int dummy, char *dummy2)
13350 {
13351     if (gameMode == EditPosition) EditPositionDone(TRUE);
13352     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13353     if (appData.oldSaveStyle)
13354       return SaveGameOldStyle(f);
13355     else
13356       return SaveGamePGN(f);
13357 }
13358
13359 /* Save the current position to the given file */
13360 int
13361 SavePositionToFile (char *filename)
13362 {
13363     FILE *f;
13364     char buf[MSG_SIZ];
13365
13366     if (strcmp(filename, "-") == 0) {
13367         return SavePosition(stdout, 0, NULL);
13368     } else {
13369         f = fopen(filename, "a");
13370         if (f == NULL) {
13371             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13372             DisplayError(buf, errno);
13373             return FALSE;
13374         } else {
13375             safeStrCpy(buf, lastMsg, MSG_SIZ);
13376             DisplayMessage(_("Waiting for access to save file"), "");
13377             flock(fileno(f), LOCK_EX); // [HGM] lock
13378             DisplayMessage(_("Saving position"), "");
13379             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13380             SavePosition(f, 0, NULL);
13381             DisplayMessage(buf, "");
13382             return TRUE;
13383         }
13384     }
13385 }
13386
13387 /* Save the current position to the given open file and close the file */
13388 int
13389 SavePosition (FILE *f, int dummy, char *dummy2)
13390 {
13391     time_t tm;
13392     char *fen;
13393
13394     if (gameMode == EditPosition) EditPositionDone(TRUE);
13395     if (appData.oldSaveStyle) {
13396         tm = time((time_t *) NULL);
13397
13398         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13399         PrintOpponents(f);
13400         fprintf(f, "[--------------\n");
13401         PrintPosition(f, currentMove);
13402         fprintf(f, "--------------]\n");
13403     } else {
13404         fen = PositionToFEN(currentMove, NULL, 1);
13405         fprintf(f, "%s\n", fen);
13406         free(fen);
13407     }
13408     fclose(f);
13409     return TRUE;
13410 }
13411
13412 void
13413 ReloadCmailMsgEvent (int unregister)
13414 {
13415 #if !WIN32
13416     static char *inFilename = NULL;
13417     static char *outFilename;
13418     int i;
13419     struct stat inbuf, outbuf;
13420     int status;
13421
13422     /* Any registered moves are unregistered if unregister is set, */
13423     /* i.e. invoked by the signal handler */
13424     if (unregister) {
13425         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13426             cmailMoveRegistered[i] = FALSE;
13427             if (cmailCommentList[i] != NULL) {
13428                 free(cmailCommentList[i]);
13429                 cmailCommentList[i] = NULL;
13430             }
13431         }
13432         nCmailMovesRegistered = 0;
13433     }
13434
13435     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13436         cmailResult[i] = CMAIL_NOT_RESULT;
13437     }
13438     nCmailResults = 0;
13439
13440     if (inFilename == NULL) {
13441         /* Because the filenames are static they only get malloced once  */
13442         /* and they never get freed                                      */
13443         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13444         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13445
13446         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13447         sprintf(outFilename, "%s.out", appData.cmailGameName);
13448     }
13449
13450     status = stat(outFilename, &outbuf);
13451     if (status < 0) {
13452         cmailMailedMove = FALSE;
13453     } else {
13454         status = stat(inFilename, &inbuf);
13455         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13456     }
13457
13458     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13459        counts the games, notes how each one terminated, etc.
13460
13461        It would be nice to remove this kludge and instead gather all
13462        the information while building the game list.  (And to keep it
13463        in the game list nodes instead of having a bunch of fixed-size
13464        parallel arrays.)  Note this will require getting each game's
13465        termination from the PGN tags, as the game list builder does
13466        not process the game moves.  --mann
13467        */
13468     cmailMsgLoaded = TRUE;
13469     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13470
13471     /* Load first game in the file or popup game menu */
13472     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13473
13474 #endif /* !WIN32 */
13475     return;
13476 }
13477
13478 int
13479 RegisterMove ()
13480 {
13481     FILE *f;
13482     char string[MSG_SIZ];
13483
13484     if (   cmailMailedMove
13485         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13486         return TRUE;            /* Allow free viewing  */
13487     }
13488
13489     /* Unregister move to ensure that we don't leave RegisterMove        */
13490     /* with the move registered when the conditions for registering no   */
13491     /* longer hold                                                       */
13492     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13493         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13494         nCmailMovesRegistered --;
13495
13496         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13497           {
13498               free(cmailCommentList[lastLoadGameNumber - 1]);
13499               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13500           }
13501     }
13502
13503     if (cmailOldMove == -1) {
13504         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13505         return FALSE;
13506     }
13507
13508     if (currentMove > cmailOldMove + 1) {
13509         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13510         return FALSE;
13511     }
13512
13513     if (currentMove < cmailOldMove) {
13514         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13515         return FALSE;
13516     }
13517
13518     if (forwardMostMove > currentMove) {
13519         /* Silently truncate extra moves */
13520         TruncateGame();
13521     }
13522
13523     if (   (currentMove == cmailOldMove + 1)
13524         || (   (currentMove == cmailOldMove)
13525             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13526                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13527         if (gameInfo.result != GameUnfinished) {
13528             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13529         }
13530
13531         if (commentList[currentMove] != NULL) {
13532             cmailCommentList[lastLoadGameNumber - 1]
13533               = StrSave(commentList[currentMove]);
13534         }
13535         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13536
13537         if (appData.debugMode)
13538           fprintf(debugFP, "Saving %s for game %d\n",
13539                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13540
13541         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13542
13543         f = fopen(string, "w");
13544         if (appData.oldSaveStyle) {
13545             SaveGameOldStyle(f); /* also closes the file */
13546
13547             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13548             f = fopen(string, "w");
13549             SavePosition(f, 0, NULL); /* also closes the file */
13550         } else {
13551             fprintf(f, "{--------------\n");
13552             PrintPosition(f, currentMove);
13553             fprintf(f, "--------------}\n\n");
13554
13555             SaveGame(f, 0, NULL); /* also closes the file*/
13556         }
13557
13558         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13559         nCmailMovesRegistered ++;
13560     } else if (nCmailGames == 1) {
13561         DisplayError(_("You have not made a move yet"), 0);
13562         return FALSE;
13563     }
13564
13565     return TRUE;
13566 }
13567
13568 void
13569 MailMoveEvent ()
13570 {
13571 #if !WIN32
13572     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13573     FILE *commandOutput;
13574     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13575     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13576     int nBuffers;
13577     int i;
13578     int archived;
13579     char *arcDir;
13580
13581     if (! cmailMsgLoaded) {
13582         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13583         return;
13584     }
13585
13586     if (nCmailGames == nCmailResults) {
13587         DisplayError(_("No unfinished games"), 0);
13588         return;
13589     }
13590
13591 #if CMAIL_PROHIBIT_REMAIL
13592     if (cmailMailedMove) {
13593       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);
13594         DisplayError(msg, 0);
13595         return;
13596     }
13597 #endif
13598
13599     if (! (cmailMailedMove || RegisterMove())) return;
13600
13601     if (   cmailMailedMove
13602         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13603       snprintf(string, MSG_SIZ, partCommandString,
13604                appData.debugMode ? " -v" : "", appData.cmailGameName);
13605         commandOutput = popen(string, "r");
13606
13607         if (commandOutput == NULL) {
13608             DisplayError(_("Failed to invoke cmail"), 0);
13609         } else {
13610             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13611                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13612             }
13613             if (nBuffers > 1) {
13614                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13615                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13616                 nBytes = MSG_SIZ - 1;
13617             } else {
13618                 (void) memcpy(msg, buffer, nBytes);
13619             }
13620             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13621
13622             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13623                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13624
13625                 archived = TRUE;
13626                 for (i = 0; i < nCmailGames; i ++) {
13627                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13628                         archived = FALSE;
13629                     }
13630                 }
13631                 if (   archived
13632                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13633                         != NULL)) {
13634                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13635                            arcDir,
13636                            appData.cmailGameName,
13637                            gameInfo.date);
13638                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13639                     cmailMsgLoaded = FALSE;
13640                 }
13641             }
13642
13643             DisplayInformation(msg);
13644             pclose(commandOutput);
13645         }
13646     } else {
13647         if ((*cmailMsg) != '\0') {
13648             DisplayInformation(cmailMsg);
13649         }
13650     }
13651
13652     return;
13653 #endif /* !WIN32 */
13654 }
13655
13656 char *
13657 CmailMsg ()
13658 {
13659 #if WIN32
13660     return NULL;
13661 #else
13662     int  prependComma = 0;
13663     char number[5];
13664     char string[MSG_SIZ];       /* Space for game-list */
13665     int  i;
13666
13667     if (!cmailMsgLoaded) return "";
13668
13669     if (cmailMailedMove) {
13670       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13671     } else {
13672         /* Create a list of games left */
13673       snprintf(string, MSG_SIZ, "[");
13674         for (i = 0; i < nCmailGames; i ++) {
13675             if (! (   cmailMoveRegistered[i]
13676                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13677                 if (prependComma) {
13678                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13679                 } else {
13680                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13681                     prependComma = 1;
13682                 }
13683
13684                 strcat(string, number);
13685             }
13686         }
13687         strcat(string, "]");
13688
13689         if (nCmailMovesRegistered + nCmailResults == 0) {
13690             switch (nCmailGames) {
13691               case 1:
13692                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13693                 break;
13694
13695               case 2:
13696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13697                 break;
13698
13699               default:
13700                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13701                          nCmailGames);
13702                 break;
13703             }
13704         } else {
13705             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13706               case 1:
13707                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13708                          string);
13709                 break;
13710
13711               case 0:
13712                 if (nCmailResults == nCmailGames) {
13713                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13714                 } else {
13715                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13716                 }
13717                 break;
13718
13719               default:
13720                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13721                          string);
13722             }
13723         }
13724     }
13725     return cmailMsg;
13726 #endif /* WIN32 */
13727 }
13728
13729 void
13730 ResetGameEvent ()
13731 {
13732     if (gameMode == Training)
13733       SetTrainingModeOff();
13734
13735     Reset(TRUE, TRUE);
13736     cmailMsgLoaded = FALSE;
13737     if (appData.icsActive) {
13738       SendToICS(ics_prefix);
13739       SendToICS("refresh\n");
13740     }
13741 }
13742
13743 void
13744 ExitEvent (int status)
13745 {
13746     exiting++;
13747     if (exiting > 2) {
13748       /* Give up on clean exit */
13749       exit(status);
13750     }
13751     if (exiting > 1) {
13752       /* Keep trying for clean exit */
13753       return;
13754     }
13755
13756     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13757
13758     if (telnetISR != NULL) {
13759       RemoveInputSource(telnetISR);
13760     }
13761     if (icsPR != NoProc) {
13762       DestroyChildProcess(icsPR, TRUE);
13763     }
13764
13765     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13766     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13767
13768     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13769     /* make sure this other one finishes before killing it!                  */
13770     if(endingGame) { int count = 0;
13771         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13772         while(endingGame && count++ < 10) DoSleep(1);
13773         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13774     }
13775
13776     /* Kill off chess programs */
13777     if (first.pr != NoProc) {
13778         ExitAnalyzeMode();
13779
13780         DoSleep( appData.delayBeforeQuit );
13781         SendToProgram("quit\n", &first);
13782         DoSleep( appData.delayAfterQuit );
13783         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13784     }
13785     if (second.pr != NoProc) {
13786         DoSleep( appData.delayBeforeQuit );
13787         SendToProgram("quit\n", &second);
13788         DoSleep( appData.delayAfterQuit );
13789         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13790     }
13791     if (first.isr != NULL) {
13792         RemoveInputSource(first.isr);
13793     }
13794     if (second.isr != NULL) {
13795         RemoveInputSource(second.isr);
13796     }
13797
13798     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13799     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13800
13801     ShutDownFrontEnd();
13802     exit(status);
13803 }
13804
13805 void
13806 PauseEngine (ChessProgramState *cps)
13807 {
13808     SendToProgram("pause\n", cps);
13809     cps->pause = 2;
13810 }
13811
13812 void
13813 UnPauseEngine (ChessProgramState *cps)
13814 {
13815     SendToProgram("resume\n", cps);
13816     cps->pause = 1;
13817 }
13818
13819 void
13820 PauseEvent ()
13821 {
13822     if (appData.debugMode)
13823         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13824     if (pausing) {
13825         pausing = FALSE;
13826         ModeHighlight();
13827         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13828             StartClocks();
13829             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13830                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13831                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13832             }
13833             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13834             HandleMachineMove(stashedInputMove, stalledEngine);
13835             stalledEngine = NULL;
13836             return;
13837         }
13838         if (gameMode == MachinePlaysWhite ||
13839             gameMode == TwoMachinesPlay   ||
13840             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13841             if(first.pause)  UnPauseEngine(&first);
13842             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13843             if(second.pause) UnPauseEngine(&second);
13844             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13845             StartClocks();
13846         } else {
13847             DisplayBothClocks();
13848         }
13849         if (gameMode == PlayFromGameFile) {
13850             if (appData.timeDelay >= 0)
13851                 AutoPlayGameLoop();
13852         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13853             Reset(FALSE, TRUE);
13854             SendToICS(ics_prefix);
13855             SendToICS("refresh\n");
13856         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13857             ForwardInner(forwardMostMove);
13858         }
13859         pauseExamInvalid = FALSE;
13860     } else {
13861         switch (gameMode) {
13862           default:
13863             return;
13864           case IcsExamining:
13865             pauseExamForwardMostMove = forwardMostMove;
13866             pauseExamInvalid = FALSE;
13867             /* fall through */
13868           case IcsObserving:
13869           case IcsPlayingWhite:
13870           case IcsPlayingBlack:
13871             pausing = TRUE;
13872             ModeHighlight();
13873             return;
13874           case PlayFromGameFile:
13875             (void) StopLoadGameTimer();
13876             pausing = TRUE;
13877             ModeHighlight();
13878             break;
13879           case BeginningOfGame:
13880             if (appData.icsActive) return;
13881             /* else fall through */
13882           case MachinePlaysWhite:
13883           case MachinePlaysBlack:
13884           case TwoMachinesPlay:
13885             if (forwardMostMove == 0)
13886               return;           /* don't pause if no one has moved */
13887             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13888                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13889                 if(onMove->pause) {           // thinking engine can be paused
13890                     PauseEngine(onMove);      // do it
13891                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13892                         PauseEngine(onMove->other);
13893                     else
13894                         SendToProgram("easy\n", onMove->other);
13895                     StopClocks();
13896                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13897             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13898                 if(first.pause) {
13899                     PauseEngine(&first);
13900                     StopClocks();
13901                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13902             } else { // human on move, pause pondering by either method
13903                 if(first.pause)
13904                     PauseEngine(&first);
13905                 else if(appData.ponderNextMove)
13906                     SendToProgram("easy\n", &first);
13907                 StopClocks();
13908             }
13909             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13910           case AnalyzeMode:
13911             pausing = TRUE;
13912             ModeHighlight();
13913             break;
13914         }
13915     }
13916 }
13917
13918 void
13919 EditCommentEvent ()
13920 {
13921     char title[MSG_SIZ];
13922
13923     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13924       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13925     } else {
13926       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13927                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13928                parseList[currentMove - 1]);
13929     }
13930
13931     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13932 }
13933
13934
13935 void
13936 EditTagsEvent ()
13937 {
13938     char *tags = PGNTags(&gameInfo);
13939     bookUp = FALSE;
13940     EditTagsPopUp(tags, NULL);
13941     free(tags);
13942 }
13943
13944 void
13945 ToggleSecond ()
13946 {
13947   if(second.analyzing) {
13948     SendToProgram("exit\n", &second);
13949     second.analyzing = FALSE;
13950   } else {
13951     if (second.pr == NoProc) StartChessProgram(&second);
13952     InitChessProgram(&second, FALSE);
13953     FeedMovesToProgram(&second, currentMove);
13954
13955     SendToProgram("analyze\n", &second);
13956     second.analyzing = TRUE;
13957   }
13958 }
13959
13960 /* Toggle ShowThinking */
13961 void
13962 ToggleShowThinking()
13963 {
13964   appData.showThinking = !appData.showThinking;
13965   ShowThinkingEvent();
13966 }
13967
13968 int
13969 AnalyzeModeEvent ()
13970 {
13971     char buf[MSG_SIZ];
13972
13973     if (!first.analysisSupport) {
13974       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13975       DisplayError(buf, 0);
13976       return 0;
13977     }
13978     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13979     if (appData.icsActive) {
13980         if (gameMode != IcsObserving) {
13981           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13982             DisplayError(buf, 0);
13983             /* secure check */
13984             if (appData.icsEngineAnalyze) {
13985                 if (appData.debugMode)
13986                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13987                 ExitAnalyzeMode();
13988                 ModeHighlight();
13989             }
13990             return 0;
13991         }
13992         /* if enable, user wants to disable icsEngineAnalyze */
13993         if (appData.icsEngineAnalyze) {
13994                 ExitAnalyzeMode();
13995                 ModeHighlight();
13996                 return 0;
13997         }
13998         appData.icsEngineAnalyze = TRUE;
13999         if (appData.debugMode)
14000             fprintf(debugFP, "ICS engine analyze starting... \n");
14001     }
14002
14003     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14004     if (appData.noChessProgram || gameMode == AnalyzeMode)
14005       return 0;
14006
14007     if (gameMode != AnalyzeFile) {
14008         if (!appData.icsEngineAnalyze) {
14009                EditGameEvent();
14010                if (gameMode != EditGame) return 0;
14011         }
14012         if (!appData.showThinking) ToggleShowThinking();
14013         ResurrectChessProgram();
14014         SendToProgram("analyze\n", &first);
14015         first.analyzing = TRUE;
14016         /*first.maybeThinking = TRUE;*/
14017         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14018         EngineOutputPopUp();
14019     }
14020     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14021     pausing = FALSE;
14022     ModeHighlight();
14023     SetGameInfo();
14024
14025     StartAnalysisClock();
14026     GetTimeMark(&lastNodeCountTime);
14027     lastNodeCount = 0;
14028     return 1;
14029 }
14030
14031 void
14032 AnalyzeFileEvent ()
14033 {
14034     if (appData.noChessProgram || gameMode == AnalyzeFile)
14035       return;
14036
14037     if (!first.analysisSupport) {
14038       char buf[MSG_SIZ];
14039       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14040       DisplayError(buf, 0);
14041       return;
14042     }
14043
14044     if (gameMode != AnalyzeMode) {
14045         keepInfo = 1; // mere annotating should not alter PGN tags
14046         EditGameEvent();
14047         keepInfo = 0;
14048         if (gameMode != EditGame) return;
14049         if (!appData.showThinking) ToggleShowThinking();
14050         ResurrectChessProgram();
14051         SendToProgram("analyze\n", &first);
14052         first.analyzing = TRUE;
14053         /*first.maybeThinking = TRUE;*/
14054         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14055         EngineOutputPopUp();
14056     }
14057     gameMode = AnalyzeFile;
14058     pausing = FALSE;
14059     ModeHighlight();
14060
14061     StartAnalysisClock();
14062     GetTimeMark(&lastNodeCountTime);
14063     lastNodeCount = 0;
14064     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14065     AnalysisPeriodicEvent(1);
14066 }
14067
14068 void
14069 MachineWhiteEvent ()
14070 {
14071     char buf[MSG_SIZ];
14072     char *bookHit = NULL;
14073
14074     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14075       return;
14076
14077
14078     if (gameMode == PlayFromGameFile ||
14079         gameMode == TwoMachinesPlay  ||
14080         gameMode == Training         ||
14081         gameMode == AnalyzeMode      ||
14082         gameMode == EndOfGame)
14083         EditGameEvent();
14084
14085     if (gameMode == EditPosition)
14086         EditPositionDone(TRUE);
14087
14088     if (!WhiteOnMove(currentMove)) {
14089         DisplayError(_("It is not White's turn"), 0);
14090         return;
14091     }
14092
14093     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14094       ExitAnalyzeMode();
14095
14096     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14097         gameMode == AnalyzeFile)
14098         TruncateGame();
14099
14100     ResurrectChessProgram();    /* in case it isn't running */
14101     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14102         gameMode = MachinePlaysWhite;
14103         ResetClocks();
14104     } else
14105     gameMode = MachinePlaysWhite;
14106     pausing = FALSE;
14107     ModeHighlight();
14108     SetGameInfo();
14109     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14110     DisplayTitle(buf);
14111     if (first.sendName) {
14112       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14113       SendToProgram(buf, &first);
14114     }
14115     if (first.sendTime) {
14116       if (first.useColors) {
14117         SendToProgram("black\n", &first); /*gnu kludge*/
14118       }
14119       SendTimeRemaining(&first, TRUE);
14120     }
14121     if (first.useColors) {
14122       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14123     }
14124     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14125     SetMachineThinkingEnables();
14126     first.maybeThinking = TRUE;
14127     StartClocks();
14128     firstMove = FALSE;
14129
14130     if (appData.autoFlipView && !flipView) {
14131       flipView = !flipView;
14132       DrawPosition(FALSE, NULL);
14133       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14134     }
14135
14136     if(bookHit) { // [HGM] book: simulate book reply
14137         static char bookMove[MSG_SIZ]; // a bit generous?
14138
14139         programStats.nodes = programStats.depth = programStats.time =
14140         programStats.score = programStats.got_only_move = 0;
14141         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14142
14143         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14144         strcat(bookMove, bookHit);
14145         HandleMachineMove(bookMove, &first);
14146     }
14147 }
14148
14149 void
14150 MachineBlackEvent ()
14151 {
14152   char buf[MSG_SIZ];
14153   char *bookHit = NULL;
14154
14155     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14156         return;
14157
14158
14159     if (gameMode == PlayFromGameFile ||
14160         gameMode == TwoMachinesPlay  ||
14161         gameMode == Training         ||
14162         gameMode == AnalyzeMode      ||
14163         gameMode == EndOfGame)
14164         EditGameEvent();
14165
14166     if (gameMode == EditPosition)
14167         EditPositionDone(TRUE);
14168
14169     if (WhiteOnMove(currentMove)) {
14170         DisplayError(_("It is not Black's turn"), 0);
14171         return;
14172     }
14173
14174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14175       ExitAnalyzeMode();
14176
14177     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14178         gameMode == AnalyzeFile)
14179         TruncateGame();
14180
14181     ResurrectChessProgram();    /* in case it isn't running */
14182     gameMode = MachinePlaysBlack;
14183     pausing = FALSE;
14184     ModeHighlight();
14185     SetGameInfo();
14186     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14187     DisplayTitle(buf);
14188     if (first.sendName) {
14189       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14190       SendToProgram(buf, &first);
14191     }
14192     if (first.sendTime) {
14193       if (first.useColors) {
14194         SendToProgram("white\n", &first); /*gnu kludge*/
14195       }
14196       SendTimeRemaining(&first, FALSE);
14197     }
14198     if (first.useColors) {
14199       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14200     }
14201     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14202     SetMachineThinkingEnables();
14203     first.maybeThinking = TRUE;
14204     StartClocks();
14205
14206     if (appData.autoFlipView && flipView) {
14207       flipView = !flipView;
14208       DrawPosition(FALSE, NULL);
14209       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14210     }
14211     if(bookHit) { // [HGM] book: simulate book reply
14212         static char bookMove[MSG_SIZ]; // a bit generous?
14213
14214         programStats.nodes = programStats.depth = programStats.time =
14215         programStats.score = programStats.got_only_move = 0;
14216         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14217
14218         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14219         strcat(bookMove, bookHit);
14220         HandleMachineMove(bookMove, &first);
14221     }
14222 }
14223
14224
14225 void
14226 DisplayTwoMachinesTitle ()
14227 {
14228     char buf[MSG_SIZ];
14229     if (appData.matchGames > 0) {
14230         if(appData.tourneyFile[0]) {
14231           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14232                    gameInfo.white, _("vs."), gameInfo.black,
14233                    nextGame+1, appData.matchGames+1,
14234                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14235         } else
14236         if (first.twoMachinesColor[0] == 'w') {
14237           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14238                    gameInfo.white, _("vs."),  gameInfo.black,
14239                    first.matchWins, second.matchWins,
14240                    matchGame - 1 - (first.matchWins + second.matchWins));
14241         } else {
14242           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14243                    gameInfo.white, _("vs."), gameInfo.black,
14244                    second.matchWins, first.matchWins,
14245                    matchGame - 1 - (first.matchWins + second.matchWins));
14246         }
14247     } else {
14248       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14249     }
14250     DisplayTitle(buf);
14251 }
14252
14253 void
14254 SettingsMenuIfReady ()
14255 {
14256   if (second.lastPing != second.lastPong) {
14257     DisplayMessage("", _("Waiting for second chess program"));
14258     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14259     return;
14260   }
14261   ThawUI();
14262   DisplayMessage("", "");
14263   SettingsPopUp(&second);
14264 }
14265
14266 int
14267 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14268 {
14269     char buf[MSG_SIZ];
14270     if (cps->pr == NoProc) {
14271         StartChessProgram(cps);
14272         if (cps->protocolVersion == 1) {
14273           retry();
14274           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14275         } else {
14276           /* kludge: allow timeout for initial "feature" command */
14277           if(retry != TwoMachinesEventIfReady) FreezeUI();
14278           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14279           DisplayMessage("", buf);
14280           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14281         }
14282         return 1;
14283     }
14284     return 0;
14285 }
14286
14287 void
14288 TwoMachinesEvent P((void))
14289 {
14290     int i;
14291     char buf[MSG_SIZ];
14292     ChessProgramState *onmove;
14293     char *bookHit = NULL;
14294     static int stalling = 0;
14295     TimeMark now;
14296     long wait;
14297
14298     if (appData.noChessProgram) return;
14299
14300     switch (gameMode) {
14301       case TwoMachinesPlay:
14302         return;
14303       case MachinePlaysWhite:
14304       case MachinePlaysBlack:
14305         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14306             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14307             return;
14308         }
14309         /* fall through */
14310       case BeginningOfGame:
14311       case PlayFromGameFile:
14312       case EndOfGame:
14313         EditGameEvent();
14314         if (gameMode != EditGame) return;
14315         break;
14316       case EditPosition:
14317         EditPositionDone(TRUE);
14318         break;
14319       case AnalyzeMode:
14320       case AnalyzeFile:
14321         ExitAnalyzeMode();
14322         break;
14323       case EditGame:
14324       default:
14325         break;
14326     }
14327
14328 //    forwardMostMove = currentMove;
14329     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14330     startingEngine = TRUE;
14331
14332     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14333
14334     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14335     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14336       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14337       return;
14338     }
14339     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14340
14341     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14342         startingEngine = FALSE;
14343         DisplayError("second engine does not play this", 0);
14344         return;
14345     }
14346
14347     if(!stalling) {
14348       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14349       SendToProgram("force\n", &second);
14350       stalling = 1;
14351       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14352       return;
14353     }
14354     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14355     if(appData.matchPause>10000 || appData.matchPause<10)
14356                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14357     wait = SubtractTimeMarks(&now, &pauseStart);
14358     if(wait < appData.matchPause) {
14359         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14360         return;
14361     }
14362     // we are now committed to starting the game
14363     stalling = 0;
14364     DisplayMessage("", "");
14365     if (startedFromSetupPosition) {
14366         SendBoard(&second, backwardMostMove);
14367     if (appData.debugMode) {
14368         fprintf(debugFP, "Two Machines\n");
14369     }
14370     }
14371     for (i = backwardMostMove; i < forwardMostMove; i++) {
14372         SendMoveToProgram(i, &second);
14373     }
14374
14375     gameMode = TwoMachinesPlay;
14376     pausing = startingEngine = FALSE;
14377     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14378     SetGameInfo();
14379     DisplayTwoMachinesTitle();
14380     firstMove = TRUE;
14381     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14382         onmove = &first;
14383     } else {
14384         onmove = &second;
14385     }
14386     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14387     SendToProgram(first.computerString, &first);
14388     if (first.sendName) {
14389       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14390       SendToProgram(buf, &first);
14391     }
14392     SendToProgram(second.computerString, &second);
14393     if (second.sendName) {
14394       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14395       SendToProgram(buf, &second);
14396     }
14397
14398     ResetClocks();
14399     if (!first.sendTime || !second.sendTime) {
14400         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14401         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14402     }
14403     if (onmove->sendTime) {
14404       if (onmove->useColors) {
14405         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14406       }
14407       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14408     }
14409     if (onmove->useColors) {
14410       SendToProgram(onmove->twoMachinesColor, onmove);
14411     }
14412     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14413 //    SendToProgram("go\n", onmove);
14414     onmove->maybeThinking = TRUE;
14415     SetMachineThinkingEnables();
14416
14417     StartClocks();
14418
14419     if(bookHit) { // [HGM] book: simulate book reply
14420         static char bookMove[MSG_SIZ]; // a bit generous?
14421
14422         programStats.nodes = programStats.depth = programStats.time =
14423         programStats.score = programStats.got_only_move = 0;
14424         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14425
14426         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14427         strcat(bookMove, bookHit);
14428         savedMessage = bookMove; // args for deferred call
14429         savedState = onmove;
14430         ScheduleDelayedEvent(DeferredBookMove, 1);
14431     }
14432 }
14433
14434 void
14435 TrainingEvent ()
14436 {
14437     if (gameMode == Training) {
14438       SetTrainingModeOff();
14439       gameMode = PlayFromGameFile;
14440       DisplayMessage("", _("Training mode off"));
14441     } else {
14442       gameMode = Training;
14443       animateTraining = appData.animate;
14444
14445       /* make sure we are not already at the end of the game */
14446       if (currentMove < forwardMostMove) {
14447         SetTrainingModeOn();
14448         DisplayMessage("", _("Training mode on"));
14449       } else {
14450         gameMode = PlayFromGameFile;
14451         DisplayError(_("Already at end of game"), 0);
14452       }
14453     }
14454     ModeHighlight();
14455 }
14456
14457 void
14458 IcsClientEvent ()
14459 {
14460     if (!appData.icsActive) return;
14461     switch (gameMode) {
14462       case IcsPlayingWhite:
14463       case IcsPlayingBlack:
14464       case IcsObserving:
14465       case IcsIdle:
14466       case BeginningOfGame:
14467       case IcsExamining:
14468         return;
14469
14470       case EditGame:
14471         break;
14472
14473       case EditPosition:
14474         EditPositionDone(TRUE);
14475         break;
14476
14477       case AnalyzeMode:
14478       case AnalyzeFile:
14479         ExitAnalyzeMode();
14480         break;
14481
14482       default:
14483         EditGameEvent();
14484         break;
14485     }
14486
14487     gameMode = IcsIdle;
14488     ModeHighlight();
14489     return;
14490 }
14491
14492 void
14493 EditGameEvent ()
14494 {
14495     int i;
14496
14497     switch (gameMode) {
14498       case Training:
14499         SetTrainingModeOff();
14500         break;
14501       case MachinePlaysWhite:
14502       case MachinePlaysBlack:
14503       case BeginningOfGame:
14504         SendToProgram("force\n", &first);
14505         SetUserThinkingEnables();
14506         break;
14507       case PlayFromGameFile:
14508         (void) StopLoadGameTimer();
14509         if (gameFileFP != NULL) {
14510             gameFileFP = NULL;
14511         }
14512         break;
14513       case EditPosition:
14514         EditPositionDone(TRUE);
14515         break;
14516       case AnalyzeMode:
14517       case AnalyzeFile:
14518         ExitAnalyzeMode();
14519         SendToProgram("force\n", &first);
14520         break;
14521       case TwoMachinesPlay:
14522         GameEnds(EndOfFile, NULL, GE_PLAYER);
14523         ResurrectChessProgram();
14524         SetUserThinkingEnables();
14525         break;
14526       case EndOfGame:
14527         ResurrectChessProgram();
14528         break;
14529       case IcsPlayingBlack:
14530       case IcsPlayingWhite:
14531         DisplayError(_("Warning: You are still playing a game"), 0);
14532         break;
14533       case IcsObserving:
14534         DisplayError(_("Warning: You are still observing a game"), 0);
14535         break;
14536       case IcsExamining:
14537         DisplayError(_("Warning: You are still examining a game"), 0);
14538         break;
14539       case IcsIdle:
14540         break;
14541       case EditGame:
14542       default:
14543         return;
14544     }
14545
14546     pausing = FALSE;
14547     StopClocks();
14548     first.offeredDraw = second.offeredDraw = 0;
14549
14550     if (gameMode == PlayFromGameFile) {
14551         whiteTimeRemaining = timeRemaining[0][currentMove];
14552         blackTimeRemaining = timeRemaining[1][currentMove];
14553         DisplayTitle("");
14554     }
14555
14556     if (gameMode == MachinePlaysWhite ||
14557         gameMode == MachinePlaysBlack ||
14558         gameMode == TwoMachinesPlay ||
14559         gameMode == EndOfGame) {
14560         i = forwardMostMove;
14561         while (i > currentMove) {
14562             SendToProgram("undo\n", &first);
14563             i--;
14564         }
14565         if(!adjustedClock) {
14566         whiteTimeRemaining = timeRemaining[0][currentMove];
14567         blackTimeRemaining = timeRemaining[1][currentMove];
14568         DisplayBothClocks();
14569         }
14570         if (whiteFlag || blackFlag) {
14571             whiteFlag = blackFlag = 0;
14572         }
14573         DisplayTitle("");
14574     }
14575
14576     gameMode = EditGame;
14577     ModeHighlight();
14578     SetGameInfo();
14579 }
14580
14581
14582 void
14583 EditPositionEvent ()
14584 {
14585     if (gameMode == EditPosition) {
14586         EditGameEvent();
14587         return;
14588     }
14589
14590     EditGameEvent();
14591     if (gameMode != EditGame) return;
14592
14593     gameMode = EditPosition;
14594     ModeHighlight();
14595     SetGameInfo();
14596     if (currentMove > 0)
14597       CopyBoard(boards[0], boards[currentMove]);
14598
14599     blackPlaysFirst = !WhiteOnMove(currentMove);
14600     ResetClocks();
14601     currentMove = forwardMostMove = backwardMostMove = 0;
14602     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14603     DisplayMove(-1);
14604     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14605 }
14606
14607 void
14608 ExitAnalyzeMode ()
14609 {
14610     /* [DM] icsEngineAnalyze - possible call from other functions */
14611     if (appData.icsEngineAnalyze) {
14612         appData.icsEngineAnalyze = FALSE;
14613
14614         DisplayMessage("",_("Close ICS engine analyze..."));
14615     }
14616     if (first.analysisSupport && first.analyzing) {
14617       SendToBoth("exit\n");
14618       first.analyzing = second.analyzing = FALSE;
14619     }
14620     thinkOutput[0] = NULLCHAR;
14621 }
14622
14623 void
14624 EditPositionDone (Boolean fakeRights)
14625 {
14626     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14627
14628     startedFromSetupPosition = TRUE;
14629     InitChessProgram(&first, FALSE);
14630     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14631       boards[0][EP_STATUS] = EP_NONE;
14632       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14633       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14634         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14635         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14636       } else boards[0][CASTLING][2] = NoRights;
14637       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14638         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14639         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14640       } else boards[0][CASTLING][5] = NoRights;
14641       if(gameInfo.variant == VariantSChess) {
14642         int i;
14643         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14644           boards[0][VIRGIN][i] = 0;
14645           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14646           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14647         }
14648       }
14649     }
14650     SendToProgram("force\n", &first);
14651     if (blackPlaysFirst) {
14652         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14653         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14654         currentMove = forwardMostMove = backwardMostMove = 1;
14655         CopyBoard(boards[1], boards[0]);
14656     } else {
14657         currentMove = forwardMostMove = backwardMostMove = 0;
14658     }
14659     SendBoard(&first, forwardMostMove);
14660     if (appData.debugMode) {
14661         fprintf(debugFP, "EditPosDone\n");
14662     }
14663     DisplayTitle("");
14664     DisplayMessage("", "");
14665     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14666     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14667     gameMode = EditGame;
14668     ModeHighlight();
14669     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14670     ClearHighlights(); /* [AS] */
14671 }
14672
14673 /* Pause for `ms' milliseconds */
14674 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14675 void
14676 TimeDelay (long ms)
14677 {
14678     TimeMark m1, m2;
14679
14680     GetTimeMark(&m1);
14681     do {
14682         GetTimeMark(&m2);
14683     } while (SubtractTimeMarks(&m2, &m1) < ms);
14684 }
14685
14686 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14687 void
14688 SendMultiLineToICS (char *buf)
14689 {
14690     char temp[MSG_SIZ+1], *p;
14691     int len;
14692
14693     len = strlen(buf);
14694     if (len > MSG_SIZ)
14695       len = MSG_SIZ;
14696
14697     strncpy(temp, buf, len);
14698     temp[len] = 0;
14699
14700     p = temp;
14701     while (*p) {
14702         if (*p == '\n' || *p == '\r')
14703           *p = ' ';
14704         ++p;
14705     }
14706
14707     strcat(temp, "\n");
14708     SendToICS(temp);
14709     SendToPlayer(temp, strlen(temp));
14710 }
14711
14712 void
14713 SetWhiteToPlayEvent ()
14714 {
14715     if (gameMode == EditPosition) {
14716         blackPlaysFirst = FALSE;
14717         DisplayBothClocks();    /* works because currentMove is 0 */
14718     } else if (gameMode == IcsExamining) {
14719         SendToICS(ics_prefix);
14720         SendToICS("tomove white\n");
14721     }
14722 }
14723
14724 void
14725 SetBlackToPlayEvent ()
14726 {
14727     if (gameMode == EditPosition) {
14728         blackPlaysFirst = TRUE;
14729         currentMove = 1;        /* kludge */
14730         DisplayBothClocks();
14731         currentMove = 0;
14732     } else if (gameMode == IcsExamining) {
14733         SendToICS(ics_prefix);
14734         SendToICS("tomove black\n");
14735     }
14736 }
14737
14738 void
14739 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14740 {
14741     char buf[MSG_SIZ];
14742     ChessSquare piece = boards[0][y][x];
14743     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14744     static int lastVariant;
14745
14746     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14747
14748     switch (selection) {
14749       case ClearBoard:
14750         CopyBoard(currentBoard, boards[0]);
14751         CopyBoard(menuBoard, initialPosition);
14752         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14753             SendToICS(ics_prefix);
14754             SendToICS("bsetup clear\n");
14755         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14756             SendToICS(ics_prefix);
14757             SendToICS("clearboard\n");
14758         } else {
14759             int nonEmpty = 0;
14760             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14761                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14762                 for (y = 0; y < BOARD_HEIGHT; y++) {
14763                     if (gameMode == IcsExamining) {
14764                         if (boards[currentMove][y][x] != EmptySquare) {
14765                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14766                                     AAA + x, ONE + y);
14767                             SendToICS(buf);
14768                         }
14769                     } else {
14770                         if(boards[0][y][x] != p) nonEmpty++;
14771                         boards[0][y][x] = p;
14772                     }
14773                 }
14774                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14775             }
14776             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14777                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14778                     ChessSquare p = menuBoard[0][x];
14779                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14780                     p = menuBoard[BOARD_HEIGHT-1][x];
14781                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14782                 }
14783                 DisplayMessage("Clicking clock again restores position", "");
14784                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14785                 if(!nonEmpty) { // asked to clear an empty board
14786                     CopyBoard(boards[0], menuBoard);
14787                 } else
14788                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14789                     CopyBoard(boards[0], initialPosition);
14790                 } else
14791                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14792                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14793                     CopyBoard(boards[0], erasedBoard);
14794                 } else
14795                     CopyBoard(erasedBoard, currentBoard);
14796
14797             }
14798         }
14799         if (gameMode == EditPosition) {
14800             DrawPosition(FALSE, boards[0]);
14801         }
14802         break;
14803
14804       case WhitePlay:
14805         SetWhiteToPlayEvent();
14806         break;
14807
14808       case BlackPlay:
14809         SetBlackToPlayEvent();
14810         break;
14811
14812       case EmptySquare:
14813         if (gameMode == IcsExamining) {
14814             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14815             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14816             SendToICS(buf);
14817         } else {
14818             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14819                 if(x == BOARD_LEFT-2) {
14820                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14821                     boards[0][y][1] = 0;
14822                 } else
14823                 if(x == BOARD_RGHT+1) {
14824                     if(y >= gameInfo.holdingsSize) break;
14825                     boards[0][y][BOARD_WIDTH-2] = 0;
14826                 } else break;
14827             }
14828             boards[0][y][x] = EmptySquare;
14829             DrawPosition(FALSE, boards[0]);
14830         }
14831         break;
14832
14833       case PromotePiece:
14834         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14835            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14836             selection = (ChessSquare) (PROMOTED piece);
14837         } else if(piece == EmptySquare) selection = WhiteSilver;
14838         else selection = (ChessSquare)((int)piece - 1);
14839         goto defaultlabel;
14840
14841       case DemotePiece:
14842         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14843            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14844             selection = (ChessSquare) (DEMOTED piece);
14845         } else if(piece == EmptySquare) selection = BlackSilver;
14846         else selection = (ChessSquare)((int)piece + 1);
14847         goto defaultlabel;
14848
14849       case WhiteQueen:
14850       case BlackQueen:
14851         if(gameInfo.variant == VariantShatranj ||
14852            gameInfo.variant == VariantXiangqi  ||
14853            gameInfo.variant == VariantCourier  ||
14854            gameInfo.variant == VariantASEAN    ||
14855            gameInfo.variant == VariantMakruk     )
14856             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14857         goto defaultlabel;
14858
14859       case WhiteKing:
14860       case BlackKing:
14861         if(gameInfo.variant == VariantXiangqi)
14862             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14863         if(gameInfo.variant == VariantKnightmate)
14864             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14865       default:
14866         defaultlabel:
14867         if (gameMode == IcsExamining) {
14868             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14869             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14870                      PieceToChar(selection), AAA + x, ONE + y);
14871             SendToICS(buf);
14872         } else {
14873             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14874                 int n;
14875                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14876                     n = PieceToNumber(selection - BlackPawn);
14877                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14878                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14879                     boards[0][BOARD_HEIGHT-1-n][1]++;
14880                 } else
14881                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14882                     n = PieceToNumber(selection);
14883                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14884                     boards[0][n][BOARD_WIDTH-1] = selection;
14885                     boards[0][n][BOARD_WIDTH-2]++;
14886                 }
14887             } else
14888             boards[0][y][x] = selection;
14889             DrawPosition(TRUE, boards[0]);
14890             ClearHighlights();
14891             fromX = fromY = -1;
14892         }
14893         break;
14894     }
14895 }
14896
14897
14898 void
14899 DropMenuEvent (ChessSquare selection, int x, int y)
14900 {
14901     ChessMove moveType;
14902
14903     switch (gameMode) {
14904       case IcsPlayingWhite:
14905       case MachinePlaysBlack:
14906         if (!WhiteOnMove(currentMove)) {
14907             DisplayMoveError(_("It is Black's turn"));
14908             return;
14909         }
14910         moveType = WhiteDrop;
14911         break;
14912       case IcsPlayingBlack:
14913       case MachinePlaysWhite:
14914         if (WhiteOnMove(currentMove)) {
14915             DisplayMoveError(_("It is White's turn"));
14916             return;
14917         }
14918         moveType = BlackDrop;
14919         break;
14920       case EditGame:
14921         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14922         break;
14923       default:
14924         return;
14925     }
14926
14927     if (moveType == BlackDrop && selection < BlackPawn) {
14928       selection = (ChessSquare) ((int) selection
14929                                  + (int) BlackPawn - (int) WhitePawn);
14930     }
14931     if (boards[currentMove][y][x] != EmptySquare) {
14932         DisplayMoveError(_("That square is occupied"));
14933         return;
14934     }
14935
14936     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14937 }
14938
14939 void
14940 AcceptEvent ()
14941 {
14942     /* Accept a pending offer of any kind from opponent */
14943
14944     if (appData.icsActive) {
14945         SendToICS(ics_prefix);
14946         SendToICS("accept\n");
14947     } else if (cmailMsgLoaded) {
14948         if (currentMove == cmailOldMove &&
14949             commentList[cmailOldMove] != NULL &&
14950             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14951                    "Black offers a draw" : "White offers a draw")) {
14952             TruncateGame();
14953             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14954             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14955         } else {
14956             DisplayError(_("There is no pending offer on this move"), 0);
14957             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14958         }
14959     } else {
14960         /* Not used for offers from chess program */
14961     }
14962 }
14963
14964 void
14965 DeclineEvent ()
14966 {
14967     /* Decline a pending offer of any kind from opponent */
14968
14969     if (appData.icsActive) {
14970         SendToICS(ics_prefix);
14971         SendToICS("decline\n");
14972     } else if (cmailMsgLoaded) {
14973         if (currentMove == cmailOldMove &&
14974             commentList[cmailOldMove] != NULL &&
14975             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14976                    "Black offers a draw" : "White offers a draw")) {
14977 #ifdef NOTDEF
14978             AppendComment(cmailOldMove, "Draw declined", TRUE);
14979             DisplayComment(cmailOldMove - 1, "Draw declined");
14980 #endif /*NOTDEF*/
14981         } else {
14982             DisplayError(_("There is no pending offer on this move"), 0);
14983         }
14984     } else {
14985         /* Not used for offers from chess program */
14986     }
14987 }
14988
14989 void
14990 RematchEvent ()
14991 {
14992     /* Issue ICS rematch command */
14993     if (appData.icsActive) {
14994         SendToICS(ics_prefix);
14995         SendToICS("rematch\n");
14996     }
14997 }
14998
14999 void
15000 CallFlagEvent ()
15001 {
15002     /* Call your opponent's flag (claim a win on time) */
15003     if (appData.icsActive) {
15004         SendToICS(ics_prefix);
15005         SendToICS("flag\n");
15006     } else {
15007         switch (gameMode) {
15008           default:
15009             return;
15010           case MachinePlaysWhite:
15011             if (whiteFlag) {
15012                 if (blackFlag)
15013                   GameEnds(GameIsDrawn, "Both players ran out of time",
15014                            GE_PLAYER);
15015                 else
15016                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15017             } else {
15018                 DisplayError(_("Your opponent is not out of time"), 0);
15019             }
15020             break;
15021           case MachinePlaysBlack:
15022             if (blackFlag) {
15023                 if (whiteFlag)
15024                   GameEnds(GameIsDrawn, "Both players ran out of time",
15025                            GE_PLAYER);
15026                 else
15027                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15028             } else {
15029                 DisplayError(_("Your opponent is not out of time"), 0);
15030             }
15031             break;
15032         }
15033     }
15034 }
15035
15036 void
15037 ClockClick (int which)
15038 {       // [HGM] code moved to back-end from winboard.c
15039         if(which) { // black clock
15040           if (gameMode == EditPosition || gameMode == IcsExamining) {
15041             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15042             SetBlackToPlayEvent();
15043           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15044           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15045           } else if (shiftKey) {
15046             AdjustClock(which, -1);
15047           } else if (gameMode == IcsPlayingWhite ||
15048                      gameMode == MachinePlaysBlack) {
15049             CallFlagEvent();
15050           }
15051         } else { // white clock
15052           if (gameMode == EditPosition || gameMode == IcsExamining) {
15053             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15054             SetWhiteToPlayEvent();
15055           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15056           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15057           } else if (shiftKey) {
15058             AdjustClock(which, -1);
15059           } else if (gameMode == IcsPlayingBlack ||
15060                    gameMode == MachinePlaysWhite) {
15061             CallFlagEvent();
15062           }
15063         }
15064 }
15065
15066 void
15067 DrawEvent ()
15068 {
15069     /* Offer draw or accept pending draw offer from opponent */
15070
15071     if (appData.icsActive) {
15072         /* Note: tournament rules require draw offers to be
15073            made after you make your move but before you punch
15074            your clock.  Currently ICS doesn't let you do that;
15075            instead, you immediately punch your clock after making
15076            a move, but you can offer a draw at any time. */
15077
15078         SendToICS(ics_prefix);
15079         SendToICS("draw\n");
15080         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15081     } else if (cmailMsgLoaded) {
15082         if (currentMove == cmailOldMove &&
15083             commentList[cmailOldMove] != NULL &&
15084             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15085                    "Black offers a draw" : "White offers a draw")) {
15086             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15087             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15088         } else if (currentMove == cmailOldMove + 1) {
15089             char *offer = WhiteOnMove(cmailOldMove) ?
15090               "White offers a draw" : "Black offers a draw";
15091             AppendComment(currentMove, offer, TRUE);
15092             DisplayComment(currentMove - 1, offer);
15093             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15094         } else {
15095             DisplayError(_("You must make your move before offering a draw"), 0);
15096             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15097         }
15098     } else if (first.offeredDraw) {
15099         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15100     } else {
15101         if (first.sendDrawOffers) {
15102             SendToProgram("draw\n", &first);
15103             userOfferedDraw = TRUE;
15104         }
15105     }
15106 }
15107
15108 void
15109 AdjournEvent ()
15110 {
15111     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15112
15113     if (appData.icsActive) {
15114         SendToICS(ics_prefix);
15115         SendToICS("adjourn\n");
15116     } else {
15117         /* Currently GNU Chess doesn't offer or accept Adjourns */
15118     }
15119 }
15120
15121
15122 void
15123 AbortEvent ()
15124 {
15125     /* Offer Abort or accept pending Abort offer from opponent */
15126
15127     if (appData.icsActive) {
15128         SendToICS(ics_prefix);
15129         SendToICS("abort\n");
15130     } else {
15131         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15132     }
15133 }
15134
15135 void
15136 ResignEvent ()
15137 {
15138     /* Resign.  You can do this even if it's not your turn. */
15139
15140     if (appData.icsActive) {
15141         SendToICS(ics_prefix);
15142         SendToICS("resign\n");
15143     } else {
15144         switch (gameMode) {
15145           case MachinePlaysWhite:
15146             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15147             break;
15148           case MachinePlaysBlack:
15149             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15150             break;
15151           case EditGame:
15152             if (cmailMsgLoaded) {
15153                 TruncateGame();
15154                 if (WhiteOnMove(cmailOldMove)) {
15155                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15156                 } else {
15157                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15158                 }
15159                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15160             }
15161             break;
15162           default:
15163             break;
15164         }
15165     }
15166 }
15167
15168
15169 void
15170 StopObservingEvent ()
15171 {
15172     /* Stop observing current games */
15173     SendToICS(ics_prefix);
15174     SendToICS("unobserve\n");
15175 }
15176
15177 void
15178 StopExaminingEvent ()
15179 {
15180     /* Stop observing current game */
15181     SendToICS(ics_prefix);
15182     SendToICS("unexamine\n");
15183 }
15184
15185 void
15186 ForwardInner (int target)
15187 {
15188     int limit; int oldSeekGraphUp = seekGraphUp;
15189
15190     if (appData.debugMode)
15191         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15192                 target, currentMove, forwardMostMove);
15193
15194     if (gameMode == EditPosition)
15195       return;
15196
15197     seekGraphUp = FALSE;
15198     MarkTargetSquares(1);
15199
15200     if (gameMode == PlayFromGameFile && !pausing)
15201       PauseEvent();
15202
15203     if (gameMode == IcsExamining && pausing)
15204       limit = pauseExamForwardMostMove;
15205     else
15206       limit = forwardMostMove;
15207
15208     if (target > limit) target = limit;
15209
15210     if (target > 0 && moveList[target - 1][0]) {
15211         int fromX, fromY, toX, toY;
15212         toX = moveList[target - 1][2] - AAA;
15213         toY = moveList[target - 1][3] - ONE;
15214         if (moveList[target - 1][1] == '@') {
15215             if (appData.highlightLastMove) {
15216                 SetHighlights(-1, -1, toX, toY);
15217             }
15218         } else {
15219             fromX = moveList[target - 1][0] - AAA;
15220             fromY = moveList[target - 1][1] - ONE;
15221             if (target == currentMove + 1) {
15222                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15223             }
15224             if (appData.highlightLastMove) {
15225                 SetHighlights(fromX, fromY, toX, toY);
15226             }
15227         }
15228     }
15229     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15230         gameMode == Training || gameMode == PlayFromGameFile ||
15231         gameMode == AnalyzeFile) {
15232         while (currentMove < target) {
15233             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15234             SendMoveToProgram(currentMove++, &first);
15235         }
15236     } else {
15237         currentMove = target;
15238     }
15239
15240     if (gameMode == EditGame || gameMode == EndOfGame) {
15241         whiteTimeRemaining = timeRemaining[0][currentMove];
15242         blackTimeRemaining = timeRemaining[1][currentMove];
15243     }
15244     DisplayBothClocks();
15245     DisplayMove(currentMove - 1);
15246     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15247     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15248     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15249         DisplayComment(currentMove - 1, commentList[currentMove]);
15250     }
15251     ClearMap(); // [HGM] exclude: invalidate map
15252 }
15253
15254
15255 void
15256 ForwardEvent ()
15257 {
15258     if (gameMode == IcsExamining && !pausing) {
15259         SendToICS(ics_prefix);
15260         SendToICS("forward\n");
15261     } else {
15262         ForwardInner(currentMove + 1);
15263     }
15264 }
15265
15266 void
15267 ToEndEvent ()
15268 {
15269     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15270         /* to optimze, we temporarily turn off analysis mode while we feed
15271          * the remaining moves to the engine. Otherwise we get analysis output
15272          * after each move.
15273          */
15274         if (first.analysisSupport) {
15275           SendToProgram("exit\nforce\n", &first);
15276           first.analyzing = FALSE;
15277         }
15278     }
15279
15280     if (gameMode == IcsExamining && !pausing) {
15281         SendToICS(ics_prefix);
15282         SendToICS("forward 999999\n");
15283     } else {
15284         ForwardInner(forwardMostMove);
15285     }
15286
15287     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15288         /* we have fed all the moves, so reactivate analysis mode */
15289         SendToProgram("analyze\n", &first);
15290         first.analyzing = TRUE;
15291         /*first.maybeThinking = TRUE;*/
15292         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15293     }
15294 }
15295
15296 void
15297 BackwardInner (int target)
15298 {
15299     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15300
15301     if (appData.debugMode)
15302         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15303                 target, currentMove, forwardMostMove);
15304
15305     if (gameMode == EditPosition) return;
15306     seekGraphUp = FALSE;
15307     MarkTargetSquares(1);
15308     if (currentMove <= backwardMostMove) {
15309         ClearHighlights();
15310         DrawPosition(full_redraw, boards[currentMove]);
15311         return;
15312     }
15313     if (gameMode == PlayFromGameFile && !pausing)
15314       PauseEvent();
15315
15316     if (moveList[target][0]) {
15317         int fromX, fromY, toX, toY;
15318         toX = moveList[target][2] - AAA;
15319         toY = moveList[target][3] - ONE;
15320         if (moveList[target][1] == '@') {
15321             if (appData.highlightLastMove) {
15322                 SetHighlights(-1, -1, toX, toY);
15323             }
15324         } else {
15325             fromX = moveList[target][0] - AAA;
15326             fromY = moveList[target][1] - ONE;
15327             if (target == currentMove - 1) {
15328                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15329             }
15330             if (appData.highlightLastMove) {
15331                 SetHighlights(fromX, fromY, toX, toY);
15332             }
15333         }
15334     }
15335     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15336         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15337         while (currentMove > target) {
15338             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15339                 // null move cannot be undone. Reload program with move history before it.
15340                 int i;
15341                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15342                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15343                 }
15344                 SendBoard(&first, i);
15345               if(second.analyzing) SendBoard(&second, i);
15346                 for(currentMove=i; currentMove<target; currentMove++) {
15347                     SendMoveToProgram(currentMove, &first);
15348                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15349                 }
15350                 break;
15351             }
15352             SendToBoth("undo\n");
15353             currentMove--;
15354         }
15355     } else {
15356         currentMove = target;
15357     }
15358
15359     if (gameMode == EditGame || gameMode == EndOfGame) {
15360         whiteTimeRemaining = timeRemaining[0][currentMove];
15361         blackTimeRemaining = timeRemaining[1][currentMove];
15362     }
15363     DisplayBothClocks();
15364     DisplayMove(currentMove - 1);
15365     DrawPosition(full_redraw, boards[currentMove]);
15366     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15367     // [HGM] PV info: routine tests if comment empty
15368     DisplayComment(currentMove - 1, commentList[currentMove]);
15369     ClearMap(); // [HGM] exclude: invalidate map
15370 }
15371
15372 void
15373 BackwardEvent ()
15374 {
15375     if (gameMode == IcsExamining && !pausing) {
15376         SendToICS(ics_prefix);
15377         SendToICS("backward\n");
15378     } else {
15379         BackwardInner(currentMove - 1);
15380     }
15381 }
15382
15383 void
15384 ToStartEvent ()
15385 {
15386     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15387         /* to optimize, we temporarily turn off analysis mode while we undo
15388          * all the moves. Otherwise we get analysis output after each undo.
15389          */
15390         if (first.analysisSupport) {
15391           SendToProgram("exit\nforce\n", &first);
15392           first.analyzing = FALSE;
15393         }
15394     }
15395
15396     if (gameMode == IcsExamining && !pausing) {
15397         SendToICS(ics_prefix);
15398         SendToICS("backward 999999\n");
15399     } else {
15400         BackwardInner(backwardMostMove);
15401     }
15402
15403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15404         /* we have fed all the moves, so reactivate analysis mode */
15405         SendToProgram("analyze\n", &first);
15406         first.analyzing = TRUE;
15407         /*first.maybeThinking = TRUE;*/
15408         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15409     }
15410 }
15411
15412 void
15413 ToNrEvent (int to)
15414 {
15415   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15416   if (to >= forwardMostMove) to = forwardMostMove;
15417   if (to <= backwardMostMove) to = backwardMostMove;
15418   if (to < currentMove) {
15419     BackwardInner(to);
15420   } else {
15421     ForwardInner(to);
15422   }
15423 }
15424
15425 void
15426 RevertEvent (Boolean annotate)
15427 {
15428     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15429         return;
15430     }
15431     if (gameMode != IcsExamining) {
15432         DisplayError(_("You are not examining a game"), 0);
15433         return;
15434     }
15435     if (pausing) {
15436         DisplayError(_("You can't revert while pausing"), 0);
15437         return;
15438     }
15439     SendToICS(ics_prefix);
15440     SendToICS("revert\n");
15441 }
15442
15443 void
15444 RetractMoveEvent ()
15445 {
15446     switch (gameMode) {
15447       case MachinePlaysWhite:
15448       case MachinePlaysBlack:
15449         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15450             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15451             return;
15452         }
15453         if (forwardMostMove < 2) return;
15454         currentMove = forwardMostMove = forwardMostMove - 2;
15455         whiteTimeRemaining = timeRemaining[0][currentMove];
15456         blackTimeRemaining = timeRemaining[1][currentMove];
15457         DisplayBothClocks();
15458         DisplayMove(currentMove - 1);
15459         ClearHighlights();/*!! could figure this out*/
15460         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15461         SendToProgram("remove\n", &first);
15462         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15463         break;
15464
15465       case BeginningOfGame:
15466       default:
15467         break;
15468
15469       case IcsPlayingWhite:
15470       case IcsPlayingBlack:
15471         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15472             SendToICS(ics_prefix);
15473             SendToICS("takeback 2\n");
15474         } else {
15475             SendToICS(ics_prefix);
15476             SendToICS("takeback 1\n");
15477         }
15478         break;
15479     }
15480 }
15481
15482 void
15483 MoveNowEvent ()
15484 {
15485     ChessProgramState *cps;
15486
15487     switch (gameMode) {
15488       case MachinePlaysWhite:
15489         if (!WhiteOnMove(forwardMostMove)) {
15490             DisplayError(_("It is your turn"), 0);
15491             return;
15492         }
15493         cps = &first;
15494         break;
15495       case MachinePlaysBlack:
15496         if (WhiteOnMove(forwardMostMove)) {
15497             DisplayError(_("It is your turn"), 0);
15498             return;
15499         }
15500         cps = &first;
15501         break;
15502       case TwoMachinesPlay:
15503         if (WhiteOnMove(forwardMostMove) ==
15504             (first.twoMachinesColor[0] == 'w')) {
15505             cps = &first;
15506         } else {
15507             cps = &second;
15508         }
15509         break;
15510       case BeginningOfGame:
15511       default:
15512         return;
15513     }
15514     SendToProgram("?\n", cps);
15515 }
15516
15517 void
15518 TruncateGameEvent ()
15519 {
15520     EditGameEvent();
15521     if (gameMode != EditGame) return;
15522     TruncateGame();
15523 }
15524
15525 void
15526 TruncateGame ()
15527 {
15528     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15529     if (forwardMostMove > currentMove) {
15530         if (gameInfo.resultDetails != NULL) {
15531             free(gameInfo.resultDetails);
15532             gameInfo.resultDetails = NULL;
15533             gameInfo.result = GameUnfinished;
15534         }
15535         forwardMostMove = currentMove;
15536         HistorySet(parseList, backwardMostMove, forwardMostMove,
15537                    currentMove-1);
15538     }
15539 }
15540
15541 void
15542 HintEvent ()
15543 {
15544     if (appData.noChessProgram) return;
15545     switch (gameMode) {
15546       case MachinePlaysWhite:
15547         if (WhiteOnMove(forwardMostMove)) {
15548             DisplayError(_("Wait until your turn."), 0);
15549             return;
15550         }
15551         break;
15552       case BeginningOfGame:
15553       case MachinePlaysBlack:
15554         if (!WhiteOnMove(forwardMostMove)) {
15555             DisplayError(_("Wait until your turn."), 0);
15556             return;
15557         }
15558         break;
15559       default:
15560         DisplayError(_("No hint available"), 0);
15561         return;
15562     }
15563     SendToProgram("hint\n", &first);
15564     hintRequested = TRUE;
15565 }
15566
15567 void
15568 CreateBookEvent ()
15569 {
15570     ListGame * lg = (ListGame *) gameList.head;
15571     FILE *f, *g;
15572     int nItem;
15573     static int secondTime = FALSE;
15574
15575     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15576         DisplayError(_("Game list not loaded or empty"), 0);
15577         return;
15578     }
15579
15580     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15581         fclose(g);
15582         secondTime++;
15583         DisplayNote(_("Book file exists! Try again for overwrite."));
15584         return;
15585     }
15586
15587     creatingBook = TRUE;
15588     secondTime = FALSE;
15589
15590     /* Get list size */
15591     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15592         LoadGame(f, nItem, "", TRUE);
15593         AddGameToBook(TRUE);
15594         lg = (ListGame *) lg->node.succ;
15595     }
15596
15597     creatingBook = FALSE;
15598     FlushBook();
15599 }
15600
15601 void
15602 BookEvent ()
15603 {
15604     if (appData.noChessProgram) return;
15605     switch (gameMode) {
15606       case MachinePlaysWhite:
15607         if (WhiteOnMove(forwardMostMove)) {
15608             DisplayError(_("Wait until your turn."), 0);
15609             return;
15610         }
15611         break;
15612       case BeginningOfGame:
15613       case MachinePlaysBlack:
15614         if (!WhiteOnMove(forwardMostMove)) {
15615             DisplayError(_("Wait until your turn."), 0);
15616             return;
15617         }
15618         break;
15619       case EditPosition:
15620         EditPositionDone(TRUE);
15621         break;
15622       case TwoMachinesPlay:
15623         return;
15624       default:
15625         break;
15626     }
15627     SendToProgram("bk\n", &first);
15628     bookOutput[0] = NULLCHAR;
15629     bookRequested = TRUE;
15630 }
15631
15632 void
15633 AboutGameEvent ()
15634 {
15635     char *tags = PGNTags(&gameInfo);
15636     TagsPopUp(tags, CmailMsg());
15637     free(tags);
15638 }
15639
15640 /* end button procedures */
15641
15642 void
15643 PrintPosition (FILE *fp, int move)
15644 {
15645     int i, j;
15646
15647     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15648         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15649             char c = PieceToChar(boards[move][i][j]);
15650             fputc(c == 'x' ? '.' : c, fp);
15651             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15652         }
15653     }
15654     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15655       fprintf(fp, "white to play\n");
15656     else
15657       fprintf(fp, "black to play\n");
15658 }
15659
15660 void
15661 PrintOpponents (FILE *fp)
15662 {
15663     if (gameInfo.white != NULL) {
15664         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15665     } else {
15666         fprintf(fp, "\n");
15667     }
15668 }
15669
15670 /* Find last component of program's own name, using some heuristics */
15671 void
15672 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15673 {
15674     char *p, *q, c;
15675     int local = (strcmp(host, "localhost") == 0);
15676     while (!local && (p = strchr(prog, ';')) != NULL) {
15677         p++;
15678         while (*p == ' ') p++;
15679         prog = p;
15680     }
15681     if (*prog == '"' || *prog == '\'') {
15682         q = strchr(prog + 1, *prog);
15683     } else {
15684         q = strchr(prog, ' ');
15685     }
15686     if (q == NULL) q = prog + strlen(prog);
15687     p = q;
15688     while (p >= prog && *p != '/' && *p != '\\') p--;
15689     p++;
15690     if(p == prog && *p == '"') p++;
15691     c = *q; *q = 0;
15692     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15693     memcpy(buf, p, q - p);
15694     buf[q - p] = NULLCHAR;
15695     if (!local) {
15696         strcat(buf, "@");
15697         strcat(buf, host);
15698     }
15699 }
15700
15701 char *
15702 TimeControlTagValue ()
15703 {
15704     char buf[MSG_SIZ];
15705     if (!appData.clockMode) {
15706       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15707     } else if (movesPerSession > 0) {
15708       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15709     } else if (timeIncrement == 0) {
15710       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15711     } else {
15712       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15713     }
15714     return StrSave(buf);
15715 }
15716
15717 void
15718 SetGameInfo ()
15719 {
15720     /* This routine is used only for certain modes */
15721     VariantClass v = gameInfo.variant;
15722     ChessMove r = GameUnfinished;
15723     char *p = NULL;
15724
15725     if(keepInfo) return;
15726
15727     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15728         r = gameInfo.result;
15729         p = gameInfo.resultDetails;
15730         gameInfo.resultDetails = NULL;
15731     }
15732     ClearGameInfo(&gameInfo);
15733     gameInfo.variant = v;
15734
15735     switch (gameMode) {
15736       case MachinePlaysWhite:
15737         gameInfo.event = StrSave( appData.pgnEventHeader );
15738         gameInfo.site = StrSave(HostName());
15739         gameInfo.date = PGNDate();
15740         gameInfo.round = StrSave("-");
15741         gameInfo.white = StrSave(first.tidy);
15742         gameInfo.black = StrSave(UserName());
15743         gameInfo.timeControl = TimeControlTagValue();
15744         break;
15745
15746       case MachinePlaysBlack:
15747         gameInfo.event = StrSave( appData.pgnEventHeader );
15748         gameInfo.site = StrSave(HostName());
15749         gameInfo.date = PGNDate();
15750         gameInfo.round = StrSave("-");
15751         gameInfo.white = StrSave(UserName());
15752         gameInfo.black = StrSave(first.tidy);
15753         gameInfo.timeControl = TimeControlTagValue();
15754         break;
15755
15756       case TwoMachinesPlay:
15757         gameInfo.event = StrSave( appData.pgnEventHeader );
15758         gameInfo.site = StrSave(HostName());
15759         gameInfo.date = PGNDate();
15760         if (roundNr > 0) {
15761             char buf[MSG_SIZ];
15762             snprintf(buf, MSG_SIZ, "%d", roundNr);
15763             gameInfo.round = StrSave(buf);
15764         } else {
15765             gameInfo.round = StrSave("-");
15766         }
15767         if (first.twoMachinesColor[0] == 'w') {
15768             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15769             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15770         } else {
15771             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15772             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15773         }
15774         gameInfo.timeControl = TimeControlTagValue();
15775         break;
15776
15777       case EditGame:
15778         gameInfo.event = StrSave("Edited game");
15779         gameInfo.site = StrSave(HostName());
15780         gameInfo.date = PGNDate();
15781         gameInfo.round = StrSave("-");
15782         gameInfo.white = StrSave("-");
15783         gameInfo.black = StrSave("-");
15784         gameInfo.result = r;
15785         gameInfo.resultDetails = p;
15786         break;
15787
15788       case EditPosition:
15789         gameInfo.event = StrSave("Edited position");
15790         gameInfo.site = StrSave(HostName());
15791         gameInfo.date = PGNDate();
15792         gameInfo.round = StrSave("-");
15793         gameInfo.white = StrSave("-");
15794         gameInfo.black = StrSave("-");
15795         break;
15796
15797       case IcsPlayingWhite:
15798       case IcsPlayingBlack:
15799       case IcsObserving:
15800       case IcsExamining:
15801         break;
15802
15803       case PlayFromGameFile:
15804         gameInfo.event = StrSave("Game from non-PGN file");
15805         gameInfo.site = StrSave(HostName());
15806         gameInfo.date = PGNDate();
15807         gameInfo.round = StrSave("-");
15808         gameInfo.white = StrSave("?");
15809         gameInfo.black = StrSave("?");
15810         break;
15811
15812       default:
15813         break;
15814     }
15815 }
15816
15817 void
15818 ReplaceComment (int index, char *text)
15819 {
15820     int len;
15821     char *p;
15822     float score;
15823
15824     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15825        pvInfoList[index-1].depth == len &&
15826        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15827        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15828     while (*text == '\n') text++;
15829     len = strlen(text);
15830     while (len > 0 && text[len - 1] == '\n') len--;
15831
15832     if (commentList[index] != NULL)
15833       free(commentList[index]);
15834
15835     if (len == 0) {
15836         commentList[index] = NULL;
15837         return;
15838     }
15839   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15840       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15841       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15842     commentList[index] = (char *) malloc(len + 2);
15843     strncpy(commentList[index], text, len);
15844     commentList[index][len] = '\n';
15845     commentList[index][len + 1] = NULLCHAR;
15846   } else {
15847     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15848     char *p;
15849     commentList[index] = (char *) malloc(len + 7);
15850     safeStrCpy(commentList[index], "{\n", 3);
15851     safeStrCpy(commentList[index]+2, text, len+1);
15852     commentList[index][len+2] = NULLCHAR;
15853     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15854     strcat(commentList[index], "\n}\n");
15855   }
15856 }
15857
15858 void
15859 CrushCRs (char *text)
15860 {
15861   char *p = text;
15862   char *q = text;
15863   char ch;
15864
15865   do {
15866     ch = *p++;
15867     if (ch == '\r') continue;
15868     *q++ = ch;
15869   } while (ch != '\0');
15870 }
15871
15872 void
15873 AppendComment (int index, char *text, Boolean addBraces)
15874 /* addBraces  tells if we should add {} */
15875 {
15876     int oldlen, len;
15877     char *old;
15878
15879 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15880     if(addBraces == 3) addBraces = 0; else // force appending literally
15881     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15882
15883     CrushCRs(text);
15884     while (*text == '\n') text++;
15885     len = strlen(text);
15886     while (len > 0 && text[len - 1] == '\n') len--;
15887     text[len] = NULLCHAR;
15888
15889     if (len == 0) return;
15890
15891     if (commentList[index] != NULL) {
15892       Boolean addClosingBrace = addBraces;
15893         old = commentList[index];
15894         oldlen = strlen(old);
15895         while(commentList[index][oldlen-1] ==  '\n')
15896           commentList[index][--oldlen] = NULLCHAR;
15897         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15898         safeStrCpy(commentList[index], old, oldlen + len + 6);
15899         free(old);
15900         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15901         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15902           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15903           while (*text == '\n') { text++; len--; }
15904           commentList[index][--oldlen] = NULLCHAR;
15905       }
15906         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15907         else          strcat(commentList[index], "\n");
15908         strcat(commentList[index], text);
15909         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15910         else          strcat(commentList[index], "\n");
15911     } else {
15912         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15913         if(addBraces)
15914           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15915         else commentList[index][0] = NULLCHAR;
15916         strcat(commentList[index], text);
15917         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15918         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15919     }
15920 }
15921
15922 static char *
15923 FindStr (char * text, char * sub_text)
15924 {
15925     char * result = strstr( text, sub_text );
15926
15927     if( result != NULL ) {
15928         result += strlen( sub_text );
15929     }
15930
15931     return result;
15932 }
15933
15934 /* [AS] Try to extract PV info from PGN comment */
15935 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15936 char *
15937 GetInfoFromComment (int index, char * text)
15938 {
15939     char * sep = text, *p;
15940
15941     if( text != NULL && index > 0 ) {
15942         int score = 0;
15943         int depth = 0;
15944         int time = -1, sec = 0, deci;
15945         char * s_eval = FindStr( text, "[%eval " );
15946         char * s_emt = FindStr( text, "[%emt " );
15947 #if 0
15948         if( s_eval != NULL || s_emt != NULL ) {
15949 #else
15950         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15951 #endif
15952             /* New style */
15953             char delim;
15954
15955             if( s_eval != NULL ) {
15956                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15957                     return text;
15958                 }
15959
15960                 if( delim != ']' ) {
15961                     return text;
15962                 }
15963             }
15964
15965             if( s_emt != NULL ) {
15966             }
15967                 return text;
15968         }
15969         else {
15970             /* We expect something like: [+|-]nnn.nn/dd */
15971             int score_lo = 0;
15972
15973             if(*text != '{') return text; // [HGM] braces: must be normal comment
15974
15975             sep = strchr( text, '/' );
15976             if( sep == NULL || sep < (text+4) ) {
15977                 return text;
15978             }
15979
15980             p = text;
15981             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15982             if(p[1] == '(') { // comment starts with PV
15983                p = strchr(p, ')'); // locate end of PV
15984                if(p == NULL || sep < p+5) return text;
15985                // at this point we have something like "{(.*) +0.23/6 ..."
15986                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15987                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15988                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15989             }
15990             time = -1; sec = -1; deci = -1;
15991             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15992                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15993                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15994                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15995                 return text;
15996             }
15997
15998             if( score_lo < 0 || score_lo >= 100 ) {
15999                 return text;
16000             }
16001
16002             if(sec >= 0) time = 600*time + 10*sec; else
16003             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16004
16005             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16006
16007             /* [HGM] PV time: now locate end of PV info */
16008             while( *++sep >= '0' && *sep <= '9'); // strip depth
16009             if(time >= 0)
16010             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16011             if(sec >= 0)
16012             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16013             if(deci >= 0)
16014             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16015             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16016         }
16017
16018         if( depth <= 0 ) {
16019             return text;
16020         }
16021
16022         if( time < 0 ) {
16023             time = -1;
16024         }
16025
16026         pvInfoList[index-1].depth = depth;
16027         pvInfoList[index-1].score = score;
16028         pvInfoList[index-1].time  = 10*time; // centi-sec
16029         if(*sep == '}') *sep = 0; else *--sep = '{';
16030         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16031     }
16032     return sep;
16033 }
16034
16035 void
16036 SendToProgram (char *message, ChessProgramState *cps)
16037 {
16038     int count, outCount, error;
16039     char buf[MSG_SIZ];
16040
16041     if (cps->pr == NoProc) return;
16042     Attention(cps);
16043
16044     if (appData.debugMode) {
16045         TimeMark now;
16046         GetTimeMark(&now);
16047         fprintf(debugFP, "%ld >%-6s: %s",
16048                 SubtractTimeMarks(&now, &programStartTime),
16049                 cps->which, message);
16050         if(serverFP)
16051             fprintf(serverFP, "%ld >%-6s: %s",
16052                 SubtractTimeMarks(&now, &programStartTime),
16053                 cps->which, message), fflush(serverFP);
16054     }
16055
16056     count = strlen(message);
16057     outCount = OutputToProcess(cps->pr, message, count, &error);
16058     if (outCount < count && !exiting
16059                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16060       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16061       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16062         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16063             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16064                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16065                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16066                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16067             } else {
16068                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16069                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16070                 gameInfo.result = res;
16071             }
16072             gameInfo.resultDetails = StrSave(buf);
16073         }
16074         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16075         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16076     }
16077 }
16078
16079 void
16080 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16081 {
16082     char *end_str;
16083     char buf[MSG_SIZ];
16084     ChessProgramState *cps = (ChessProgramState *)closure;
16085
16086     if (isr != cps->isr) return; /* Killed intentionally */
16087     if (count <= 0) {
16088         if (count == 0) {
16089             RemoveInputSource(cps->isr);
16090             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16091                     _(cps->which), cps->program);
16092             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16093             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16094                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16095                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16096                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16097                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16098                 } else {
16099                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16100                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16101                     gameInfo.result = res;
16102                 }
16103                 gameInfo.resultDetails = StrSave(buf);
16104             }
16105             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16106             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16107         } else {
16108             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16109                     _(cps->which), cps->program);
16110             RemoveInputSource(cps->isr);
16111
16112             /* [AS] Program is misbehaving badly... kill it */
16113             if( count == -2 ) {
16114                 DestroyChildProcess( cps->pr, 9 );
16115                 cps->pr = NoProc;
16116             }
16117
16118             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16119         }
16120         return;
16121     }
16122
16123     if ((end_str = strchr(message, '\r')) != NULL)
16124       *end_str = NULLCHAR;
16125     if ((end_str = strchr(message, '\n')) != NULL)
16126       *end_str = NULLCHAR;
16127
16128     if (appData.debugMode) {
16129         TimeMark now; int print = 1;
16130         char *quote = ""; char c; int i;
16131
16132         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16133                 char start = message[0];
16134                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16135                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16136                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16137                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16138                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16139                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16140                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16141                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16142                    sscanf(message, "hint: %c", &c)!=1 &&
16143                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16144                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16145                     print = (appData.engineComments >= 2);
16146                 }
16147                 message[0] = start; // restore original message
16148         }
16149         if(print) {
16150                 GetTimeMark(&now);
16151                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16152                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16153                         quote,
16154                         message);
16155                 if(serverFP)
16156                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16157                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16158                         quote,
16159                         message), fflush(serverFP);
16160         }
16161     }
16162
16163     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16164     if (appData.icsEngineAnalyze) {
16165         if (strstr(message, "whisper") != NULL ||
16166              strstr(message, "kibitz") != NULL ||
16167             strstr(message, "tellics") != NULL) return;
16168     }
16169
16170     HandleMachineMove(message, cps);
16171 }
16172
16173
16174 void
16175 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16176 {
16177     char buf[MSG_SIZ];
16178     int seconds;
16179
16180     if( timeControl_2 > 0 ) {
16181         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16182             tc = timeControl_2;
16183         }
16184     }
16185     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16186     inc /= cps->timeOdds;
16187     st  /= cps->timeOdds;
16188
16189     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16190
16191     if (st > 0) {
16192       /* Set exact time per move, normally using st command */
16193       if (cps->stKludge) {
16194         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16195         seconds = st % 60;
16196         if (seconds == 0) {
16197           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16198         } else {
16199           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16200         }
16201       } else {
16202         snprintf(buf, MSG_SIZ, "st %d\n", st);
16203       }
16204     } else {
16205       /* Set conventional or incremental time control, using level command */
16206       if (seconds == 0) {
16207         /* Note old gnuchess bug -- minutes:seconds used to not work.
16208            Fixed in later versions, but still avoid :seconds
16209            when seconds is 0. */
16210         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16211       } else {
16212         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16213                  seconds, inc/1000.);
16214       }
16215     }
16216     SendToProgram(buf, cps);
16217
16218     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16219     /* Orthogonally, limit search to given depth */
16220     if (sd > 0) {
16221       if (cps->sdKludge) {
16222         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16223       } else {
16224         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16225       }
16226       SendToProgram(buf, cps);
16227     }
16228
16229     if(cps->nps >= 0) { /* [HGM] nps */
16230         if(cps->supportsNPS == FALSE)
16231           cps->nps = -1; // don't use if engine explicitly says not supported!
16232         else {
16233           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16234           SendToProgram(buf, cps);
16235         }
16236     }
16237 }
16238
16239 ChessProgramState *
16240 WhitePlayer ()
16241 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16242 {
16243     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16244        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16245         return &second;
16246     return &first;
16247 }
16248
16249 void
16250 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16251 {
16252     char message[MSG_SIZ];
16253     long time, otime;
16254
16255     /* Note: this routine must be called when the clocks are stopped
16256        or when they have *just* been set or switched; otherwise
16257        it will be off by the time since the current tick started.
16258     */
16259     if (machineWhite) {
16260         time = whiteTimeRemaining / 10;
16261         otime = blackTimeRemaining / 10;
16262     } else {
16263         time = blackTimeRemaining / 10;
16264         otime = whiteTimeRemaining / 10;
16265     }
16266     /* [HGM] translate opponent's time by time-odds factor */
16267     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16268
16269     if (time <= 0) time = 1;
16270     if (otime <= 0) otime = 1;
16271
16272     snprintf(message, MSG_SIZ, "time %ld\n", time);
16273     SendToProgram(message, cps);
16274
16275     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16276     SendToProgram(message, cps);
16277 }
16278
16279 char *
16280 EngineDefinedVariant (ChessProgramState *cps, int n)
16281 {   // return name of n-th unknown variant that engine supports
16282     static char buf[MSG_SIZ];
16283     char *p, *s = cps->variants;
16284     if(!s) return NULL;
16285     do { // parse string from variants feature
16286       VariantClass v;
16287         p = strchr(s, ',');
16288         if(p) *p = NULLCHAR;
16289       v = StringToVariant(s);
16290       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16291         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16292             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16293         }
16294         if(p) *p++ = ',';
16295         if(n < 0) return buf;
16296     } while(s = p);
16297     return NULL;
16298 }
16299
16300 int
16301 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16302 {
16303   char buf[MSG_SIZ];
16304   int len = strlen(name);
16305   int val;
16306
16307   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16308     (*p) += len + 1;
16309     sscanf(*p, "%d", &val);
16310     *loc = (val != 0);
16311     while (**p && **p != ' ')
16312       (*p)++;
16313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16314     SendToProgram(buf, cps);
16315     return TRUE;
16316   }
16317   return FALSE;
16318 }
16319
16320 int
16321 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16322 {
16323   char buf[MSG_SIZ];
16324   int len = strlen(name);
16325   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16326     (*p) += len + 1;
16327     sscanf(*p, "%d", loc);
16328     while (**p && **p != ' ') (*p)++;
16329     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16330     SendToProgram(buf, cps);
16331     return TRUE;
16332   }
16333   return FALSE;
16334 }
16335
16336 int
16337 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16338 {
16339   char buf[MSG_SIZ];
16340   int len = strlen(name);
16341   if (strncmp((*p), name, len) == 0
16342       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16343     (*p) += len + 2;
16344     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16345     sscanf(*p, "%[^\"]", *loc);
16346     while (**p && **p != '\"') (*p)++;
16347     if (**p == '\"') (*p)++;
16348     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16349     SendToProgram(buf, cps);
16350     return TRUE;
16351   }
16352   return FALSE;
16353 }
16354
16355 int
16356 ParseOption (Option *opt, ChessProgramState *cps)
16357 // [HGM] options: process the string that defines an engine option, and determine
16358 // name, type, default value, and allowed value range
16359 {
16360         char *p, *q, buf[MSG_SIZ];
16361         int n, min = (-1)<<31, max = 1<<31, def;
16362
16363         if(p = strstr(opt->name, " -spin ")) {
16364             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16365             if(max < min) max = min; // enforce consistency
16366             if(def < min) def = min;
16367             if(def > max) def = max;
16368             opt->value = def;
16369             opt->min = min;
16370             opt->max = max;
16371             opt->type = Spin;
16372         } else if((p = strstr(opt->name, " -slider "))) {
16373             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16374             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16375             if(max < min) max = min; // enforce consistency
16376             if(def < min) def = min;
16377             if(def > max) def = max;
16378             opt->value = def;
16379             opt->min = min;
16380             opt->max = max;
16381             opt->type = Spin; // Slider;
16382         } else if((p = strstr(opt->name, " -string "))) {
16383             opt->textValue = p+9;
16384             opt->type = TextBox;
16385         } else if((p = strstr(opt->name, " -file "))) {
16386             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16387             opt->textValue = p+7;
16388             opt->type = FileName; // FileName;
16389         } else if((p = strstr(opt->name, " -path "))) {
16390             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16391             opt->textValue = p+7;
16392             opt->type = PathName; // PathName;
16393         } else if(p = strstr(opt->name, " -check ")) {
16394             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16395             opt->value = (def != 0);
16396             opt->type = CheckBox;
16397         } else if(p = strstr(opt->name, " -combo ")) {
16398             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16399             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16400             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16401             opt->value = n = 0;
16402             while(q = StrStr(q, " /// ")) {
16403                 n++; *q = 0;    // count choices, and null-terminate each of them
16404                 q += 5;
16405                 if(*q == '*') { // remember default, which is marked with * prefix
16406                     q++;
16407                     opt->value = n;
16408                 }
16409                 cps->comboList[cps->comboCnt++] = q;
16410             }
16411             cps->comboList[cps->comboCnt++] = NULL;
16412             opt->max = n + 1;
16413             opt->type = ComboBox;
16414         } else if(p = strstr(opt->name, " -button")) {
16415             opt->type = Button;
16416         } else if(p = strstr(opt->name, " -save")) {
16417             opt->type = SaveButton;
16418         } else return FALSE;
16419         *p = 0; // terminate option name
16420         // now look if the command-line options define a setting for this engine option.
16421         if(cps->optionSettings && cps->optionSettings[0])
16422             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16423         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16424           snprintf(buf, MSG_SIZ, "option %s", p);
16425                 if(p = strstr(buf, ",")) *p = 0;
16426                 if(q = strchr(buf, '=')) switch(opt->type) {
16427                     case ComboBox:
16428                         for(n=0; n<opt->max; n++)
16429                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16430                         break;
16431                     case TextBox:
16432                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16433                         break;
16434                     case Spin:
16435                     case CheckBox:
16436                         opt->value = atoi(q+1);
16437                     default:
16438                         break;
16439                 }
16440                 strcat(buf, "\n");
16441                 SendToProgram(buf, cps);
16442         }
16443         return TRUE;
16444 }
16445
16446 void
16447 FeatureDone (ChessProgramState *cps, int val)
16448 {
16449   DelayedEventCallback cb = GetDelayedEvent();
16450   if ((cb == InitBackEnd3 && cps == &first) ||
16451       (cb == SettingsMenuIfReady && cps == &second) ||
16452       (cb == LoadEngine) ||
16453       (cb == TwoMachinesEventIfReady)) {
16454     CancelDelayedEvent();
16455     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16456   }
16457   cps->initDone = val;
16458   if(val) cps->reload = FALSE;
16459 }
16460
16461 /* Parse feature command from engine */
16462 void
16463 ParseFeatures (char *args, ChessProgramState *cps)
16464 {
16465   char *p = args;
16466   char *q = NULL;
16467   int val;
16468   char buf[MSG_SIZ];
16469
16470   for (;;) {
16471     while (*p == ' ') p++;
16472     if (*p == NULLCHAR) return;
16473
16474     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16475     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16476     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16477     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16478     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16479     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16480     if (BoolFeature(&p, "reuse", &val, cps)) {
16481       /* Engine can disable reuse, but can't enable it if user said no */
16482       if (!val) cps->reuse = FALSE;
16483       continue;
16484     }
16485     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16486     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16487       if (gameMode == TwoMachinesPlay) {
16488         DisplayTwoMachinesTitle();
16489       } else {
16490         DisplayTitle("");
16491       }
16492       continue;
16493     }
16494     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16495     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16496     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16497     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16498     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16499     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16500     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16501     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16502     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16503     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16504     if (IntFeature(&p, "done", &val, cps)) {
16505       FeatureDone(cps, val);
16506       continue;
16507     }
16508     /* Added by Tord: */
16509     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16510     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16511     /* End of additions by Tord */
16512
16513     /* [HGM] added features: */
16514     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16515     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16516     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16517     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16518     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16519     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16520     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16521     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16522         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16523         FREE(cps->option[cps->nrOptions].name);
16524         cps->option[cps->nrOptions].name = q; q = NULL;
16525         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16526           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16527             SendToProgram(buf, cps);
16528             continue;
16529         }
16530         if(cps->nrOptions >= MAX_OPTIONS) {
16531             cps->nrOptions--;
16532             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16533             DisplayError(buf, 0);
16534         }
16535         continue;
16536     }
16537     /* End of additions by HGM */
16538
16539     /* unknown feature: complain and skip */
16540     q = p;
16541     while (*q && *q != '=') q++;
16542     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16543     SendToProgram(buf, cps);
16544     p = q;
16545     if (*p == '=') {
16546       p++;
16547       if (*p == '\"') {
16548         p++;
16549         while (*p && *p != '\"') p++;
16550         if (*p == '\"') p++;
16551       } else {
16552         while (*p && *p != ' ') p++;
16553       }
16554     }
16555   }
16556
16557 }
16558
16559 void
16560 PeriodicUpdatesEvent (int newState)
16561 {
16562     if (newState == appData.periodicUpdates)
16563       return;
16564
16565     appData.periodicUpdates=newState;
16566
16567     /* Display type changes, so update it now */
16568 //    DisplayAnalysis();
16569
16570     /* Get the ball rolling again... */
16571     if (newState) {
16572         AnalysisPeriodicEvent(1);
16573         StartAnalysisClock();
16574     }
16575 }
16576
16577 void
16578 PonderNextMoveEvent (int newState)
16579 {
16580     if (newState == appData.ponderNextMove) return;
16581     if (gameMode == EditPosition) EditPositionDone(TRUE);
16582     if (newState) {
16583         SendToProgram("hard\n", &first);
16584         if (gameMode == TwoMachinesPlay) {
16585             SendToProgram("hard\n", &second);
16586         }
16587     } else {
16588         SendToProgram("easy\n", &first);
16589         thinkOutput[0] = NULLCHAR;
16590         if (gameMode == TwoMachinesPlay) {
16591             SendToProgram("easy\n", &second);
16592         }
16593     }
16594     appData.ponderNextMove = newState;
16595 }
16596
16597 void
16598 NewSettingEvent (int option, int *feature, char *command, int value)
16599 {
16600     char buf[MSG_SIZ];
16601
16602     if (gameMode == EditPosition) EditPositionDone(TRUE);
16603     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16604     if(feature == NULL || *feature) SendToProgram(buf, &first);
16605     if (gameMode == TwoMachinesPlay) {
16606         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16607     }
16608 }
16609
16610 void
16611 ShowThinkingEvent ()
16612 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16613 {
16614     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16615     int newState = appData.showThinking
16616         // [HGM] thinking: other features now need thinking output as well
16617         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16618
16619     if (oldState == newState) return;
16620     oldState = newState;
16621     if (gameMode == EditPosition) EditPositionDone(TRUE);
16622     if (oldState) {
16623         SendToProgram("post\n", &first);
16624         if (gameMode == TwoMachinesPlay) {
16625             SendToProgram("post\n", &second);
16626         }
16627     } else {
16628         SendToProgram("nopost\n", &first);
16629         thinkOutput[0] = NULLCHAR;
16630         if (gameMode == TwoMachinesPlay) {
16631             SendToProgram("nopost\n", &second);
16632         }
16633     }
16634 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16635 }
16636
16637 void
16638 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16639 {
16640   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16641   if (pr == NoProc) return;
16642   AskQuestion(title, question, replyPrefix, pr);
16643 }
16644
16645 void
16646 TypeInEvent (char firstChar)
16647 {
16648     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16649         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16650         gameMode == AnalyzeMode || gameMode == EditGame ||
16651         gameMode == EditPosition || gameMode == IcsExamining ||
16652         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16653         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16654                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16655                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16656         gameMode == Training) PopUpMoveDialog(firstChar);
16657 }
16658
16659 void
16660 TypeInDoneEvent (char *move)
16661 {
16662         Board board;
16663         int n, fromX, fromY, toX, toY;
16664         char promoChar;
16665         ChessMove moveType;
16666
16667         // [HGM] FENedit
16668         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16669                 EditPositionPasteFEN(move);
16670                 return;
16671         }
16672         // [HGM] movenum: allow move number to be typed in any mode
16673         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16674           ToNrEvent(2*n-1);
16675           return;
16676         }
16677         // undocumented kludge: allow command-line option to be typed in!
16678         // (potentially fatal, and does not implement the effect of the option.)
16679         // should only be used for options that are values on which future decisions will be made,
16680         // and definitely not on options that would be used during initialization.
16681         if(strstr(move, "!!! -") == move) {
16682             ParseArgsFromString(move+4);
16683             return;
16684         }
16685
16686       if (gameMode != EditGame && currentMove != forwardMostMove &&
16687         gameMode != Training) {
16688         DisplayMoveError(_("Displayed move is not current"));
16689       } else {
16690         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16691           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16692         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16693         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16694           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16695           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16696         } else {
16697           DisplayMoveError(_("Could not parse move"));
16698         }
16699       }
16700 }
16701
16702 void
16703 DisplayMove (int moveNumber)
16704 {
16705     char message[MSG_SIZ];
16706     char res[MSG_SIZ];
16707     char cpThinkOutput[MSG_SIZ];
16708
16709     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16710
16711     if (moveNumber == forwardMostMove - 1 ||
16712         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16713
16714         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16715
16716         if (strchr(cpThinkOutput, '\n')) {
16717             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16718         }
16719     } else {
16720         *cpThinkOutput = NULLCHAR;
16721     }
16722
16723     /* [AS] Hide thinking from human user */
16724     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16725         *cpThinkOutput = NULLCHAR;
16726         if( thinkOutput[0] != NULLCHAR ) {
16727             int i;
16728
16729             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16730                 cpThinkOutput[i] = '.';
16731             }
16732             cpThinkOutput[i] = NULLCHAR;
16733             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16734         }
16735     }
16736
16737     if (moveNumber == forwardMostMove - 1 &&
16738         gameInfo.resultDetails != NULL) {
16739         if (gameInfo.resultDetails[0] == NULLCHAR) {
16740           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16741         } else {
16742           snprintf(res, MSG_SIZ, " {%s} %s",
16743                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16744         }
16745     } else {
16746         res[0] = NULLCHAR;
16747     }
16748
16749     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16750         DisplayMessage(res, cpThinkOutput);
16751     } else {
16752       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16753                 WhiteOnMove(moveNumber) ? " " : ".. ",
16754                 parseList[moveNumber], res);
16755         DisplayMessage(message, cpThinkOutput);
16756     }
16757 }
16758
16759 void
16760 DisplayComment (int moveNumber, char *text)
16761 {
16762     char title[MSG_SIZ];
16763
16764     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16765       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16766     } else {
16767       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16768               WhiteOnMove(moveNumber) ? " " : ".. ",
16769               parseList[moveNumber]);
16770     }
16771     if (text != NULL && (appData.autoDisplayComment || commentUp))
16772         CommentPopUp(title, text);
16773 }
16774
16775 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16776  * might be busy thinking or pondering.  It can be omitted if your
16777  * gnuchess is configured to stop thinking immediately on any user
16778  * input.  However, that gnuchess feature depends on the FIONREAD
16779  * ioctl, which does not work properly on some flavors of Unix.
16780  */
16781 void
16782 Attention (ChessProgramState *cps)
16783 {
16784 #if ATTENTION
16785     if (!cps->useSigint) return;
16786     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16787     switch (gameMode) {
16788       case MachinePlaysWhite:
16789       case MachinePlaysBlack:
16790       case TwoMachinesPlay:
16791       case IcsPlayingWhite:
16792       case IcsPlayingBlack:
16793       case AnalyzeMode:
16794       case AnalyzeFile:
16795         /* Skip if we know it isn't thinking */
16796         if (!cps->maybeThinking) return;
16797         if (appData.debugMode)
16798           fprintf(debugFP, "Interrupting %s\n", cps->which);
16799         InterruptChildProcess(cps->pr);
16800         cps->maybeThinking = FALSE;
16801         break;
16802       default:
16803         break;
16804     }
16805 #endif /*ATTENTION*/
16806 }
16807
16808 int
16809 CheckFlags ()
16810 {
16811     if (whiteTimeRemaining <= 0) {
16812         if (!whiteFlag) {
16813             whiteFlag = TRUE;
16814             if (appData.icsActive) {
16815                 if (appData.autoCallFlag &&
16816                     gameMode == IcsPlayingBlack && !blackFlag) {
16817                   SendToICS(ics_prefix);
16818                   SendToICS("flag\n");
16819                 }
16820             } else {
16821                 if (blackFlag) {
16822                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16823                 } else {
16824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16825                     if (appData.autoCallFlag) {
16826                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16827                         return TRUE;
16828                     }
16829                 }
16830             }
16831         }
16832     }
16833     if (blackTimeRemaining <= 0) {
16834         if (!blackFlag) {
16835             blackFlag = TRUE;
16836             if (appData.icsActive) {
16837                 if (appData.autoCallFlag &&
16838                     gameMode == IcsPlayingWhite && !whiteFlag) {
16839                   SendToICS(ics_prefix);
16840                   SendToICS("flag\n");
16841                 }
16842             } else {
16843                 if (whiteFlag) {
16844                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16845                 } else {
16846                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16847                     if (appData.autoCallFlag) {
16848                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16849                         return TRUE;
16850                     }
16851                 }
16852             }
16853         }
16854     }
16855     return FALSE;
16856 }
16857
16858 void
16859 CheckTimeControl ()
16860 {
16861     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16862         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16863
16864     /*
16865      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16866      */
16867     if ( !WhiteOnMove(forwardMostMove) ) {
16868         /* White made time control */
16869         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16870         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16871         /* [HGM] time odds: correct new time quota for time odds! */
16872                                             / WhitePlayer()->timeOdds;
16873         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16874     } else {
16875         lastBlack -= blackTimeRemaining;
16876         /* Black made time control */
16877         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16878                                             / WhitePlayer()->other->timeOdds;
16879         lastWhite = whiteTimeRemaining;
16880     }
16881 }
16882
16883 void
16884 DisplayBothClocks ()
16885 {
16886     int wom = gameMode == EditPosition ?
16887       !blackPlaysFirst : WhiteOnMove(currentMove);
16888     DisplayWhiteClock(whiteTimeRemaining, wom);
16889     DisplayBlackClock(blackTimeRemaining, !wom);
16890 }
16891
16892
16893 /* Timekeeping seems to be a portability nightmare.  I think everyone
16894    has ftime(), but I'm really not sure, so I'm including some ifdefs
16895    to use other calls if you don't.  Clocks will be less accurate if
16896    you have neither ftime nor gettimeofday.
16897 */
16898
16899 /* VS 2008 requires the #include outside of the function */
16900 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16901 #include <sys/timeb.h>
16902 #endif
16903
16904 /* Get the current time as a TimeMark */
16905 void
16906 GetTimeMark (TimeMark *tm)
16907 {
16908 #if HAVE_GETTIMEOFDAY
16909
16910     struct timeval timeVal;
16911     struct timezone timeZone;
16912
16913     gettimeofday(&timeVal, &timeZone);
16914     tm->sec = (long) timeVal.tv_sec;
16915     tm->ms = (int) (timeVal.tv_usec / 1000L);
16916
16917 #else /*!HAVE_GETTIMEOFDAY*/
16918 #if HAVE_FTIME
16919
16920 // include <sys/timeb.h> / moved to just above start of function
16921     struct timeb timeB;
16922
16923     ftime(&timeB);
16924     tm->sec = (long) timeB.time;
16925     tm->ms = (int) timeB.millitm;
16926
16927 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16928     tm->sec = (long) time(NULL);
16929     tm->ms = 0;
16930 #endif
16931 #endif
16932 }
16933
16934 /* Return the difference in milliseconds between two
16935    time marks.  We assume the difference will fit in a long!
16936 */
16937 long
16938 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16939 {
16940     return 1000L*(tm2->sec - tm1->sec) +
16941            (long) (tm2->ms - tm1->ms);
16942 }
16943
16944
16945 /*
16946  * Code to manage the game clocks.
16947  *
16948  * In tournament play, black starts the clock and then white makes a move.
16949  * We give the human user a slight advantage if he is playing white---the
16950  * clocks don't run until he makes his first move, so it takes zero time.
16951  * Also, we don't account for network lag, so we could get out of sync
16952  * with GNU Chess's clock -- but then, referees are always right.
16953  */
16954
16955 static TimeMark tickStartTM;
16956 static long intendedTickLength;
16957
16958 long
16959 NextTickLength (long timeRemaining)
16960 {
16961     long nominalTickLength, nextTickLength;
16962
16963     if (timeRemaining > 0L && timeRemaining <= 10000L)
16964       nominalTickLength = 100L;
16965     else
16966       nominalTickLength = 1000L;
16967     nextTickLength = timeRemaining % nominalTickLength;
16968     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16969
16970     return nextTickLength;
16971 }
16972
16973 /* Adjust clock one minute up or down */
16974 void
16975 AdjustClock (Boolean which, int dir)
16976 {
16977     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16978     if(which) blackTimeRemaining += 60000*dir;
16979     else      whiteTimeRemaining += 60000*dir;
16980     DisplayBothClocks();
16981     adjustedClock = TRUE;
16982 }
16983
16984 /* Stop clocks and reset to a fresh time control */
16985 void
16986 ResetClocks ()
16987 {
16988     (void) StopClockTimer();
16989     if (appData.icsActive) {
16990         whiteTimeRemaining = blackTimeRemaining = 0;
16991     } else if (searchTime) {
16992         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16993         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16994     } else { /* [HGM] correct new time quote for time odds */
16995         whiteTC = blackTC = fullTimeControlString;
16996         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16997         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16998     }
16999     if (whiteFlag || blackFlag) {
17000         DisplayTitle("");
17001         whiteFlag = blackFlag = FALSE;
17002     }
17003     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17004     DisplayBothClocks();
17005     adjustedClock = FALSE;
17006 }
17007
17008 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17009
17010 /* Decrement running clock by amount of time that has passed */
17011 void
17012 DecrementClocks ()
17013 {
17014     long timeRemaining;
17015     long lastTickLength, fudge;
17016     TimeMark now;
17017
17018     if (!appData.clockMode) return;
17019     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17020
17021     GetTimeMark(&now);
17022
17023     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17024
17025     /* Fudge if we woke up a little too soon */
17026     fudge = intendedTickLength - lastTickLength;
17027     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17028
17029     if (WhiteOnMove(forwardMostMove)) {
17030         if(whiteNPS >= 0) lastTickLength = 0;
17031         timeRemaining = whiteTimeRemaining -= lastTickLength;
17032         if(timeRemaining < 0 && !appData.icsActive) {
17033             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17034             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17035                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17036                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17037             }
17038         }
17039         DisplayWhiteClock(whiteTimeRemaining - fudge,
17040                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17041     } else {
17042         if(blackNPS >= 0) lastTickLength = 0;
17043         timeRemaining = blackTimeRemaining -= lastTickLength;
17044         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17045             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17046             if(suddenDeath) {
17047                 blackStartMove = forwardMostMove;
17048                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17049             }
17050         }
17051         DisplayBlackClock(blackTimeRemaining - fudge,
17052                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17053     }
17054     if (CheckFlags()) return;
17055
17056     if(twoBoards) { // count down secondary board's clocks as well
17057         activePartnerTime -= lastTickLength;
17058         partnerUp = 1;
17059         if(activePartner == 'W')
17060             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17061         else
17062             DisplayBlackClock(activePartnerTime, TRUE);
17063         partnerUp = 0;
17064     }
17065
17066     tickStartTM = now;
17067     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17068     StartClockTimer(intendedTickLength);
17069
17070     /* if the time remaining has fallen below the alarm threshold, sound the
17071      * alarm. if the alarm has sounded and (due to a takeback or time control
17072      * with increment) the time remaining has increased to a level above the
17073      * threshold, reset the alarm so it can sound again.
17074      */
17075
17076     if (appData.icsActive && appData.icsAlarm) {
17077
17078         /* make sure we are dealing with the user's clock */
17079         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17080                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17081            )) return;
17082
17083         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17084             alarmSounded = FALSE;
17085         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17086             PlayAlarmSound();
17087             alarmSounded = TRUE;
17088         }
17089     }
17090 }
17091
17092
17093 /* A player has just moved, so stop the previously running
17094    clock and (if in clock mode) start the other one.
17095    We redisplay both clocks in case we're in ICS mode, because
17096    ICS gives us an update to both clocks after every move.
17097    Note that this routine is called *after* forwardMostMove
17098    is updated, so the last fractional tick must be subtracted
17099    from the color that is *not* on move now.
17100 */
17101 void
17102 SwitchClocks (int newMoveNr)
17103 {
17104     long lastTickLength;
17105     TimeMark now;
17106     int flagged = FALSE;
17107
17108     GetTimeMark(&now);
17109
17110     if (StopClockTimer() && appData.clockMode) {
17111         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17112         if (!WhiteOnMove(forwardMostMove)) {
17113             if(blackNPS >= 0) lastTickLength = 0;
17114             blackTimeRemaining -= lastTickLength;
17115            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17116 //         if(pvInfoList[forwardMostMove].time == -1)
17117                  pvInfoList[forwardMostMove].time =               // use GUI time
17118                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17119         } else {
17120            if(whiteNPS >= 0) lastTickLength = 0;
17121            whiteTimeRemaining -= lastTickLength;
17122            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17123 //         if(pvInfoList[forwardMostMove].time == -1)
17124                  pvInfoList[forwardMostMove].time =
17125                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17126         }
17127         flagged = CheckFlags();
17128     }
17129     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17130     CheckTimeControl();
17131
17132     if (flagged || !appData.clockMode) return;
17133
17134     switch (gameMode) {
17135       case MachinePlaysBlack:
17136       case MachinePlaysWhite:
17137       case BeginningOfGame:
17138         if (pausing) return;
17139         break;
17140
17141       case EditGame:
17142       case PlayFromGameFile:
17143       case IcsExamining:
17144         return;
17145
17146       default:
17147         break;
17148     }
17149
17150     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17151         if(WhiteOnMove(forwardMostMove))
17152              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17153         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17154     }
17155
17156     tickStartTM = now;
17157     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17158       whiteTimeRemaining : blackTimeRemaining);
17159     StartClockTimer(intendedTickLength);
17160 }
17161
17162
17163 /* Stop both clocks */
17164 void
17165 StopClocks ()
17166 {
17167     long lastTickLength;
17168     TimeMark now;
17169
17170     if (!StopClockTimer()) return;
17171     if (!appData.clockMode) return;
17172
17173     GetTimeMark(&now);
17174
17175     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17176     if (WhiteOnMove(forwardMostMove)) {
17177         if(whiteNPS >= 0) lastTickLength = 0;
17178         whiteTimeRemaining -= lastTickLength;
17179         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17180     } else {
17181         if(blackNPS >= 0) lastTickLength = 0;
17182         blackTimeRemaining -= lastTickLength;
17183         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17184     }
17185     CheckFlags();
17186 }
17187
17188 /* Start clock of player on move.  Time may have been reset, so
17189    if clock is already running, stop and restart it. */
17190 void
17191 StartClocks ()
17192 {
17193     (void) StopClockTimer(); /* in case it was running already */
17194     DisplayBothClocks();
17195     if (CheckFlags()) return;
17196
17197     if (!appData.clockMode) return;
17198     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17199
17200     GetTimeMark(&tickStartTM);
17201     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17202       whiteTimeRemaining : blackTimeRemaining);
17203
17204    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17205     whiteNPS = blackNPS = -1;
17206     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17207        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17208         whiteNPS = first.nps;
17209     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17210        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17211         blackNPS = first.nps;
17212     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17213         whiteNPS = second.nps;
17214     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17215         blackNPS = second.nps;
17216     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17217
17218     StartClockTimer(intendedTickLength);
17219 }
17220
17221 char *
17222 TimeString (long ms)
17223 {
17224     long second, minute, hour, day;
17225     char *sign = "";
17226     static char buf[32];
17227
17228     if (ms > 0 && ms <= 9900) {
17229       /* convert milliseconds to tenths, rounding up */
17230       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17231
17232       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17233       return buf;
17234     }
17235
17236     /* convert milliseconds to seconds, rounding up */
17237     /* use floating point to avoid strangeness of integer division
17238        with negative dividends on many machines */
17239     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17240
17241     if (second < 0) {
17242         sign = "-";
17243         second = -second;
17244     }
17245
17246     day = second / (60 * 60 * 24);
17247     second = second % (60 * 60 * 24);
17248     hour = second / (60 * 60);
17249     second = second % (60 * 60);
17250     minute = second / 60;
17251     second = second % 60;
17252
17253     if (day > 0)
17254       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17255               sign, day, hour, minute, second);
17256     else if (hour > 0)
17257       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17258     else
17259       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17260
17261     return buf;
17262 }
17263
17264
17265 /*
17266  * This is necessary because some C libraries aren't ANSI C compliant yet.
17267  */
17268 char *
17269 StrStr (char *string, char *match)
17270 {
17271     int i, length;
17272
17273     length = strlen(match);
17274
17275     for (i = strlen(string) - length; i >= 0; i--, string++)
17276       if (!strncmp(match, string, length))
17277         return string;
17278
17279     return NULL;
17280 }
17281
17282 char *
17283 StrCaseStr (char *string, char *match)
17284 {
17285     int i, j, length;
17286
17287     length = strlen(match);
17288
17289     for (i = strlen(string) - length; i >= 0; i--, string++) {
17290         for (j = 0; j < length; j++) {
17291             if (ToLower(match[j]) != ToLower(string[j]))
17292               break;
17293         }
17294         if (j == length) return string;
17295     }
17296
17297     return NULL;
17298 }
17299
17300 #ifndef _amigados
17301 int
17302 StrCaseCmp (char *s1, char *s2)
17303 {
17304     char c1, c2;
17305
17306     for (;;) {
17307         c1 = ToLower(*s1++);
17308         c2 = ToLower(*s2++);
17309         if (c1 > c2) return 1;
17310         if (c1 < c2) return -1;
17311         if (c1 == NULLCHAR) return 0;
17312     }
17313 }
17314
17315
17316 int
17317 ToLower (int c)
17318 {
17319     return isupper(c) ? tolower(c) : c;
17320 }
17321
17322
17323 int
17324 ToUpper (int c)
17325 {
17326     return islower(c) ? toupper(c) : c;
17327 }
17328 #endif /* !_amigados    */
17329
17330 char *
17331 StrSave (char *s)
17332 {
17333   char *ret;
17334
17335   if ((ret = (char *) malloc(strlen(s) + 1)))
17336     {
17337       safeStrCpy(ret, s, strlen(s)+1);
17338     }
17339   return ret;
17340 }
17341
17342 char *
17343 StrSavePtr (char *s, char **savePtr)
17344 {
17345     if (*savePtr) {
17346         free(*savePtr);
17347     }
17348     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17349       safeStrCpy(*savePtr, s, strlen(s)+1);
17350     }
17351     return(*savePtr);
17352 }
17353
17354 char *
17355 PGNDate ()
17356 {
17357     time_t clock;
17358     struct tm *tm;
17359     char buf[MSG_SIZ];
17360
17361     clock = time((time_t *)NULL);
17362     tm = localtime(&clock);
17363     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17364             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17365     return StrSave(buf);
17366 }
17367
17368
17369 char *
17370 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17371 {
17372     int i, j, fromX, fromY, toX, toY;
17373     int whiteToPlay;
17374     char buf[MSG_SIZ];
17375     char *p, *q;
17376     int emptycount;
17377     ChessSquare piece;
17378
17379     whiteToPlay = (gameMode == EditPosition) ?
17380       !blackPlaysFirst : (move % 2 == 0);
17381     p = buf;
17382
17383     /* Piece placement data */
17384     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17385         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17386         emptycount = 0;
17387         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17388             if (boards[move][i][j] == EmptySquare) {
17389                 emptycount++;
17390             } else { ChessSquare piece = boards[move][i][j];
17391                 if (emptycount > 0) {
17392                     if(emptycount<10) /* [HGM] can be >= 10 */
17393                         *p++ = '0' + emptycount;
17394                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17395                     emptycount = 0;
17396                 }
17397                 if(PieceToChar(piece) == '+') {
17398                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17399                     *p++ = '+';
17400                     piece = (ChessSquare)(DEMOTED piece);
17401                 }
17402                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17403                 if(p[-1] == '~') {
17404                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17405                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17406                     *p++ = '~';
17407                 }
17408             }
17409         }
17410         if (emptycount > 0) {
17411             if(emptycount<10) /* [HGM] can be >= 10 */
17412                 *p++ = '0' + emptycount;
17413             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17414             emptycount = 0;
17415         }
17416         *p++ = '/';
17417     }
17418     *(p - 1) = ' ';
17419
17420     /* [HGM] print Crazyhouse or Shogi holdings */
17421     if( gameInfo.holdingsWidth ) {
17422         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17423         q = p;
17424         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17425             piece = boards[move][i][BOARD_WIDTH-1];
17426             if( piece != EmptySquare )
17427               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17428                   *p++ = PieceToChar(piece);
17429         }
17430         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17431             piece = boards[move][BOARD_HEIGHT-i-1][0];
17432             if( piece != EmptySquare )
17433               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17434                   *p++ = PieceToChar(piece);
17435         }
17436
17437         if( q == p ) *p++ = '-';
17438         *p++ = ']';
17439         *p++ = ' ';
17440     }
17441
17442     /* Active color */
17443     *p++ = whiteToPlay ? 'w' : 'b';
17444     *p++ = ' ';
17445
17446   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17447     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17448   } else {
17449   if(nrCastlingRights) {
17450      q = p;
17451      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17452        /* [HGM] write directly from rights */
17453            if(boards[move][CASTLING][2] != NoRights &&
17454               boards[move][CASTLING][0] != NoRights   )
17455                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17456            if(boards[move][CASTLING][2] != NoRights &&
17457               boards[move][CASTLING][1] != NoRights   )
17458                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17459            if(boards[move][CASTLING][5] != NoRights &&
17460               boards[move][CASTLING][3] != NoRights   )
17461                 *p++ = boards[move][CASTLING][3] + AAA;
17462            if(boards[move][CASTLING][5] != NoRights &&
17463               boards[move][CASTLING][4] != NoRights   )
17464                 *p++ = boards[move][CASTLING][4] + AAA;
17465      } else {
17466
17467         /* [HGM] write true castling rights */
17468         if( nrCastlingRights == 6 ) {
17469             int q, k=0;
17470             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17471                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17472             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17473                  boards[move][CASTLING][2] != NoRights  );
17474             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17475                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17476                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17477                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17478                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17479             }
17480             if(q) *p++ = 'Q';
17481             k = 0;
17482             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17483                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17484             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17485                  boards[move][CASTLING][5] != NoRights  );
17486             if(gameInfo.variant == VariantSChess) {
17487                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17488                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17489                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17490                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17491             }
17492             if(q) *p++ = 'q';
17493         }
17494      }
17495      if (q == p) *p++ = '-'; /* No castling rights */
17496      *p++ = ' ';
17497   }
17498
17499   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17500      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17501      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17502     /* En passant target square */
17503     if (move > backwardMostMove) {
17504         fromX = moveList[move - 1][0] - AAA;
17505         fromY = moveList[move - 1][1] - ONE;
17506         toX = moveList[move - 1][2] - AAA;
17507         toY = moveList[move - 1][3] - ONE;
17508         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17509             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17510             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17511             fromX == toX) {
17512             /* 2-square pawn move just happened */
17513             *p++ = toX + AAA;
17514             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17515         } else {
17516             *p++ = '-';
17517         }
17518     } else if(move == backwardMostMove) {
17519         // [HGM] perhaps we should always do it like this, and forget the above?
17520         if((signed char)boards[move][EP_STATUS] >= 0) {
17521             *p++ = boards[move][EP_STATUS] + AAA;
17522             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17523         } else {
17524             *p++ = '-';
17525         }
17526     } else {
17527         *p++ = '-';
17528     }
17529     *p++ = ' ';
17530   }
17531   }
17532
17533     if(moveCounts)
17534     {   int i = 0, j=move;
17535
17536         /* [HGM] find reversible plies */
17537         if (appData.debugMode) { int k;
17538             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17539             for(k=backwardMostMove; k<=forwardMostMove; k++)
17540                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17541
17542         }
17543
17544         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17545         if( j == backwardMostMove ) i += initialRulePlies;
17546         sprintf(p, "%d ", i);
17547         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17548
17549         /* Fullmove number */
17550         sprintf(p, "%d", (move / 2) + 1);
17551     } else *--p = NULLCHAR;
17552
17553     return StrSave(buf);
17554 }
17555
17556 Boolean
17557 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17558 {
17559     int i, j, k, w=0;
17560     char *p, c;
17561     int emptycount, virgin[BOARD_FILES];
17562     ChessSquare piece;
17563
17564     p = fen;
17565
17566     /* Piece placement data */
17567     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17568         j = 0;
17569         for (;;) {
17570             if (*p == '/' || *p == ' ' || *p == '[' ) {
17571                 if(j > w) w = j;
17572                 emptycount = gameInfo.boardWidth - j;
17573                 while (emptycount--)
17574                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17575                 if (*p == '/') p++;
17576                 else if(autoSize) { // we stumbled unexpectedly into end of board
17577                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17578                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17579                     }
17580                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17581                 }
17582                 break;
17583 #if(BOARD_FILES >= 10)
17584             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17585                 p++; emptycount=10;
17586                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17587                 while (emptycount--)
17588                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17589 #endif
17590             } else if (*p == '*') {
17591                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17592             } else if (isdigit(*p)) {
17593                 emptycount = *p++ - '0';
17594                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17595                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17596                 while (emptycount--)
17597                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17598             } else if (*p == '+' || isalpha(*p)) {
17599                 if (j >= gameInfo.boardWidth) return FALSE;
17600                 if(*p=='+') {
17601                     piece = CharToPiece(*++p);
17602                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17603                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17604                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17605                 } else piece = CharToPiece(*p++);
17606
17607                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17608                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17609                     piece = (ChessSquare) (PROMOTED piece);
17610                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17611                     p++;
17612                 }
17613                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17614             } else {
17615                 return FALSE;
17616             }
17617         }
17618     }
17619     while (*p == '/' || *p == ' ') p++;
17620
17621     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17622
17623     /* [HGM] by default clear Crazyhouse holdings, if present */
17624     if(gameInfo.holdingsWidth) {
17625        for(i=0; i<BOARD_HEIGHT; i++) {
17626            board[i][0]             = EmptySquare; /* black holdings */
17627            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17628            board[i][1]             = (ChessSquare) 0; /* black counts */
17629            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17630        }
17631     }
17632
17633     /* [HGM] look for Crazyhouse holdings here */
17634     while(*p==' ') p++;
17635     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17636         if(*p == '[') p++;
17637         if(*p == '-' ) p++; /* empty holdings */ else {
17638             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17639             /* if we would allow FEN reading to set board size, we would   */
17640             /* have to add holdings and shift the board read so far here   */
17641             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17642                 p++;
17643                 if((int) piece >= (int) BlackPawn ) {
17644                     i = (int)piece - (int)BlackPawn;
17645                     i = PieceToNumber((ChessSquare)i);
17646                     if( i >= gameInfo.holdingsSize ) return FALSE;
17647                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17648                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17649                 } else {
17650                     i = (int)piece - (int)WhitePawn;
17651                     i = PieceToNumber((ChessSquare)i);
17652                     if( i >= gameInfo.holdingsSize ) return FALSE;
17653                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17654                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17655                 }
17656             }
17657         }
17658         if(*p == ']') p++;
17659     }
17660
17661     while(*p == ' ') p++;
17662
17663     /* Active color */
17664     c = *p++;
17665     if(appData.colorNickNames) {
17666       if( c == appData.colorNickNames[0] ) c = 'w'; else
17667       if( c == appData.colorNickNames[1] ) c = 'b';
17668     }
17669     switch (c) {
17670       case 'w':
17671         *blackPlaysFirst = FALSE;
17672         break;
17673       case 'b':
17674         *blackPlaysFirst = TRUE;
17675         break;
17676       default:
17677         return FALSE;
17678     }
17679
17680     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17681     /* return the extra info in global variiables             */
17682
17683     /* set defaults in case FEN is incomplete */
17684     board[EP_STATUS] = EP_UNKNOWN;
17685     for(i=0; i<nrCastlingRights; i++ ) {
17686         board[CASTLING][i] =
17687             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17688     }   /* assume possible unless obviously impossible */
17689     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17690     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17691     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17692                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17693     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17694     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17695     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17696                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17697     FENrulePlies = 0;
17698
17699     while(*p==' ') p++;
17700     if(nrCastlingRights) {
17701       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17702       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17703           /* castling indicator present, so default becomes no castlings */
17704           for(i=0; i<nrCastlingRights; i++ ) {
17705                  board[CASTLING][i] = NoRights;
17706           }
17707       }
17708       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17709              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17710              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17711              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17712         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17713
17714         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17715             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17716             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17717         }
17718         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17719             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17720         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17721                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17722         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17723                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17724         switch(c) {
17725           case'K':
17726               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17727               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17728               board[CASTLING][2] = whiteKingFile;
17729               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17730               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17731               break;
17732           case'Q':
17733               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17734               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17735               board[CASTLING][2] = whiteKingFile;
17736               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17737               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17738               break;
17739           case'k':
17740               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17741               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17742               board[CASTLING][5] = blackKingFile;
17743               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17744               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17745               break;
17746           case'q':
17747               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17748               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17749               board[CASTLING][5] = blackKingFile;
17750               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17751               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17752           case '-':
17753               break;
17754           default: /* FRC castlings */
17755               if(c >= 'a') { /* black rights */
17756                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17757                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17758                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17759                   if(i == BOARD_RGHT) break;
17760                   board[CASTLING][5] = i;
17761                   c -= AAA;
17762                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17763                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17764                   if(c > i)
17765                       board[CASTLING][3] = c;
17766                   else
17767                       board[CASTLING][4] = c;
17768               } else { /* white rights */
17769                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17770                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17771                     if(board[0][i] == WhiteKing) break;
17772                   if(i == BOARD_RGHT) break;
17773                   board[CASTLING][2] = i;
17774                   c -= AAA - 'a' + 'A';
17775                   if(board[0][c] >= WhiteKing) break;
17776                   if(c > i)
17777                       board[CASTLING][0] = c;
17778                   else
17779                       board[CASTLING][1] = c;
17780               }
17781         }
17782       }
17783       for(i=0; i<nrCastlingRights; i++)
17784         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17785       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17786     if (appData.debugMode) {
17787         fprintf(debugFP, "FEN castling rights:");
17788         for(i=0; i<nrCastlingRights; i++)
17789         fprintf(debugFP, " %d", board[CASTLING][i]);
17790         fprintf(debugFP, "\n");
17791     }
17792
17793       while(*p==' ') p++;
17794     }
17795
17796     /* read e.p. field in games that know e.p. capture */
17797     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17798        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17799        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17800       if(*p=='-') {
17801         p++; board[EP_STATUS] = EP_NONE;
17802       } else {
17803          char c = *p++ - AAA;
17804
17805          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17806          if(*p >= '0' && *p <='9') p++;
17807          board[EP_STATUS] = c;
17808       }
17809     }
17810
17811
17812     if(sscanf(p, "%d", &i) == 1) {
17813         FENrulePlies = i; /* 50-move ply counter */
17814         /* (The move number is still ignored)    */
17815     }
17816
17817     return TRUE;
17818 }
17819
17820 void
17821 EditPositionPasteFEN (char *fen)
17822 {
17823   if (fen != NULL) {
17824     Board initial_position;
17825
17826     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17827       DisplayError(_("Bad FEN position in clipboard"), 0);
17828       return ;
17829     } else {
17830       int savedBlackPlaysFirst = blackPlaysFirst;
17831       EditPositionEvent();
17832       blackPlaysFirst = savedBlackPlaysFirst;
17833       CopyBoard(boards[0], initial_position);
17834       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17835       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17836       DisplayBothClocks();
17837       DrawPosition(FALSE, boards[currentMove]);
17838     }
17839   }
17840 }
17841
17842 static char cseq[12] = "\\   ";
17843
17844 Boolean
17845 set_cont_sequence (char *new_seq)
17846 {
17847     int len;
17848     Boolean ret;
17849
17850     // handle bad attempts to set the sequence
17851         if (!new_seq)
17852                 return 0; // acceptable error - no debug
17853
17854     len = strlen(new_seq);
17855     ret = (len > 0) && (len < sizeof(cseq));
17856     if (ret)
17857       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17858     else if (appData.debugMode)
17859       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17860     return ret;
17861 }
17862
17863 /*
17864     reformat a source message so words don't cross the width boundary.  internal
17865     newlines are not removed.  returns the wrapped size (no null character unless
17866     included in source message).  If dest is NULL, only calculate the size required
17867     for the dest buffer.  lp argument indicats line position upon entry, and it's
17868     passed back upon exit.
17869 */
17870 int
17871 wrap (char *dest, char *src, int count, int width, int *lp)
17872 {
17873     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17874
17875     cseq_len = strlen(cseq);
17876     old_line = line = *lp;
17877     ansi = len = clen = 0;
17878
17879     for (i=0; i < count; i++)
17880     {
17881         if (src[i] == '\033')
17882             ansi = 1;
17883
17884         // if we hit the width, back up
17885         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17886         {
17887             // store i & len in case the word is too long
17888             old_i = i, old_len = len;
17889
17890             // find the end of the last word
17891             while (i && src[i] != ' ' && src[i] != '\n')
17892             {
17893                 i--;
17894                 len--;
17895             }
17896
17897             // word too long?  restore i & len before splitting it
17898             if ((old_i-i+clen) >= width)
17899             {
17900                 i = old_i;
17901                 len = old_len;
17902             }
17903
17904             // extra space?
17905             if (i && src[i-1] == ' ')
17906                 len--;
17907
17908             if (src[i] != ' ' && src[i] != '\n')
17909             {
17910                 i--;
17911                 if (len)
17912                     len--;
17913             }
17914
17915             // now append the newline and continuation sequence
17916             if (dest)
17917                 dest[len] = '\n';
17918             len++;
17919             if (dest)
17920                 strncpy(dest+len, cseq, cseq_len);
17921             len += cseq_len;
17922             line = cseq_len;
17923             clen = cseq_len;
17924             continue;
17925         }
17926
17927         if (dest)
17928             dest[len] = src[i];
17929         len++;
17930         if (!ansi)
17931             line++;
17932         if (src[i] == '\n')
17933             line = 0;
17934         if (src[i] == 'm')
17935             ansi = 0;
17936     }
17937     if (dest && appData.debugMode)
17938     {
17939         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17940             count, width, line, len, *lp);
17941         show_bytes(debugFP, src, count);
17942         fprintf(debugFP, "\ndest: ");
17943         show_bytes(debugFP, dest, len);
17944         fprintf(debugFP, "\n");
17945     }
17946     *lp = dest ? line : old_line;
17947
17948     return len;
17949 }
17950
17951 // [HGM] vari: routines for shelving variations
17952 Boolean modeRestore = FALSE;
17953
17954 void
17955 PushInner (int firstMove, int lastMove)
17956 {
17957         int i, j, nrMoves = lastMove - firstMove;
17958
17959         // push current tail of game on stack
17960         savedResult[storedGames] = gameInfo.result;
17961         savedDetails[storedGames] = gameInfo.resultDetails;
17962         gameInfo.resultDetails = NULL;
17963         savedFirst[storedGames] = firstMove;
17964         savedLast [storedGames] = lastMove;
17965         savedFramePtr[storedGames] = framePtr;
17966         framePtr -= nrMoves; // reserve space for the boards
17967         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17968             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17969             for(j=0; j<MOVE_LEN; j++)
17970                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17971             for(j=0; j<2*MOVE_LEN; j++)
17972                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17973             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17974             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17975             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17976             pvInfoList[firstMove+i-1].depth = 0;
17977             commentList[framePtr+i] = commentList[firstMove+i];
17978             commentList[firstMove+i] = NULL;
17979         }
17980
17981         storedGames++;
17982         forwardMostMove = firstMove; // truncate game so we can start variation
17983 }
17984
17985 void
17986 PushTail (int firstMove, int lastMove)
17987 {
17988         if(appData.icsActive) { // only in local mode
17989                 forwardMostMove = currentMove; // mimic old ICS behavior
17990                 return;
17991         }
17992         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17993
17994         PushInner(firstMove, lastMove);
17995         if(storedGames == 1) GreyRevert(FALSE);
17996         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17997 }
17998
17999 void
18000 PopInner (Boolean annotate)
18001 {
18002         int i, j, nrMoves;
18003         char buf[8000], moveBuf[20];
18004
18005         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18006         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18007         nrMoves = savedLast[storedGames] - currentMove;
18008         if(annotate) {
18009                 int cnt = 10;
18010                 if(!WhiteOnMove(currentMove))
18011                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18012                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18013                 for(i=currentMove; i<forwardMostMove; i++) {
18014                         if(WhiteOnMove(i))
18015                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18016                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18017                         strcat(buf, moveBuf);
18018                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18019                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18020                 }
18021                 strcat(buf, ")");
18022         }
18023         for(i=1; i<=nrMoves; i++) { // copy last variation back
18024             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18025             for(j=0; j<MOVE_LEN; j++)
18026                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18027             for(j=0; j<2*MOVE_LEN; j++)
18028                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18029             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18030             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18031             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18032             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18033             commentList[currentMove+i] = commentList[framePtr+i];
18034             commentList[framePtr+i] = NULL;
18035         }
18036         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18037         framePtr = savedFramePtr[storedGames];
18038         gameInfo.result = savedResult[storedGames];
18039         if(gameInfo.resultDetails != NULL) {
18040             free(gameInfo.resultDetails);
18041       }
18042         gameInfo.resultDetails = savedDetails[storedGames];
18043         forwardMostMove = currentMove + nrMoves;
18044 }
18045
18046 Boolean
18047 PopTail (Boolean annotate)
18048 {
18049         if(appData.icsActive) return FALSE; // only in local mode
18050         if(!storedGames) return FALSE; // sanity
18051         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18052
18053         PopInner(annotate);
18054         if(currentMove < forwardMostMove) ForwardEvent(); else
18055         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18056
18057         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18058         return TRUE;
18059 }
18060
18061 void
18062 CleanupTail ()
18063 {       // remove all shelved variations
18064         int i;
18065         for(i=0; i<storedGames; i++) {
18066             if(savedDetails[i])
18067                 free(savedDetails[i]);
18068             savedDetails[i] = NULL;
18069         }
18070         for(i=framePtr; i<MAX_MOVES; i++) {
18071                 if(commentList[i]) free(commentList[i]);
18072                 commentList[i] = NULL;
18073         }
18074         framePtr = MAX_MOVES-1;
18075         storedGames = 0;
18076 }
18077
18078 void
18079 LoadVariation (int index, char *text)
18080 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18081         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18082         int level = 0, move;
18083
18084         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18085         // first find outermost bracketing variation
18086         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18087             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18088                 if(*p == '{') wait = '}'; else
18089                 if(*p == '[') wait = ']'; else
18090                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18091                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18092             }
18093             if(*p == wait) wait = NULLCHAR; // closing ]} found
18094             p++;
18095         }
18096         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18097         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18098         end[1] = NULLCHAR; // clip off comment beyond variation
18099         ToNrEvent(currentMove-1);
18100         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18101         // kludge: use ParsePV() to append variation to game
18102         move = currentMove;
18103         ParsePV(start, TRUE, TRUE);
18104         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18105         ClearPremoveHighlights();
18106         CommentPopDown();
18107         ToNrEvent(currentMove+1);
18108 }
18109
18110 void
18111 LoadTheme ()
18112 {
18113     char *p, *q, buf[MSG_SIZ];
18114     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18115         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18116         ParseArgsFromString(buf);
18117         ActivateTheme(TRUE); // also redo colors
18118         return;
18119     }
18120     p = nickName;
18121     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18122     {
18123         int len;
18124         q = appData.themeNames;
18125         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18126       if(appData.useBitmaps) {
18127         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18128                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18129                 appData.liteBackTextureMode,
18130                 appData.darkBackTextureMode );
18131       } else {
18132         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18133                 Col2Text(2),   // lightSquareColor
18134                 Col2Text(3) ); // darkSquareColor
18135       }
18136       if(appData.useBorder) {
18137         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18138                 appData.border);
18139       } else {
18140         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18141       }
18142       if(appData.useFont) {
18143         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18144                 appData.renderPiecesWithFont,
18145                 appData.fontToPieceTable,
18146                 Col2Text(9),    // appData.fontBackColorWhite
18147                 Col2Text(10) ); // appData.fontForeColorBlack
18148       } else {
18149         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18150                 appData.pieceDirectory);
18151         if(!appData.pieceDirectory[0])
18152           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18153                 Col2Text(0),   // whitePieceColor
18154                 Col2Text(1) ); // blackPieceColor
18155       }
18156       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18157                 Col2Text(4),   // highlightSquareColor
18158                 Col2Text(5) ); // premoveHighlightColor
18159         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18160         if(insert != q) insert[-1] = NULLCHAR;
18161         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18162         if(q)   free(q);
18163     }
18164     ActivateTheme(FALSE);
18165 }