48a32a457c062a959d4c064875d0cce9469dbc5a
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569 ChessSquare  lionArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackLion, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackKnight, BlackRook }
574 };
575
576
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
583 };
584
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
590 };
591
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
597 };
598
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
604 };
605
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
611 };
612
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
618 };
619
620 #ifdef GOTHIC
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
626 };
627 #else // !GOTHIC
628 #define GothicArray CapablancaArray
629 #endif // !GOTHIC
630
631 #ifdef FALCON
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
637 };
638 #else // !FALCON
639 #define FalconArray CapablancaArray
640 #endif // !FALCON
641
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
648
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
655 };
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
669 };
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
674
675
676 Board initialPosition;
677
678
679 /* Convert str to a rating. Checks for special cases of "----",
680
681    "++++", etc. Also strips ()'s */
682 int
683 string_to_rating (char *str)
684 {
685   while(*str && !isdigit(*str)) ++str;
686   if (!*str)
687     return 0;   /* One of the special "no rating" cases */
688   else
689     return atoi(str);
690 }
691
692 void
693 ClearProgramStats ()
694 {
695     /* Init programStats */
696     programStats.movelist[0] = 0;
697     programStats.depth = 0;
698     programStats.nr_moves = 0;
699     programStats.moves_left = 0;
700     programStats.nodes = 0;
701     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
702     programStats.score = 0;
703     programStats.got_only_move = 0;
704     programStats.got_fail = 0;
705     programStats.line_is_book = 0;
706 }
707
708 void
709 CommonEngineInit ()
710 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711     if (appData.firstPlaysBlack) {
712         first.twoMachinesColor = "black\n";
713         second.twoMachinesColor = "white\n";
714     } else {
715         first.twoMachinesColor = "white\n";
716         second.twoMachinesColor = "black\n";
717     }
718
719     first.other = &second;
720     second.other = &first;
721
722     { float norm = 1;
723         if(appData.timeOddsMode) {
724             norm = appData.timeOdds[0];
725             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
726         }
727         first.timeOdds  = appData.timeOdds[0]/norm;
728         second.timeOdds = appData.timeOdds[1]/norm;
729     }
730
731     if(programVersion) free(programVersion);
732     if (appData.noChessProgram) {
733         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734         sprintf(programVersion, "%s", PACKAGE_STRING);
735     } else {
736       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
739     }
740 }
741
742 void
743 UnloadEngine (ChessProgramState *cps)
744 {
745         /* Kill off first chess program */
746         if (cps->isr != NULL)
747           RemoveInputSource(cps->isr);
748         cps->isr = NULL;
749
750         if (cps->pr != NoProc) {
751             ExitAnalyzeMode();
752             DoSleep( appData.delayBeforeQuit );
753             SendToProgram("quit\n", cps);
754             DoSleep( appData.delayAfterQuit );
755             DestroyChildProcess(cps->pr, cps->useSigterm);
756         }
757         cps->pr = NoProc;
758         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
759 }
760
761 void
762 ClearOptions (ChessProgramState *cps)
763 {
764     int i;
765     cps->nrOptions = cps->comboCnt = 0;
766     for(i=0; i<MAX_OPTIONS; i++) {
767         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768         cps->option[i].textValue = 0;
769     }
770 }
771
772 char *engineNames[] = {
773   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 N_("first"),
776   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
778 N_("second")
779 };
780
781 void
782 InitEngine (ChessProgramState *cps, int n)
783 {   // [HGM] all engine initialiation put in a function that does one engine
784
785     ClearOptions(cps);
786
787     cps->which = engineNames[n];
788     cps->maybeThinking = FALSE;
789     cps->pr = NoProc;
790     cps->isr = NULL;
791     cps->sendTime = 2;
792     cps->sendDrawOffers = 1;
793
794     cps->program = appData.chessProgram[n];
795     cps->host = appData.host[n];
796     cps->dir = appData.directory[n];
797     cps->initString = appData.engInitString[n];
798     cps->computerString = appData.computerString[n];
799     cps->useSigint  = TRUE;
800     cps->useSigterm = TRUE;
801     cps->reuse = appData.reuse[n];
802     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
803     cps->useSetboard = FALSE;
804     cps->useSAN = FALSE;
805     cps->usePing = FALSE;
806     cps->lastPing = 0;
807     cps->lastPong = 0;
808     cps->usePlayother = FALSE;
809     cps->useColors = TRUE;
810     cps->useUsermove = FALSE;
811     cps->sendICS = FALSE;
812     cps->sendName = appData.icsActive;
813     cps->sdKludge = FALSE;
814     cps->stKludge = FALSE;
815     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816     TidyProgramName(cps->program, cps->host, cps->tidy);
817     cps->matchWins = 0;
818     ASSIGN(cps->variants, appData.variant);
819     cps->analysisSupport = 2; /* detect */
820     cps->analyzing = FALSE;
821     cps->initDone = FALSE;
822     cps->reload = FALSE;
823
824     /* New features added by Tord: */
825     cps->useFEN960 = FALSE;
826     cps->useOOCastle = TRUE;
827     /* End of new features added by Tord. */
828     cps->fenOverride  = appData.fenOverride[n];
829
830     /* [HGM] time odds: set factor for each machine */
831     cps->timeOdds  = appData.timeOdds[n];
832
833     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834     cps->accumulateTC = appData.accumulateTC[n];
835     cps->maxNrOfSessions = 1;
836
837     /* [HGM] debug */
838     cps->debug = FALSE;
839
840     cps->supportsNPS = UNKNOWN;
841     cps->memSize = FALSE;
842     cps->maxCores = FALSE;
843     ASSIGN(cps->egtFormats, "");
844
845     /* [HGM] options */
846     cps->optionSettings  = appData.engOptions[n];
847
848     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849     cps->isUCI = appData.isUCI[n]; /* [AS] */
850     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
851     cps->highlight = 0;
852
853     if (appData.protocolVersion[n] > PROTOVER
854         || appData.protocolVersion[n] < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.protocolVersion[n]);
861         if( (len >= MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         cps->protocolVersion = appData.protocolVersion[n];
869       }
870
871     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
872     ParseFeatures(appData.featureDefaults, cps);
873 }
874
875 ChessProgramState *savCps;
876
877 GameMode oldMode;
878
879 void
880 LoadEngine ()
881 {
882     int i;
883     if(WaitForEngine(savCps, LoadEngine)) return;
884     CommonEngineInit(); // recalculate time odds
885     if(gameInfo.variant != StringToVariant(appData.variant)) {
886         // we changed variant when loading the engine; this forces us to reset
887         Reset(TRUE, savCps != &first);
888         oldMode = BeginningOfGame; // to prevent restoring old mode
889     }
890     InitChessProgram(savCps, FALSE);
891     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892     DisplayMessage("", "");
893     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
895     ThawUI();
896     SetGNUMode();
897     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
898 }
899
900 void
901 ReplaceEngine (ChessProgramState *cps, int n)
902 {
903     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
904     keepInfo = 1;
905     if(oldMode != BeginningOfGame) EditGameEvent();
906     keepInfo = 0;
907     UnloadEngine(cps);
908     appData.noChessProgram = FALSE;
909     appData.clockMode = TRUE;
910     InitEngine(cps, n);
911     UpdateLogos(TRUE);
912     if(n) return; // only startup first engine immediately; second can wait
913     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
914     LoadEngine();
915 }
916
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
919
920 static char resetOptions[] =
921         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
925
926 void
927 FloatToFront(char **list, char *engineLine)
928 {
929     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
930     int i=0;
931     if(appData.recentEngines <= 0) return;
932     TidyProgramName(engineLine, "localhost", tidy+1);
933     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934     strncpy(buf+1, *list, MSG_SIZ-50);
935     if(p = strstr(buf, tidy)) { // tidy name appears in list
936         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937         while(*p++ = *++q); // squeeze out
938     }
939     strcat(tidy, buf+1); // put list behind tidy name
940     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942     ASSIGN(*list, tidy+1);
943 }
944
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
946
947 void
948 Load (ChessProgramState *cps, int i)
949 {
950     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956         appData.firstProtocolVersion = PROTOVER;
957         ParseArgsFromString(buf);
958         SwapEngines(i);
959         ReplaceEngine(cps, i);
960         FloatToFront(&appData.recentEngineList, engineLine);
961         return;
962     }
963     p = engineName;
964     while(q = strchr(p, SLASH)) p = q+1;
965     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966     if(engineDir[0] != NULLCHAR) {
967         ASSIGN(appData.directory[i], engineDir); p = engineName;
968     } else if(p != engineName) { // derive directory from engine path, when not given
969         p[-1] = 0;
970         ASSIGN(appData.directory[i], engineName);
971         p[-1] = SLASH;
972         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973     } else { ASSIGN(appData.directory[i], "."); }
974     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
975     if(params[0]) {
976         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977         snprintf(command, MSG_SIZ, "%s %s", p, params);
978         p = command;
979     }
980     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981     ASSIGN(appData.chessProgram[i], p);
982     appData.isUCI[i] = isUCI;
983     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984     appData.hasOwnBookUCI[i] = hasBook;
985     if(!nickName[0]) useNick = FALSE;
986     if(useNick) ASSIGN(appData.pgnName[i], nickName);
987     if(addToList) {
988         int len;
989         char quote;
990         q = firstChessProgramNames;
991         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994                         quote, p, quote, appData.directory[i],
995                         useNick ? " -fn \"" : "",
996                         useNick ? nickName : "",
997                         useNick ? "\"" : "",
998                         v1 ? " -firstProtocolVersion 1" : "",
999                         hasBook ? "" : " -fNoOwnBookUCI",
1000                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001                         storeVariant ? " -variant " : "",
1002                         storeVariant ? VariantName(gameInfo.variant) : "");
1003         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005         if(insert != q) insert[-1] = NULLCHAR;
1006         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1007         if(q)   free(q);
1008         FloatToFront(&appData.recentEngineList, buf);
1009     }
1010     ReplaceEngine(cps, i);
1011 }
1012
1013 void
1014 InitTimeControls ()
1015 {
1016     int matched, min, sec;
1017     /*
1018      * Parse timeControl resource
1019      */
1020     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021                           appData.movesPerSession)) {
1022         char buf[MSG_SIZ];
1023         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024         DisplayFatalError(buf, 0, 2);
1025     }
1026
1027     /*
1028      * Parse searchTime resource
1029      */
1030     if (*appData.searchTime != NULLCHAR) {
1031         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1032         if (matched == 1) {
1033             searchTime = min * 60;
1034         } else if (matched == 2) {
1035             searchTime = min * 60 + sec;
1036         } else {
1037             char buf[MSG_SIZ];
1038             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039             DisplayFatalError(buf, 0, 2);
1040         }
1041     }
1042 }
1043
1044 void
1045 InitBackEnd1 ()
1046 {
1047
1048     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1050
1051     GetTimeMark(&programStartTime);
1052     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053     appData.seedBase = random() + (random()<<15);
1054     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1055
1056     ClearProgramStats();
1057     programStats.ok_to_send = 1;
1058     programStats.seen_stat = 0;
1059
1060     /*
1061      * Initialize game list
1062      */
1063     ListNew(&gameList);
1064
1065
1066     /*
1067      * Internet chess server status
1068      */
1069     if (appData.icsActive) {
1070         appData.matchMode = FALSE;
1071         appData.matchGames = 0;
1072 #if ZIPPY
1073         appData.noChessProgram = !appData.zippyPlay;
1074 #else
1075         appData.zippyPlay = FALSE;
1076         appData.zippyTalk = FALSE;
1077         appData.noChessProgram = TRUE;
1078 #endif
1079         if (*appData.icsHelper != NULLCHAR) {
1080             appData.useTelnet = TRUE;
1081             appData.telnetProgram = appData.icsHelper;
1082         }
1083     } else {
1084         appData.zippyTalk = appData.zippyPlay = FALSE;
1085     }
1086
1087     /* [AS] Initialize pv info list [HGM] and game state */
1088     {
1089         int i, j;
1090
1091         for( i=0; i<=framePtr; i++ ) {
1092             pvInfoList[i].depth = -1;
1093             boards[i][EP_STATUS] = EP_NONE;
1094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1095         }
1096     }
1097
1098     InitTimeControls();
1099
1100     /* [AS] Adjudication threshold */
1101     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1102
1103     InitEngine(&first, 0);
1104     InitEngine(&second, 1);
1105     CommonEngineInit();
1106
1107     pairing.which = "pairing"; // pairing engine
1108     pairing.pr = NoProc;
1109     pairing.isr = NULL;
1110     pairing.program = appData.pairingEngine;
1111     pairing.host = "localhost";
1112     pairing.dir = ".";
1113
1114     if (appData.icsActive) {
1115         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1116     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117         appData.clockMode = FALSE;
1118         first.sendTime = second.sendTime = 0;
1119     }
1120
1121 #if ZIPPY
1122     /* Override some settings from environment variables, for backward
1123        compatibility.  Unfortunately it's not feasible to have the env
1124        vars just set defaults, at least in xboard.  Ugh.
1125     */
1126     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1127       ZippyInit();
1128     }
1129 #endif
1130
1131     if (!appData.icsActive) {
1132       char buf[MSG_SIZ];
1133       int len;
1134
1135       /* Check for variants that are supported only in ICS mode,
1136          or not at all.  Some that are accepted here nevertheless
1137          have bugs; see comments below.
1138       */
1139       VariantClass variant = StringToVariant(appData.variant);
1140       switch (variant) {
1141       case VariantBughouse:     /* need four players and two boards */
1142       case VariantKriegspiel:   /* need to hide pieces and move details */
1143         /* case VariantFischeRandom: (Fabien: moved below) */
1144         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145         if( (len >= MSG_SIZ) && appData.debugMode )
1146           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1147
1148         DisplayFatalError(buf, 0, 2);
1149         return;
1150
1151       case VariantUnknown:
1152       case VariantLoadable:
1153       case Variant29:
1154       case Variant30:
1155       case Variant31:
1156       case Variant32:
1157       case Variant33:
1158       case Variant34:
1159       case Variant35:
1160       case Variant36:
1161       default:
1162         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163         if( (len >= MSG_SIZ) && appData.debugMode )
1164           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1165
1166         DisplayFatalError(buf, 0, 2);
1167         return;
1168
1169       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1170       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1171       case VariantGothic:     /* [HGM] should work */
1172       case VariantCapablanca: /* [HGM] should work */
1173       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1174       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1175       case VariantChu:        /* [HGM] experimental */
1176       case VariantKnightmate: /* [HGM] should work */
1177       case VariantCylinder:   /* [HGM] untested */
1178       case VariantFalcon:     /* [HGM] untested */
1179       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180                                  offboard interposition not understood */
1181       case VariantNormal:     /* definitely works! */
1182       case VariantWildCastle: /* pieces not automatically shuffled */
1183       case VariantNoCastle:   /* pieces not automatically shuffled */
1184       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185       case VariantLosers:     /* should work except for win condition,
1186                                  and doesn't know captures are mandatory */
1187       case VariantSuicide:    /* should work except for win condition,
1188                                  and doesn't know captures are mandatory */
1189       case VariantGiveaway:   /* should work except for win condition,
1190                                  and doesn't know captures are mandatory */
1191       case VariantTwoKings:   /* should work */
1192       case VariantAtomic:     /* should work except for win condition */
1193       case Variant3Check:     /* should work except for win condition */
1194       case VariantShatranj:   /* should work except for all win conditions */
1195       case VariantMakruk:     /* should work except for draw countdown */
1196       case VariantASEAN :     /* should work except for draw countdown */
1197       case VariantBerolina:   /* might work if TestLegality is off */
1198       case VariantCapaRandom: /* should work */
1199       case VariantJanus:      /* should work */
1200       case VariantSuper:      /* experimental */
1201       case VariantGreat:      /* experimental, requires legality testing to be off */
1202       case VariantSChess:     /* S-Chess, should work */
1203       case VariantGrand:      /* should work */
1204       case VariantSpartan:    /* should work */
1205       case VariantLion:       /* should work */
1206         break;
1207       }
1208     }
1209
1210 }
1211
1212 int
1213 NextIntegerFromString (char ** str, long * value)
1214 {
1215     int result = -1;
1216     char * s = *str;
1217
1218     while( *s == ' ' || *s == '\t' ) {
1219         s++;
1220     }
1221
1222     *value = 0;
1223
1224     if( *s >= '0' && *s <= '9' ) {
1225         while( *s >= '0' && *s <= '9' ) {
1226             *value = *value * 10 + (*s - '0');
1227             s++;
1228         }
1229
1230         result = 0;
1231     }
1232
1233     *str = s;
1234
1235     return result;
1236 }
1237
1238 int
1239 NextTimeControlFromString (char ** str, long * value)
1240 {
1241     long temp;
1242     int result = NextIntegerFromString( str, &temp );
1243
1244     if( result == 0 ) {
1245         *value = temp * 60; /* Minutes */
1246         if( **str == ':' ) {
1247             (*str)++;
1248             result = NextIntegerFromString( str, &temp );
1249             *value += temp; /* Seconds */
1250         }
1251     }
1252
1253     return result;
1254 }
1255
1256 int
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259     int result = -1, type = 0; long temp, temp2;
1260
1261     if(**str != ':') return -1; // old params remain in force!
1262     (*str)++;
1263     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264     if( NextIntegerFromString( str, &temp ) ) return -1;
1265     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1266
1267     if(**str != '/') {
1268         /* time only: incremental or sudden-death time control */
1269         if(**str == '+') { /* increment follows; read it */
1270             (*str)++;
1271             if(**str == '!') type = *(*str)++; // Bronstein TC
1272             if(result = NextIntegerFromString( str, &temp2)) return -1;
1273             *inc = temp2 * 1000;
1274             if(**str == '.') { // read fraction of increment
1275                 char *start = ++(*str);
1276                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1277                 temp2 *= 1000;
1278                 while(start++ < *str) temp2 /= 10;
1279                 *inc += temp2;
1280             }
1281         } else *inc = 0;
1282         *moves = 0; *tc = temp * 1000; *incType = type;
1283         return 0;
1284     }
1285
1286     (*str)++; /* classical time control */
1287     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1288
1289     if(result == 0) {
1290         *moves = temp;
1291         *tc    = temp2 * 1000;
1292         *inc   = 0;
1293         *incType = type;
1294     }
1295     return result;
1296 }
1297
1298 int
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 {   /* [HGM] get time to add from the multi-session time-control string */
1301     int incType, moves=1; /* kludge to force reading of first session */
1302     long time, increment;
1303     char *s = tcString;
1304
1305     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1306     do {
1307         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309         if(movenr == -1) return time;    /* last move before new session     */
1310         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312         if(!moves) return increment;     /* current session is incremental   */
1313         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314     } while(movenr >= -1);               /* try again for next session       */
1315
1316     return 0; // no new time quota on this move
1317 }
1318
1319 int
1320 ParseTimeControl (char *tc, float ti, int mps)
1321 {
1322   long tc1;
1323   long tc2;
1324   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1325   int min, sec=0;
1326
1327   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1330   if(ti > 0) {
1331
1332     if(mps)
1333       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1334     else
1335       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1336   } else {
1337     if(mps)
1338       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1339     else
1340       snprintf(buf, MSG_SIZ, ":%s", mytc);
1341   }
1342   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1343
1344   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1345     return FALSE;
1346   }
1347
1348   if( *tc == '/' ) {
1349     /* Parse second time control */
1350     tc++;
1351
1352     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1353       return FALSE;
1354     }
1355
1356     if( tc2 == 0 ) {
1357       return FALSE;
1358     }
1359
1360     timeControl_2 = tc2 * 1000;
1361   }
1362   else {
1363     timeControl_2 = 0;
1364   }
1365
1366   if( tc1 == 0 ) {
1367     return FALSE;
1368   }
1369
1370   timeControl = tc1 * 1000;
1371
1372   if (ti >= 0) {
1373     timeIncrement = ti * 1000;  /* convert to ms */
1374     movesPerSession = 0;
1375   } else {
1376     timeIncrement = 0;
1377     movesPerSession = mps;
1378   }
1379   return TRUE;
1380 }
1381
1382 void
1383 InitBackEnd2 ()
1384 {
1385     if (appData.debugMode) {
1386 #    ifdef __GIT_VERSION
1387       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1388 #    else
1389       fprintf(debugFP, "Version: %s\n", programVersion);
1390 #    endif
1391     }
1392     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1393
1394     set_cont_sequence(appData.wrapContSeq);
1395     if (appData.matchGames > 0) {
1396         appData.matchMode = TRUE;
1397     } else if (appData.matchMode) {
1398         appData.matchGames = 1;
1399     }
1400     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401         appData.matchGames = appData.sameColorGames;
1402     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1405     }
1406     Reset(TRUE, FALSE);
1407     if (appData.noChessProgram || first.protocolVersion == 1) {
1408       InitBackEnd3();
1409     } else {
1410       /* kludge: allow timeout for initial "feature" commands */
1411       FreezeUI();
1412       DisplayMessage("", _("Starting chess program"));
1413       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1414     }
1415 }
1416
1417 int
1418 CalculateIndex (int index, int gameNr)
1419 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1420     int res;
1421     if(index > 0) return index; // fixed nmber
1422     if(index == 0) return 1;
1423     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1425     return res;
1426 }
1427
1428 int
1429 LoadGameOrPosition (int gameNr)
1430 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431     if (*appData.loadGameFile != NULLCHAR) {
1432         if (!LoadGameFromFile(appData.loadGameFile,
1433                 CalculateIndex(appData.loadGameIndex, gameNr),
1434                               appData.loadGameFile, FALSE)) {
1435             DisplayFatalError(_("Bad game file"), 0, 1);
1436             return 0;
1437         }
1438     } else if (*appData.loadPositionFile != NULLCHAR) {
1439         if (!LoadPositionFromFile(appData.loadPositionFile,
1440                 CalculateIndex(appData.loadPositionIndex, gameNr),
1441                                   appData.loadPositionFile)) {
1442             DisplayFatalError(_("Bad position file"), 0, 1);
1443             return 0;
1444         }
1445     }
1446     return 1;
1447 }
1448
1449 void
1450 ReserveGame (int gameNr, char resChar)
1451 {
1452     FILE *tf = fopen(appData.tourneyFile, "r+");
1453     char *p, *q, c, buf[MSG_SIZ];
1454     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455     safeStrCpy(buf, lastMsg, MSG_SIZ);
1456     DisplayMessage(_("Pick new game"), "");
1457     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458     ParseArgsFromFile(tf);
1459     p = q = appData.results;
1460     if(appData.debugMode) {
1461       char *r = appData.participants;
1462       fprintf(debugFP, "results = '%s'\n", p);
1463       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464       fprintf(debugFP, "\n");
1465     }
1466     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1467     nextGame = q - p;
1468     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469     safeStrCpy(q, p, strlen(p) + 2);
1470     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1474         q[nextGame] = '*';
1475     }
1476     fseek(tf, -(strlen(p)+4), SEEK_END);
1477     c = fgetc(tf);
1478     if(c != '"') // depending on DOS or Unix line endings we can be one off
1479          fseek(tf, -(strlen(p)+2), SEEK_END);
1480     else fseek(tf, -(strlen(p)+3), SEEK_END);
1481     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482     DisplayMessage(buf, "");
1483     free(p); appData.results = q;
1484     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486       int round = appData.defaultMatchGames * appData.tourneyType;
1487       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1488          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489         UnloadEngine(&first);  // next game belongs to other pairing;
1490         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1491     }
1492     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1493 }
1494
1495 void
1496 MatchEvent (int mode)
1497 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1498         int dummy;
1499         if(matchMode) { // already in match mode: switch it off
1500             abortMatch = TRUE;
1501             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1502             return;
1503         }
1504 //      if(gameMode != BeginningOfGame) {
1505 //          DisplayError(_("You can only start a match from the initial position."), 0);
1506 //          return;
1507 //      }
1508         abortMatch = FALSE;
1509         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510         /* Set up machine vs. machine match */
1511         nextGame = 0;
1512         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513         if(appData.tourneyFile[0]) {
1514             ReserveGame(-1, 0);
1515             if(nextGame > appData.matchGames) {
1516                 char buf[MSG_SIZ];
1517                 if(strchr(appData.results, '*') == NULL) {
1518                     FILE *f;
1519                     appData.tourneyCycles++;
1520                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1521                         fclose(f);
1522                         NextTourneyGame(-1, &dummy);
1523                         ReserveGame(-1, 0);
1524                         if(nextGame <= appData.matchGames) {
1525                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1526                             matchMode = mode;
1527                             ScheduleDelayedEvent(NextMatchGame, 10000);
1528                             return;
1529                         }
1530                     }
1531                 }
1532                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533                 DisplayError(buf, 0);
1534                 appData.tourneyFile[0] = 0;
1535                 return;
1536             }
1537         } else
1538         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1539             DisplayFatalError(_("Can't have a match with no chess programs"),
1540                               0, 2);
1541             return;
1542         }
1543         matchMode = mode;
1544         matchGame = roundNr = 1;
1545         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1546         NextMatchGame();
1547 }
1548
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1550
1551 void
1552 InitBackEnd3 P((void))
1553 {
1554     GameMode initialMode;
1555     char buf[MSG_SIZ];
1556     int err, len;
1557
1558     InitChessProgram(&first, startedFromSetupPosition);
1559
1560     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1561         free(programVersion);
1562         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1565     }
1566
1567     if (appData.icsActive) {
1568 #ifdef WIN32
1569         /* [DM] Make a console window if needed [HGM] merged ifs */
1570         ConsoleCreate();
1571 #endif
1572         err = establish();
1573         if (err != 0)
1574           {
1575             if (*appData.icsCommPort != NULLCHAR)
1576               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577                              appData.icsCommPort);
1578             else
1579               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580                         appData.icsHost, appData.icsPort);
1581
1582             if( (len >= MSG_SIZ) && appData.debugMode )
1583               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585             DisplayFatalError(buf, err, 1);
1586             return;
1587         }
1588         SetICSMode();
1589         telnetISR =
1590           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1591         fromUserISR =
1592           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595     } else if (appData.noChessProgram) {
1596         SetNCPMode();
1597     } else {
1598         SetGNUMode();
1599     }
1600
1601     if (*appData.cmailGameName != NULLCHAR) {
1602         SetCmailMode();
1603         OpenLoopback(&cmailPR);
1604         cmailISR =
1605           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1606     }
1607
1608     ThawUI();
1609     DisplayMessage("", "");
1610     if (StrCaseCmp(appData.initialMode, "") == 0) {
1611       initialMode = BeginningOfGame;
1612       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1616         ModeHighlight();
1617       }
1618     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619       initialMode = TwoMachinesPlay;
1620     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621       initialMode = AnalyzeFile;
1622     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623       initialMode = AnalyzeMode;
1624     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625       initialMode = MachinePlaysWhite;
1626     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627       initialMode = MachinePlaysBlack;
1628     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629       initialMode = EditGame;
1630     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631       initialMode = EditPosition;
1632     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633       initialMode = Training;
1634     } else {
1635       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636       if( (len >= MSG_SIZ) && appData.debugMode )
1637         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638
1639       DisplayFatalError(buf, 0, 2);
1640       return;
1641     }
1642
1643     if (appData.matchMode) {
1644         if(appData.tourneyFile[0]) { // start tourney from command line
1645             FILE *f;
1646             if(f = fopen(appData.tourneyFile, "r")) {
1647                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1648                 fclose(f);
1649                 appData.clockMode = TRUE;
1650                 SetGNUMode();
1651             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1652         }
1653         MatchEvent(TRUE);
1654     } else if (*appData.cmailGameName != NULLCHAR) {
1655         /* Set up cmail mode */
1656         ReloadCmailMsgEvent(TRUE);
1657     } else {
1658         /* Set up other modes */
1659         if (initialMode == AnalyzeFile) {
1660           if (*appData.loadGameFile == NULLCHAR) {
1661             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1662             return;
1663           }
1664         }
1665         if (*appData.loadGameFile != NULLCHAR) {
1666             (void) LoadGameFromFile(appData.loadGameFile,
1667                                     appData.loadGameIndex,
1668                                     appData.loadGameFile, TRUE);
1669         } else if (*appData.loadPositionFile != NULLCHAR) {
1670             (void) LoadPositionFromFile(appData.loadPositionFile,
1671                                         appData.loadPositionIndex,
1672                                         appData.loadPositionFile);
1673             /* [HGM] try to make self-starting even after FEN load */
1674             /* to allow automatic setup of fairy variants with wtm */
1675             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676                 gameMode = BeginningOfGame;
1677                 setboardSpoiledMachineBlack = 1;
1678             }
1679             /* [HGM] loadPos: make that every new game uses the setup */
1680             /* from file as long as we do not switch variant          */
1681             if(!blackPlaysFirst) {
1682                 startedFromPositionFile = TRUE;
1683                 CopyBoard(filePosition, boards[0]);
1684             }
1685         }
1686         if (initialMode == AnalyzeMode) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1693             return;
1694           }
1695           AnalyzeModeEvent();
1696         } else if (initialMode == AnalyzeFile) {
1697           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698           ShowThinkingEvent();
1699           AnalyzeFileEvent();
1700           AnalysisPeriodicEvent(1);
1701         } else if (initialMode == MachinePlaysWhite) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           MachineWhiteEvent();
1713         } else if (initialMode == MachinePlaysBlack) {
1714           if (appData.noChessProgram) {
1715             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1716                               0, 2);
1717             return;
1718           }
1719           if (appData.icsActive) {
1720             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1721                               0, 2);
1722             return;
1723           }
1724           MachineBlackEvent();
1725         } else if (initialMode == TwoMachinesPlay) {
1726           if (appData.noChessProgram) {
1727             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1728                               0, 2);
1729             return;
1730           }
1731           if (appData.icsActive) {
1732             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1733                               0, 2);
1734             return;
1735           }
1736           TwoMachinesEvent();
1737         } else if (initialMode == EditGame) {
1738           EditGameEvent();
1739         } else if (initialMode == EditPosition) {
1740           EditPositionEvent();
1741         } else if (initialMode == Training) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744             return;
1745           }
1746           TrainingEvent();
1747         }
1748     }
1749 }
1750
1751 void
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1753 {
1754     DisplayBook(current+1);
1755
1756     MoveHistorySet( movelist, first, last, current, pvInfoList );
1757
1758     EvalGraphSet( first, last, current, pvInfoList );
1759
1760     MakeEngineOutputTitle();
1761 }
1762
1763 /*
1764  * Establish will establish a contact to a remote host.port.
1765  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766  *  used to talk to the host.
1767  * Returns 0 if okay, error code if not.
1768  */
1769 int
1770 establish ()
1771 {
1772     char buf[MSG_SIZ];
1773
1774     if (*appData.icsCommPort != NULLCHAR) {
1775         /* Talk to the host through a serial comm port */
1776         return OpenCommPort(appData.icsCommPort, &icsPR);
1777
1778     } else if (*appData.gateway != NULLCHAR) {
1779         if (*appData.remoteShell == NULLCHAR) {
1780             /* Use the rcmd protocol to run telnet program on a gateway host */
1781             snprintf(buf, sizeof(buf), "%s %s %s",
1782                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1783             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1784
1785         } else {
1786             /* Use the rsh program to run telnet program on a gateway host */
1787             if (*appData.remoteUser == NULLCHAR) {
1788                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789                         appData.gateway, appData.telnetProgram,
1790                         appData.icsHost, appData.icsPort);
1791             } else {
1792                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793                         appData.remoteShell, appData.gateway,
1794                         appData.remoteUser, appData.telnetProgram,
1795                         appData.icsHost, appData.icsPort);
1796             }
1797             return StartChildProcess(buf, "", &icsPR);
1798
1799         }
1800     } else if (appData.useTelnet) {
1801         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1802
1803     } else {
1804         /* TCP socket interface differs somewhat between
1805            Unix and NT; handle details in the front end.
1806            */
1807         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1808     }
1809 }
1810
1811 void
1812 EscapeExpand (char *p, char *q)
1813 {       // [HGM] initstring: routine to shape up string arguments
1814         while(*p++ = *q++) if(p[-1] == '\\')
1815             switch(*q++) {
1816                 case 'n': p[-1] = '\n'; break;
1817                 case 'r': p[-1] = '\r'; break;
1818                 case 't': p[-1] = '\t'; break;
1819                 case '\\': p[-1] = '\\'; break;
1820                 case 0: *p = 0; return;
1821                 default: p[-1] = q[-1]; break;
1822             }
1823 }
1824
1825 void
1826 show_bytes (FILE *fp, char *buf, int count)
1827 {
1828     while (count--) {
1829         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830             fprintf(fp, "\\%03o", *buf & 0xff);
1831         } else {
1832             putc(*buf, fp);
1833         }
1834         buf++;
1835     }
1836     fflush(fp);
1837 }
1838
1839 /* Returns an errno value */
1840 int
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1842 {
1843     char buf[8192], *p, *q, *buflim;
1844     int left, newcount, outcount;
1845
1846     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847         *appData.gateway != NULLCHAR) {
1848         if (appData.debugMode) {
1849             fprintf(debugFP, ">ICS: ");
1850             show_bytes(debugFP, message, count);
1851             fprintf(debugFP, "\n");
1852         }
1853         return OutputToProcess(pr, message, count, outError);
1854     }
1855
1856     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1857     p = message;
1858     q = buf;
1859     left = count;
1860     newcount = 0;
1861     while (left) {
1862         if (q >= buflim) {
1863             if (appData.debugMode) {
1864                 fprintf(debugFP, ">ICS: ");
1865                 show_bytes(debugFP, buf, newcount);
1866                 fprintf(debugFP, "\n");
1867             }
1868             outcount = OutputToProcess(pr, buf, newcount, outError);
1869             if (outcount < newcount) return -1; /* to be sure */
1870             q = buf;
1871             newcount = 0;
1872         }
1873         if (*p == '\n') {
1874             *q++ = '\r';
1875             newcount++;
1876         } else if (((unsigned char) *p) == TN_IAC) {
1877             *q++ = (char) TN_IAC;
1878             newcount ++;
1879         }
1880         *q++ = *p++;
1881         newcount++;
1882         left--;
1883     }
1884     if (appData.debugMode) {
1885         fprintf(debugFP, ">ICS: ");
1886         show_bytes(debugFP, buf, newcount);
1887         fprintf(debugFP, "\n");
1888     }
1889     outcount = OutputToProcess(pr, buf, newcount, outError);
1890     if (outcount < newcount) return -1; /* to be sure */
1891     return count;
1892 }
1893
1894 void
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1896 {
1897     int outError, outCount;
1898     static int gotEof = 0;
1899     static FILE *ini;
1900
1901     /* Pass data read from player on to ICS */
1902     if (count > 0) {
1903         gotEof = 0;
1904         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905         if (outCount < count) {
1906             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1907         }
1908         if(have_sent_ICS_logon == 2) {
1909           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910             fprintf(ini, "%s", message);
1911             have_sent_ICS_logon = 3;
1912           } else
1913             have_sent_ICS_logon = 1;
1914         } else if(have_sent_ICS_logon == 3) {
1915             fprintf(ini, "%s", message);
1916             fclose(ini);
1917           have_sent_ICS_logon = 1;
1918         }
1919     } else if (count < 0) {
1920         RemoveInputSource(isr);
1921         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922     } else if (gotEof++ > 0) {
1923         RemoveInputSource(isr);
1924         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1925     }
1926 }
1927
1928 void
1929 KeepAlive ()
1930 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933     SendToICS("date\n");
1934     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1935 }
1936
1937 /* added routine for printf style output to ics */
1938 void
1939 ics_printf (char *format, ...)
1940 {
1941     char buffer[MSG_SIZ];
1942     va_list args;
1943
1944     va_start(args, format);
1945     vsnprintf(buffer, sizeof(buffer), format, args);
1946     buffer[sizeof(buffer)-1] = '\0';
1947     SendToICS(buffer);
1948     va_end(args);
1949 }
1950
1951 void
1952 SendToICS (char *s)
1953 {
1954     int count, outCount, outError;
1955
1956     if (icsPR == NoProc) return;
1957
1958     count = strlen(s);
1959     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960     if (outCount < count) {
1961         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1962     }
1963 }
1964
1965 /* This is used for sending logon scripts to the ICS. Sending
1966    without a delay causes problems when using timestamp on ICC
1967    (at least on my machine). */
1968 void
1969 SendToICSDelayed (char *s, long msdelay)
1970 {
1971     int count, outCount, outError;
1972
1973     if (icsPR == NoProc) return;
1974
1975     count = strlen(s);
1976     if (appData.debugMode) {
1977         fprintf(debugFP, ">ICS: ");
1978         show_bytes(debugFP, s, count);
1979         fprintf(debugFP, "\n");
1980     }
1981     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1982                                       msdelay);
1983     if (outCount < count) {
1984         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1985     }
1986 }
1987
1988
1989 /* Remove all highlighting escape sequences in s
1990    Also deletes any suffix starting with '('
1991    */
1992 char *
1993 StripHighlightAndTitle (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             if (*s == '(' || *s == '[') {
2005                 *p = NULLCHAR;
2006                 return retbuf;
2007             }
2008             *p++ = *s++;
2009         }
2010     }
2011     *p = NULLCHAR;
2012     return retbuf;
2013 }
2014
2015 /* Remove all highlighting escape sequences in s */
2016 char *
2017 StripHighlight (char *s)
2018 {
2019     static char retbuf[MSG_SIZ];
2020     char *p = retbuf;
2021
2022     while (*s != NULLCHAR) {
2023         while (*s == '\033') {
2024             while (*s != NULLCHAR && !isalpha(*s)) s++;
2025             if (*s != NULLCHAR) s++;
2026         }
2027         while (*s != NULLCHAR && *s != '\033') {
2028             *p++ = *s++;
2029         }
2030     }
2031     *p = NULLCHAR;
2032     return retbuf;
2033 }
2034
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2037 char *
2038 VariantName (VariantClass v)
2039 {
2040     if(v == VariantUnknown || *engineVariant) return engineVariant;
2041     return variantNames[v];
2042 }
2043
2044
2045 /* Identify a variant from the strings the chess servers use or the
2046    PGN Variant tag names we use. */
2047 VariantClass
2048 StringToVariant (char *e)
2049 {
2050     char *p;
2051     int wnum = -1;
2052     VariantClass v = VariantNormal;
2053     int i, found = FALSE;
2054     char buf[MSG_SIZ];
2055     int len;
2056
2057     if (!e) return v;
2058
2059     /* [HGM] skip over optional board-size prefixes */
2060     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062         while( *e++ != '_');
2063     }
2064
2065     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2066         v = VariantNormal;
2067         found = TRUE;
2068     } else
2069     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070       if (StrCaseStr(e, variantNames[i])) {
2071         v = (VariantClass) i;
2072         found = TRUE;
2073         break;
2074       }
2075     }
2076
2077     if (!found) {
2078       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079           || StrCaseStr(e, "wild/fr")
2080           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081         v = VariantFischeRandom;
2082       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083                  (i = 1, p = StrCaseStr(e, "w"))) {
2084         p += i;
2085         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2086         if (isdigit(*p)) {
2087           wnum = atoi(p);
2088         } else {
2089           wnum = -1;
2090         }
2091         switch (wnum) {
2092         case 0: /* FICS only, actually */
2093         case 1:
2094           /* Castling legal even if K starts on d-file */
2095           v = VariantWildCastle;
2096           break;
2097         case 2:
2098         case 3:
2099         case 4:
2100           /* Castling illegal even if K & R happen to start in
2101              normal positions. */
2102           v = VariantNoCastle;
2103           break;
2104         case 5:
2105         case 7:
2106         case 8:
2107         case 10:
2108         case 11:
2109         case 12:
2110         case 13:
2111         case 14:
2112         case 15:
2113         case 18:
2114         case 19:
2115           /* Castling legal iff K & R start in normal positions */
2116           v = VariantNormal;
2117           break;
2118         case 6:
2119         case 20:
2120         case 21:
2121           /* Special wilds for position setup; unclear what to do here */
2122           v = VariantLoadable;
2123           break;
2124         case 9:
2125           /* Bizarre ICC game */
2126           v = VariantTwoKings;
2127           break;
2128         case 16:
2129           v = VariantKriegspiel;
2130           break;
2131         case 17:
2132           v = VariantLosers;
2133           break;
2134         case 22:
2135           v = VariantFischeRandom;
2136           break;
2137         case 23:
2138           v = VariantCrazyhouse;
2139           break;
2140         case 24:
2141           v = VariantBughouse;
2142           break;
2143         case 25:
2144           v = Variant3Check;
2145           break;
2146         case 26:
2147           /* Not quite the same as FICS suicide! */
2148           v = VariantGiveaway;
2149           break;
2150         case 27:
2151           v = VariantAtomic;
2152           break;
2153         case 28:
2154           v = VariantShatranj;
2155           break;
2156
2157         /* Temporary names for future ICC types.  The name *will* change in
2158            the next xboard/WinBoard release after ICC defines it. */
2159         case 29:
2160           v = Variant29;
2161           break;
2162         case 30:
2163           v = Variant30;
2164           break;
2165         case 31:
2166           v = Variant31;
2167           break;
2168         case 32:
2169           v = Variant32;
2170           break;
2171         case 33:
2172           v = Variant33;
2173           break;
2174         case 34:
2175           v = Variant34;
2176           break;
2177         case 35:
2178           v = Variant35;
2179           break;
2180         case 36:
2181           v = Variant36;
2182           break;
2183         case 37:
2184           v = VariantShogi;
2185           break;
2186         case 38:
2187           v = VariantXiangqi;
2188           break;
2189         case 39:
2190           v = VariantCourier;
2191           break;
2192         case 40:
2193           v = VariantGothic;
2194           break;
2195         case 41:
2196           v = VariantCapablanca;
2197           break;
2198         case 42:
2199           v = VariantKnightmate;
2200           break;
2201         case 43:
2202           v = VariantFairy;
2203           break;
2204         case 44:
2205           v = VariantCylinder;
2206           break;
2207         case 45:
2208           v = VariantFalcon;
2209           break;
2210         case 46:
2211           v = VariantCapaRandom;
2212           break;
2213         case 47:
2214           v = VariantBerolina;
2215           break;
2216         case 48:
2217           v = VariantJanus;
2218           break;
2219         case 49:
2220           v = VariantSuper;
2221           break;
2222         case 50:
2223           v = VariantGreat;
2224           break;
2225         case -1:
2226           /* Found "wild" or "w" in the string but no number;
2227              must assume it's normal chess. */
2228           v = VariantNormal;
2229           break;
2230         default:
2231           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232           if( (len >= MSG_SIZ) && appData.debugMode )
2233             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2234
2235           DisplayError(buf, 0);
2236           v = VariantUnknown;
2237           break;
2238         }
2239       }
2240     }
2241     if (appData.debugMode) {
2242       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243               e, wnum, VariantName(v));
2244     }
2245     return v;
2246 }
2247
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2250
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252    advance *index beyond it, and set leftover_start to the new value of
2253    *index; else return FALSE.  If pattern contains the character '*', it
2254    matches any sequence of characters not containing '\r', '\n', or the
2255    character following the '*' (if any), and the matched sequence(s) are
2256    copied into star_match.
2257    */
2258 int
2259 looking_at ( char *buf, int *index, char *pattern)
2260 {
2261     char *bufp = &buf[*index], *patternp = pattern;
2262     int star_count = 0;
2263     char *matchp = star_match[0];
2264
2265     for (;;) {
2266         if (*patternp == NULLCHAR) {
2267             *index = leftover_start = bufp - buf;
2268             *matchp = NULLCHAR;
2269             return TRUE;
2270         }
2271         if (*bufp == NULLCHAR) return FALSE;
2272         if (*patternp == '*') {
2273             if (*bufp == *(patternp + 1)) {
2274                 *matchp = NULLCHAR;
2275                 matchp = star_match[++star_count];
2276                 patternp += 2;
2277                 bufp++;
2278                 continue;
2279             } else if (*bufp == '\n' || *bufp == '\r') {
2280                 patternp++;
2281                 if (*patternp == NULLCHAR)
2282                   continue;
2283                 else
2284                   return FALSE;
2285             } else {
2286                 *matchp++ = *bufp++;
2287                 continue;
2288             }
2289         }
2290         if (*patternp != *bufp) return FALSE;
2291         patternp++;
2292         bufp++;
2293     }
2294 }
2295
2296 void
2297 SendToPlayer (char *data, int length)
2298 {
2299     int error, outCount;
2300     outCount = OutputToProcess(NoProc, data, length, &error);
2301     if (outCount < length) {
2302         DisplayFatalError(_("Error writing to display"), error, 1);
2303     }
2304 }
2305
2306 void
2307 PackHolding (char packed[], char *holding)
2308 {
2309     char *p = holding;
2310     char *q = packed;
2311     int runlength = 0;
2312     int curr = 9999;
2313     do {
2314         if (*p == curr) {
2315             runlength++;
2316         } else {
2317             switch (runlength) {
2318               case 0:
2319                 break;
2320               case 1:
2321                 *q++ = curr;
2322                 break;
2323               case 2:
2324                 *q++ = curr;
2325                 *q++ = curr;
2326                 break;
2327               default:
2328                 sprintf(q, "%d", runlength);
2329                 while (*q) q++;
2330                 *q++ = curr;
2331                 break;
2332             }
2333             runlength = 1;
2334             curr = *p;
2335         }
2336     } while (*p++);
2337     *q = NULLCHAR;
2338 }
2339
2340 /* Telnet protocol requests from the front end */
2341 void
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2343 {
2344     unsigned char msg[3];
2345     int outCount, outError;
2346
2347     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2348
2349     if (appData.debugMode) {
2350         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2351         switch (ddww) {
2352           case TN_DO:
2353             ddwwStr = "DO";
2354             break;
2355           case TN_DONT:
2356             ddwwStr = "DONT";
2357             break;
2358           case TN_WILL:
2359             ddwwStr = "WILL";
2360             break;
2361           case TN_WONT:
2362             ddwwStr = "WONT";
2363             break;
2364           default:
2365             ddwwStr = buf1;
2366             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367             break;
2368         }
2369         switch (option) {
2370           case TN_ECHO:
2371             optionStr = "ECHO";
2372             break;
2373           default:
2374             optionStr = buf2;
2375             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2376             break;
2377         }
2378         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2379     }
2380     msg[0] = TN_IAC;
2381     msg[1] = ddww;
2382     msg[2] = option;
2383     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2384     if (outCount < 3) {
2385         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2386     }
2387 }
2388
2389 void
2390 DoEcho ()
2391 {
2392     if (!appData.icsActive) return;
2393     TelnetRequest(TN_DO, TN_ECHO);
2394 }
2395
2396 void
2397 DontEcho ()
2398 {
2399     if (!appData.icsActive) return;
2400     TelnetRequest(TN_DONT, TN_ECHO);
2401 }
2402
2403 void
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2405 {
2406     /* put the holdings sent to us by the server on the board holdings area */
2407     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2408     char p;
2409     ChessSquare piece;
2410
2411     if(gameInfo.holdingsWidth < 2)  return;
2412     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413         return; // prevent overwriting by pre-board holdings
2414
2415     if( (int)lowestPiece >= BlackPawn ) {
2416         holdingsColumn = 0;
2417         countsColumn = 1;
2418         holdingsStartRow = BOARD_HEIGHT-1;
2419         direction = -1;
2420     } else {
2421         holdingsColumn = BOARD_WIDTH-1;
2422         countsColumn = BOARD_WIDTH-2;
2423         holdingsStartRow = 0;
2424         direction = 1;
2425     }
2426
2427     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428         board[i][holdingsColumn] = EmptySquare;
2429         board[i][countsColumn]   = (ChessSquare) 0;
2430     }
2431     while( (p=*holdings++) != NULLCHAR ) {
2432         piece = CharToPiece( ToUpper(p) );
2433         if(piece == EmptySquare) continue;
2434         /*j = (int) piece - (int) WhitePawn;*/
2435         j = PieceToNumber(piece);
2436         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437         if(j < 0) continue;               /* should not happen */
2438         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440         board[holdingsStartRow+j*direction][countsColumn]++;
2441     }
2442 }
2443
2444
2445 void
2446 VariantSwitch (Board board, VariantClass newVariant)
2447 {
2448    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449    static Board oldBoard;
2450
2451    startedFromPositionFile = FALSE;
2452    if(gameInfo.variant == newVariant) return;
2453
2454    /* [HGM] This routine is called each time an assignment is made to
2455     * gameInfo.variant during a game, to make sure the board sizes
2456     * are set to match the new variant. If that means adding or deleting
2457     * holdings, we shift the playing board accordingly
2458     * This kludge is needed because in ICS observe mode, we get boards
2459     * of an ongoing game without knowing the variant, and learn about the
2460     * latter only later. This can be because of the move list we requested,
2461     * in which case the game history is refilled from the beginning anyway,
2462     * but also when receiving holdings of a crazyhouse game. In the latter
2463     * case we want to add those holdings to the already received position.
2464     */
2465
2466
2467    if (appData.debugMode) {
2468      fprintf(debugFP, "Switch board from %s to %s\n",
2469              VariantName(gameInfo.variant), VariantName(newVariant));
2470      setbuf(debugFP, NULL);
2471    }
2472    shuffleOpenings = 0;       /* [HGM] shuffle */
2473    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2474    switch(newVariant)
2475      {
2476      case VariantShogi:
2477        newWidth = 9;  newHeight = 9;
2478        gameInfo.holdingsSize = 7;
2479      case VariantBughouse:
2480      case VariantCrazyhouse:
2481        newHoldingsWidth = 2; break;
2482      case VariantGreat:
2483        newWidth = 10;
2484      case VariantSuper:
2485        newHoldingsWidth = 2;
2486        gameInfo.holdingsSize = 8;
2487        break;
2488      case VariantGothic:
2489      case VariantCapablanca:
2490      case VariantCapaRandom:
2491        newWidth = 10;
2492      default:
2493        newHoldingsWidth = gameInfo.holdingsSize = 0;
2494      };
2495
2496    if(newWidth  != gameInfo.boardWidth  ||
2497       newHeight != gameInfo.boardHeight ||
2498       newHoldingsWidth != gameInfo.holdingsWidth ) {
2499
2500      /* shift position to new playing area, if needed */
2501      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502        for(i=0; i<BOARD_HEIGHT; i++)
2503          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2505              board[i][j];
2506        for(i=0; i<newHeight; i++) {
2507          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2509        }
2510      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511        for(i=0; i<BOARD_HEIGHT; i++)
2512          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2514              board[i][j];
2515      }
2516      board[HOLDINGS_SET] = 0;
2517      gameInfo.boardWidth  = newWidth;
2518      gameInfo.boardHeight = newHeight;
2519      gameInfo.holdingsWidth = newHoldingsWidth;
2520      gameInfo.variant = newVariant;
2521      InitDrawingSizes(-2, 0);
2522    } else gameInfo.variant = newVariant;
2523    CopyBoard(oldBoard, board);   // remember correctly formatted board
2524      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2525    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2526 }
2527
2528 static int loggedOn = FALSE;
2529
2530 /*-- Game start info cache: --*/
2531 int gs_gamenum;
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\   ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2539
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2542
2543 // [HGM] seekgraph
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2547 #define SQUARE 0x80
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2556
2557 void
2558 PlotSeekAd (int i)
2559 {
2560         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562         if(r < minRating+100 && r >=0 ) r = minRating+100;
2563         if(r > maxRating) r = maxRating;
2564         if(tc < 1.f) tc = 1.f;
2565         if(tc > 95.f) tc = 95.f;
2566         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567         y = ((double)r - minRating)/(maxRating - minRating)
2568             * (h-vMargin-squareSize/8-1) + vMargin;
2569         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570         if(strstr(seekAdList[i], " u ")) color = 1;
2571         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572            !strstr(seekAdList[i], "bullet") &&
2573            !strstr(seekAdList[i], "blitz") &&
2574            !strstr(seekAdList[i], "standard") ) color = 2;
2575         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2577 }
2578
2579 void
2580 PlotSingleSeekAd (int i)
2581 {
2582         PlotSeekAd(i);
2583 }
2584
2585 void
2586 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2587 {
2588         char buf[MSG_SIZ], *ext = "";
2589         VariantClass v = StringToVariant(type);
2590         if(strstr(type, "wild")) {
2591             ext = type + 4; // append wild number
2592             if(v == VariantFischeRandom) type = "chess960"; else
2593             if(v == VariantLoadable) type = "setup"; else
2594             type = VariantName(v);
2595         }
2596         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602             seekNrList[nrOfSeekAds] = nr;
2603             zList[nrOfSeekAds] = 0;
2604             seekAdList[nrOfSeekAds++] = StrSave(buf);
2605             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2606         }
2607 }
2608
2609 void
2610 EraseSeekDot (int i)
2611 {
2612     int x = xList[i], y = yList[i], d=squareSize/4, k;
2613     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615     // now replot every dot that overlapped
2616     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617         int xx = xList[k], yy = yList[k];
2618         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619             DrawSeekDot(xx, yy, colorList[k]);
2620     }
2621 }
2622
2623 void
2624 RemoveSeekAd (int nr)
2625 {
2626         int i;
2627         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2628             EraseSeekDot(i);
2629             if(seekAdList[i]) free(seekAdList[i]);
2630             seekAdList[i] = seekAdList[--nrOfSeekAds];
2631             seekNrList[i] = seekNrList[nrOfSeekAds];
2632             ratingList[i] = ratingList[nrOfSeekAds];
2633             colorList[i]  = colorList[nrOfSeekAds];
2634             tcList[i] = tcList[nrOfSeekAds];
2635             xList[i]  = xList[nrOfSeekAds];
2636             yList[i]  = yList[nrOfSeekAds];
2637             zList[i]  = zList[nrOfSeekAds];
2638             seekAdList[nrOfSeekAds] = NULL;
2639             break;
2640         }
2641 }
2642
2643 Boolean
2644 MatchSoughtLine (char *line)
2645 {
2646     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647     int nr, base, inc, u=0; char dummy;
2648
2649     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2651        (u=1) &&
2652        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2654         // match: compact and save the line
2655         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2656         return TRUE;
2657     }
2658     return FALSE;
2659 }
2660
2661 int
2662 DrawSeekGraph ()
2663 {
2664     int i;
2665     if(!seekGraphUp) return FALSE;
2666     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2668
2669     DrawSeekBackground(0, 0, w, h);
2670     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2674         yy = h-1-yy;
2675         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2676         if(i%500 == 0) {
2677             char buf[MSG_SIZ];
2678             snprintf(buf, MSG_SIZ, "%d", i);
2679             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2680         }
2681     }
2682     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683     for(i=1; i<100; i+=(i<10?1:5)) {
2684         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2690         }
2691     }
2692     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2693     return TRUE;
2694 }
2695
2696 int
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2698 {
2699     static int lastDown = 0, displayed = 0, lastSecond;
2700     if(y < 0) return FALSE;
2701     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703         if(!seekGraphUp) return FALSE;
2704         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705         DrawPosition(TRUE, NULL);
2706         return TRUE;
2707     }
2708     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709         if(click == Release || moving) return FALSE;
2710         nrOfSeekAds = 0;
2711         soughtPending = TRUE;
2712         SendToICS(ics_prefix);
2713         SendToICS("sought\n"); // should this be "sought all"?
2714     } else { // issue challenge based on clicked ad
2715         int dist = 10000; int i, closest = 0, second = 0;
2716         for(i=0; i<nrOfSeekAds; i++) {
2717             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2718             if(d < dist) { dist = d; closest = i; }
2719             second += (d - zList[i] < 120); // count in-range ads
2720             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2721         }
2722         if(dist < 120) {
2723             char buf[MSG_SIZ];
2724             second = (second > 1);
2725             if(displayed != closest || second != lastSecond) {
2726                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727                 lastSecond = second; displayed = closest;
2728             }
2729             if(click == Press) {
2730                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2731                 lastDown = closest;
2732                 return TRUE;
2733             } // on press 'hit', only show info
2734             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736             SendToICS(ics_prefix);
2737             SendToICS(buf);
2738             return TRUE; // let incoming board of started game pop down the graph
2739         } else if(click == Release) { // release 'miss' is ignored
2740             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741             if(moving == 2) { // right up-click
2742                 nrOfSeekAds = 0; // refresh graph
2743                 soughtPending = TRUE;
2744                 SendToICS(ics_prefix);
2745                 SendToICS("sought\n"); // should this be "sought all"?
2746             }
2747             return TRUE;
2748         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749         // press miss or release hit 'pop down' seek graph
2750         seekGraphUp = FALSE;
2751         DrawPosition(TRUE, NULL);
2752     }
2753     return TRUE;
2754 }
2755
2756 void
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2758 {
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2768
2769     static int started = STARTED_NONE;
2770     static char parse[20000];
2771     static int parse_pos = 0;
2772     static char buf[BUF_SIZE + 1];
2773     static int firstTime = TRUE, intfSet = FALSE;
2774     static ColorClass prevColor = ColorNormal;
2775     static int savingComment = FALSE;
2776     static int cmatch = 0; // continuation sequence match
2777     char *bp;
2778     char str[MSG_SIZ];
2779     int i, oldi;
2780     int buf_len;
2781     int next_out;
2782     int tkind;
2783     int backup;    /* [DM] For zippy color lines */
2784     char *p;
2785     char talker[MSG_SIZ]; // [HGM] chat
2786     int channel;
2787
2788     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2789
2790     if (appData.debugMode) {
2791       if (!error) {
2792         fprintf(debugFP, "<ICS: ");
2793         show_bytes(debugFP, data, count);
2794         fprintf(debugFP, "\n");
2795       }
2796     }
2797
2798     if (appData.debugMode) { int f = forwardMostMove;
2799         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2802     }
2803     if (count > 0) {
2804         /* If last read ended with a partial line that we couldn't parse,
2805            prepend it to the new read and try again. */
2806         if (leftover_len > 0) {
2807             for (i=0; i<leftover_len; i++)
2808               buf[i] = buf[leftover_start + i];
2809         }
2810
2811     /* copy new characters into the buffer */
2812     bp = buf + leftover_len;
2813     buf_len=leftover_len;
2814     for (i=0; i<count; i++)
2815     {
2816         // ignore these
2817         if (data[i] == '\r')
2818             continue;
2819
2820         // join lines split by ICS?
2821         if (!appData.noJoin)
2822         {
2823             /*
2824                 Joining just consists of finding matches against the
2825                 continuation sequence, and discarding that sequence
2826                 if found instead of copying it.  So, until a match
2827                 fails, there's nothing to do since it might be the
2828                 complete sequence, and thus, something we don't want
2829                 copied.
2830             */
2831             if (data[i] == cont_seq[cmatch])
2832             {
2833                 cmatch++;
2834                 if (cmatch == strlen(cont_seq))
2835                 {
2836                     cmatch = 0; // complete match.  just reset the counter
2837
2838                     /*
2839                         it's possible for the ICS to not include the space
2840                         at the end of the last word, making our [correct]
2841                         join operation fuse two separate words.  the server
2842                         does this when the space occurs at the width setting.
2843                     */
2844                     if (!buf_len || buf[buf_len-1] != ' ')
2845                     {
2846                         *bp++ = ' ';
2847                         buf_len++;
2848                     }
2849                 }
2850                 continue;
2851             }
2852             else if (cmatch)
2853             {
2854                 /*
2855                     match failed, so we have to copy what matched before
2856                     falling through and copying this character.  In reality,
2857                     this will only ever be just the newline character, but
2858                     it doesn't hurt to be precise.
2859                 */
2860                 strncpy(bp, cont_seq, cmatch);
2861                 bp += cmatch;
2862                 buf_len += cmatch;
2863                 cmatch = 0;
2864             }
2865         }
2866
2867         // copy this char
2868         *bp++ = data[i];
2869         buf_len++;
2870     }
2871
2872         buf[buf_len] = NULLCHAR;
2873 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2874         next_out = 0;
2875         leftover_start = 0;
2876
2877         i = 0;
2878         while (i < buf_len) {
2879             /* Deal with part of the TELNET option negotiation
2880                protocol.  We refuse to do anything beyond the
2881                defaults, except that we allow the WILL ECHO option,
2882                which ICS uses to turn off password echoing when we are
2883                directly connected to it.  We reject this option
2884                if localLineEditing mode is on (always on in xboard)
2885                and we are talking to port 23, which might be a real
2886                telnet server that will try to keep WILL ECHO on permanently.
2887              */
2888             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890                 unsigned char option;
2891                 oldi = i;
2892                 switch ((unsigned char) buf[++i]) {
2893                   case TN_WILL:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<WILL ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       case TN_ECHO:
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "ECHO ");
2900                         /* Reply only if this is a change, according
2901                            to the protocol rules. */
2902                         if (remoteEchoOption) break;
2903                         if (appData.localLineEditing &&
2904                             atoi(appData.icsPort) == TN_PORT) {
2905                             TelnetRequest(TN_DONT, TN_ECHO);
2906                         } else {
2907                             EchoOff();
2908                             TelnetRequest(TN_DO, TN_ECHO);
2909                             remoteEchoOption = TRUE;
2910                         }
2911                         break;
2912                       default:
2913                         if (appData.debugMode)
2914                           fprintf(debugFP, "%d ", option);
2915                         /* Whatever this is, we don't want it. */
2916                         TelnetRequest(TN_DONT, option);
2917                         break;
2918                     }
2919                     break;
2920                   case TN_WONT:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WONT ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (!remoteEchoOption) break;
2930                         EchoOn();
2931                         TelnetRequest(TN_DONT, TN_ECHO);
2932                         remoteEchoOption = FALSE;
2933                         break;
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", (unsigned char) option);
2937                         /* Whatever this is, it must already be turned
2938                            off, because we never agree to turn on
2939                            anything non-default, so according to the
2940                            protocol rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_DO:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<DO ");
2947                     switch (option = (unsigned char) buf[++i]) {
2948                       default:
2949                         /* Whatever this is, we refuse to do it. */
2950                         if (appData.debugMode)
2951                           fprintf(debugFP, "%d ", option);
2952                         TelnetRequest(TN_WONT, option);
2953                         break;
2954                     }
2955                     break;
2956                   case TN_DONT:
2957                     if (appData.debugMode)
2958                       fprintf(debugFP, "\n<DONT ");
2959                     switch (option = (unsigned char) buf[++i]) {
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we are already not doing
2964                            it, because we never agree to do anything
2965                            non-default, so according to the protocol
2966                            rules, we don't reply. */
2967                         break;
2968                     }
2969                     break;
2970                   case TN_IAC:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<IAC ");
2973                     /* Doubled IAC; pass it through */
2974                     i--;
2975                     break;
2976                   default:
2977                     if (appData.debugMode)
2978                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979                     /* Drop all other telnet commands on the floor */
2980                     break;
2981                 }
2982                 if (oldi > next_out)
2983                   SendToPlayer(&buf[next_out], oldi - next_out);
2984                 if (++i > next_out)
2985                   next_out = i;
2986                 continue;
2987             }
2988
2989             /* OK, this at least will *usually* work */
2990             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2991                 loggedOn = TRUE;
2992             }
2993
2994             if (loggedOn && !intfSet) {
2995                 if (ics_type == ICS_ICC) {
2996                   snprintf(str, MSG_SIZ,
2997                           "/set-quietly interface %s\n/set-quietly style 12\n",
2998                           programVersion);
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3001                 } else if (ics_type == ICS_CHESSNET) {
3002                   snprintf(str, MSG_SIZ, "/style 12\n");
3003                 } else {
3004                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005                   strcat(str, programVersion);
3006                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3009 #ifdef WIN32
3010                   strcat(str, "$iset nohighlight 1\n");
3011 #endif
3012                   strcat(str, "$iset lock 1\n$style 12\n");
3013                 }
3014                 SendToICS(str);
3015                 NotifyFrontendLogin();
3016                 intfSet = TRUE;
3017             }
3018
3019             if (started == STARTED_COMMENT) {
3020                 /* Accumulate characters in comment */
3021                 parse[parse_pos++] = buf[i];
3022                 if (buf[i] == '\n') {
3023                     parse[parse_pos] = NULLCHAR;
3024                     if(chattingPartner>=0) {
3025                         char mess[MSG_SIZ];
3026                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027                         OutputChatMessage(chattingPartner, mess);
3028                         chattingPartner = -1;
3029                         next_out = i+1; // [HGM] suppress printing in ICS window
3030                     } else
3031                     if(!suppressKibitz) // [HGM] kibitz
3032                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034                         int nrDigit = 0, nrAlph = 0, j;
3035                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037                         parse[parse_pos] = NULLCHAR;
3038                         // try to be smart: if it does not look like search info, it should go to
3039                         // ICS interaction window after all, not to engine-output window.
3040                         for(j=0; j<parse_pos; j++) { // count letters and digits
3041                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3043                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3044                         }
3045                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046                             int depth=0; float score;
3047                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049                                 pvInfoList[forwardMostMove-1].depth = depth;
3050                                 pvInfoList[forwardMostMove-1].score = 100*score;
3051                             }
3052                             OutputKibitz(suppressKibitz, parse);
3053                         } else {
3054                             char tmp[MSG_SIZ];
3055                             if(gameMode == IcsObserving) // restore original ICS messages
3056                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3058                             else
3059                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061                             SendToPlayer(tmp, strlen(tmp));
3062                         }
3063                         next_out = i+1; // [HGM] suppress printing in ICS window
3064                     }
3065                     started = STARTED_NONE;
3066                 } else {
3067                     /* Don't match patterns against characters in comment */
3068                     i++;
3069                     continue;
3070                 }
3071             }
3072             if (started == STARTED_CHATTER) {
3073                 if (buf[i] != '\n') {
3074                     /* Don't match patterns against characters in chatter */
3075                     i++;
3076                     continue;
3077                 }
3078                 started = STARTED_NONE;
3079                 if(suppressKibitz) next_out = i+1;
3080             }
3081
3082             /* Kludge to deal with rcmd protocol */
3083             if (firstTime && looking_at(buf, &i, "\001*")) {
3084                 DisplayFatalError(&buf[1], 0, 1);
3085                 continue;
3086             } else {
3087                 firstTime = FALSE;
3088             }
3089
3090             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3091                 ics_type = ICS_ICC;
3092                 ics_prefix = "/";
3093                 if (appData.debugMode)
3094                   fprintf(debugFP, "ics_type %d\n", ics_type);
3095                 continue;
3096             }
3097             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098                 ics_type = ICS_FICS;
3099                 ics_prefix = "$";
3100                 if (appData.debugMode)
3101                   fprintf(debugFP, "ics_type %d\n", ics_type);
3102                 continue;
3103             }
3104             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105                 ics_type = ICS_CHESSNET;
3106                 ics_prefix = "/";
3107                 if (appData.debugMode)
3108                   fprintf(debugFP, "ics_type %d\n", ics_type);
3109                 continue;
3110             }
3111
3112             if (!loggedOn &&
3113                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3115                  looking_at(buf, &i, "will be \"*\""))) {
3116               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3117               continue;
3118             }
3119
3120             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3121               char buf[MSG_SIZ];
3122               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123               DisplayIcsInteractionTitle(buf);
3124               have_set_title = TRUE;
3125             }
3126
3127             /* skip finger notes */
3128             if (started == STARTED_NONE &&
3129                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130                  (buf[i] == '1' && buf[i+1] == '0')) &&
3131                 buf[i+2] == ':' && buf[i+3] == ' ') {
3132               started = STARTED_CHATTER;
3133               i += 3;
3134               continue;
3135             }
3136
3137             oldi = i;
3138             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139             if(appData.seekGraph) {
3140                 if(soughtPending && MatchSoughtLine(buf+i)) {
3141                     i = strstr(buf+i, "rated") - buf;
3142                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                     next_out = leftover_start = i;
3144                     started = STARTED_CHATTER;
3145                     suppressKibitz = TRUE;
3146                     continue;
3147                 }
3148                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149                         && looking_at(buf, &i, "* ads displayed")) {
3150                     soughtPending = FALSE;
3151                     seekGraphUp = TRUE;
3152                     DrawSeekGraph();
3153                     continue;
3154                 }
3155                 if(appData.autoRefresh) {
3156                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157                         int s = (ics_type == ICS_ICC); // ICC format differs
3158                         if(seekGraphUp)
3159                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161                         looking_at(buf, &i, "*% "); // eat prompt
3162                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = i; // suppress
3165                         continue;
3166                     }
3167                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168                         char *p = star_match[0];
3169                         while(*p) {
3170                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3171                             while(*p && *p++ != ' '); // next
3172                         }
3173                         looking_at(buf, &i, "*% "); // eat prompt
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         continue;
3177                     }
3178                 }
3179             }
3180
3181             /* skip formula vars */
3182             if (started == STARTED_NONE &&
3183                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184               started = STARTED_CHATTER;
3185               i += 3;
3186               continue;
3187             }
3188
3189             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190             if (appData.autoKibitz && started == STARTED_NONE &&
3191                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3192                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3197                         suppressKibitz = TRUE;
3198                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = i;
3200                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201                                 && (gameMode == IcsPlayingWhite)) ||
3202                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3204                             started = STARTED_CHATTER; // own kibitz we simply discard
3205                         else {
3206                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207                             parse_pos = 0; parse[0] = NULLCHAR;
3208                             savingComment = TRUE;
3209                             suppressKibitz = gameMode != IcsObserving ? 2 :
3210                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3211                         }
3212                         continue;
3213                 } else
3214                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216                          && atoi(star_match[0])) {
3217                     // suppress the acknowledgements of our own autoKibitz
3218                     char *p;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221                     SendToPlayer(star_match[0], strlen(star_match[0]));
3222                     if(looking_at(buf, &i, "*% ")) // eat prompt
3223                         suppressKibitz = FALSE;
3224                     next_out = i;
3225                     continue;
3226                 }
3227             } // [HGM] kibitz: end of patch
3228
3229             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3230
3231             // [HGM] chat: intercept tells by users for which we have an open chat window
3232             channel = -1;
3233             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234                                            looking_at(buf, &i, "* whispers:") ||
3235                                            looking_at(buf, &i, "* kibitzes:") ||
3236                                            looking_at(buf, &i, "* shouts:") ||
3237                                            looking_at(buf, &i, "* c-shouts:") ||
3238                                            looking_at(buf, &i, "--> * ") ||
3239                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3243                 int p;
3244                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245                 chattingPartner = -1;
3246
3247                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250                     talker[0] = '['; strcat(talker, "] ");
3251                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252                     chattingPartner = p; break;
3253                     }
3254                 } else
3255                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256                 for(p=0; p<MAX_CHAT; p++) {
3257                     if(!strcmp("kibitzes", chatPartner[p])) {
3258                         talker[0] = '['; strcat(talker, "] ");
3259                         chattingPartner = p; break;
3260                     }
3261                 } else
3262                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263                 for(p=0; p<MAX_CHAT; p++) {
3264                     if(!strcmp("whispers", chatPartner[p])) {
3265                         talker[0] = '['; strcat(talker, "] ");
3266                         chattingPartner = p; break;
3267                     }
3268                 } else
3269                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270                   if(buf[i-8] == '-' && buf[i-3] == 't')
3271                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272                     if(!strcmp("c-shouts", chatPartner[p])) {
3273                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274                         chattingPartner = p; break;
3275                     }
3276                   }
3277                   if(chattingPartner < 0)
3278                   for(p=0; p<MAX_CHAT; p++) {
3279                     if(!strcmp("shouts", chatPartner[p])) {
3280                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283                         chattingPartner = p; break;
3284                     }
3285                   }
3286                 }
3287                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289                     talker[0] = 0; Colorize(ColorTell, FALSE);
3290                     chattingPartner = p; break;
3291                 }
3292                 if(chattingPartner<0) i = oldi; else {
3293                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296                     started = STARTED_COMMENT;
3297                     parse_pos = 0; parse[0] = NULLCHAR;
3298                     savingComment = 3 + chattingPartner; // counts as TRUE
3299                     suppressKibitz = TRUE;
3300                     continue;
3301                 }
3302             } // [HGM] chat: end of patch
3303
3304           backup = i;
3305             if (appData.zippyTalk || appData.zippyPlay) {
3306                 /* [DM] Backup address for color zippy lines */
3307 #if ZIPPY
3308                if (loggedOn == TRUE)
3309                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3311 #endif
3312             } // [DM] 'else { ' deleted
3313                 if (
3314                     /* Regular tells and says */
3315                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3317                     looking_at(buf, &i, "* says: ") ||
3318                     /* Don't color "message" or "messages" output */
3319                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320                     looking_at(buf, &i, "*. * at *:*: ") ||
3321                     looking_at(buf, &i, "--* (*:*): ") ||
3322                     /* Message notifications (same color as tells) */
3323                     looking_at(buf, &i, "* has left a message ") ||
3324                     looking_at(buf, &i, "* just sent you a message:\n") ||
3325                     /* Whispers and kibitzes */
3326                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327                     looking_at(buf, &i, "* kibitzes: ") ||
3328                     /* Channel tells */
3329                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3330
3331                   if (tkind == 1 && strchr(star_match[0], ':')) {
3332                       /* Avoid "tells you:" spoofs in channels */
3333                      tkind = 3;
3334                   }
3335                   if (star_match[0][0] == NULLCHAR ||
3336                       strchr(star_match[0], ' ') ||
3337                       (tkind == 3 && strchr(star_match[1], ' '))) {
3338                     /* Reject bogus matches */
3339                     i = oldi;
3340                   } else {
3341                     if (appData.colorize) {
3342                       if (oldi > next_out) {
3343                         SendToPlayer(&buf[next_out], oldi - next_out);
3344                         next_out = oldi;
3345                       }
3346                       switch (tkind) {
3347                       case 1:
3348                         Colorize(ColorTell, FALSE);
3349                         curColor = ColorTell;
3350                         break;
3351                       case 2:
3352                         Colorize(ColorKibitz, FALSE);
3353                         curColor = ColorKibitz;
3354                         break;
3355                       case 3:
3356                         p = strrchr(star_match[1], '(');
3357                         if (p == NULL) {
3358                           p = star_match[1];
3359                         } else {
3360                           p++;
3361                         }
3362                         if (atoi(p) == 1) {
3363                           Colorize(ColorChannel1, FALSE);
3364                           curColor = ColorChannel1;
3365                         } else {
3366                           Colorize(ColorChannel, FALSE);
3367                           curColor = ColorChannel;
3368                         }
3369                         break;
3370                       case 5:
3371                         curColor = ColorNormal;
3372                         break;
3373                       }
3374                     }
3375                     if (started == STARTED_NONE && appData.autoComment &&
3376                         (gameMode == IcsObserving ||
3377                          gameMode == IcsPlayingWhite ||
3378                          gameMode == IcsPlayingBlack)) {
3379                       parse_pos = i - oldi;
3380                       memcpy(parse, &buf[oldi], parse_pos);
3381                       parse[parse_pos] = NULLCHAR;
3382                       started = STARTED_COMMENT;
3383                       savingComment = TRUE;
3384                     } else {
3385                       started = STARTED_CHATTER;
3386                       savingComment = FALSE;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                   }
3391                 }
3392
3393                 if (looking_at(buf, &i, "* s-shouts: ") ||
3394                     looking_at(buf, &i, "* c-shouts: ")) {
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorSShout, FALSE);
3401                         curColor = ColorSShout;
3402                     }
3403                     loggedOn = TRUE;
3404                     started = STARTED_CHATTER;
3405                     continue;
3406                 }
3407
3408                 if (looking_at(buf, &i, "--->")) {
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* shouts: ") ||
3414                     looking_at(buf, &i, "--> ")) {
3415                     if (appData.colorize) {
3416                         if (oldi > next_out) {
3417                             SendToPlayer(&buf[next_out], oldi - next_out);
3418                             next_out = oldi;
3419                         }
3420                         Colorize(ColorShout, FALSE);
3421                         curColor = ColorShout;
3422                     }
3423                     loggedOn = TRUE;
3424                     started = STARTED_CHATTER;
3425                     continue;
3426                 }
3427
3428                 if (looking_at( buf, &i, "Challenge:")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorChallenge, FALSE);
3435                         curColor = ColorChallenge;
3436                     }
3437                     loggedOn = TRUE;
3438                     continue;
3439                 }
3440
3441                 if (looking_at(buf, &i, "* offers you") ||
3442                     looking_at(buf, &i, "* offers to be") ||
3443                     looking_at(buf, &i, "* would like to") ||
3444                     looking_at(buf, &i, "* requests to") ||
3445                     looking_at(buf, &i, "Your opponent offers") ||
3446                     looking_at(buf, &i, "Your opponent requests")) {
3447
3448                     if (appData.colorize) {
3449                         if (oldi > next_out) {
3450                             SendToPlayer(&buf[next_out], oldi - next_out);
3451                             next_out = oldi;
3452                         }
3453                         Colorize(ColorRequest, FALSE);
3454                         curColor = ColorRequest;
3455                     }
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* (*) seeking")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorSeek, FALSE);
3466                         curColor = ColorSeek;
3467                     }
3468                     continue;
3469             }
3470
3471           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3472
3473             if (looking_at(buf, &i, "\\   ")) {
3474                 if (prevColor != ColorNormal) {
3475                     if (oldi > next_out) {
3476                         SendToPlayer(&buf[next_out], oldi - next_out);
3477                         next_out = oldi;
3478                     }
3479                     Colorize(prevColor, TRUE);
3480                     curColor = prevColor;
3481                 }
3482                 if (savingComment) {
3483                     parse_pos = i - oldi;
3484                     memcpy(parse, &buf[oldi], parse_pos);
3485                     parse[parse_pos] = NULLCHAR;
3486                     started = STARTED_COMMENT;
3487                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488                         chattingPartner = savingComment - 3; // kludge to remember the box
3489                 } else {
3490                     started = STARTED_CHATTER;
3491                 }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "Black Strength :") ||
3496                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497                 looking_at(buf, &i, "<10>") ||
3498                 looking_at(buf, &i, "#@#")) {
3499                 /* Wrong board style */
3500                 loggedOn = TRUE;
3501                 SendToICS(ics_prefix);
3502                 SendToICS("set style 12\n");
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i, "login:")) {
3509               if (!have_sent_ICS_logon) {
3510                 if(ICSInitScript())
3511                   have_sent_ICS_logon = 1;
3512                 else // no init script was found
3513                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3516               }
3517                 continue;
3518             }
3519
3520             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521                 (looking_at(buf, &i, "\n<12> ") ||
3522                  looking_at(buf, &i, "<12> "))) {
3523                 loggedOn = TRUE;
3524                 if (oldi > next_out) {
3525                     SendToPlayer(&buf[next_out], oldi - next_out);
3526                 }
3527                 next_out = i;
3528                 started = STARTED_BOARD;
3529                 parse_pos = 0;
3530                 continue;
3531             }
3532
3533             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534                 looking_at(buf, &i, "<b1> ")) {
3535                 if (oldi > next_out) {
3536                     SendToPlayer(&buf[next_out], oldi - next_out);
3537                 }
3538                 next_out = i;
3539                 started = STARTED_HOLDINGS;
3540                 parse_pos = 0;
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3545                 loggedOn = TRUE;
3546                 /* Header for a move list -- first line */
3547
3548                 switch (ics_getting_history) {
3549                   case H_FALSE:
3550                     switch (gameMode) {
3551                       case IcsIdle:
3552                       case BeginningOfGame:
3553                         /* User typed "moves" or "oldmoves" while we
3554                            were idle.  Pretend we asked for these
3555                            moves and soak them up so user can step
3556                            through them and/or save them.
3557                            */
3558                         Reset(FALSE, TRUE);
3559                         gameMode = IcsObserving;
3560                         ModeHighlight();
3561                         ics_gamenum = -1;
3562                         ics_getting_history = H_GOT_UNREQ_HEADER;
3563                         break;
3564                       case EditGame: /*?*/
3565                       case EditPosition: /*?*/
3566                         /* Should above feature work in these modes too? */
3567                         /* For now it doesn't */
3568                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3569                         break;
3570                       default:
3571                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3572                         break;
3573                     }
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Is this the right one? */
3577                     if (gameInfo.white && gameInfo.black &&
3578                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3579                         strcmp(gameInfo.black, star_match[2]) == 0) {
3580                         /* All is well */
3581                         ics_getting_history = H_GOT_REQ_HEADER;
3582                     }
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                   case H_GOT_UNREQ_HEADER:
3586                   case H_GOT_UNWANTED_HEADER:
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: two headers"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                 }
3593
3594                 /* Save player ratings into gameInfo if needed */
3595                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597                     (gameInfo.whiteRating == -1 ||
3598                      gameInfo.blackRating == -1)) {
3599
3600                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3601                     gameInfo.blackRating = string_to_rating(star_match[3]);
3602                     if (appData.debugMode)
3603                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604                               gameInfo.whiteRating, gameInfo.blackRating);
3605                 }
3606                 continue;
3607             }
3608
3609             if (looking_at(buf, &i,
3610               "* * match, initial time: * minute*, increment: * second")) {
3611                 /* Header for a move list -- second line */
3612                 /* Initial board will follow if this is a wild game */
3613                 if (gameInfo.event != NULL) free(gameInfo.event);
3614                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615                 gameInfo.event = StrSave(str);
3616                 /* [HGM] we switched variant. Translate boards if needed. */
3617                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3618                 continue;
3619             }
3620
3621             if (looking_at(buf, &i, "Move  ")) {
3622                 /* Beginning of a move list */
3623                 switch (ics_getting_history) {
3624                   case H_FALSE:
3625                     /* Normally should not happen */
3626                     /* Maybe user hit reset while we were parsing */
3627                     break;
3628                   case H_REQUESTED:
3629                     /* Happens if we are ignoring a move list that is not
3630                      * the one we just requested.  Common if the user
3631                      * tries to observe two games without turning off
3632                      * getMoveList */
3633                     break;
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: nested"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                   case H_GOT_REQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES;
3642                     parse_pos = 0;
3643                     if (oldi > next_out) {
3644                         SendToPlayer(&buf[next_out], oldi - next_out);
3645                     }
3646                     break;
3647                   case H_GOT_UNREQ_HEADER:
3648                     ics_getting_history = H_GETTING_MOVES;
3649                     started = STARTED_MOVES_NOHIDE;
3650                     parse_pos = 0;
3651                     break;
3652                   case H_GOT_UNWANTED_HEADER:
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655                 }
3656                 continue;
3657             }
3658
3659             if (looking_at(buf, &i, "% ") ||
3660                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663                     soughtPending = FALSE;
3664                     seekGraphUp = TRUE;
3665                     DrawSeekGraph();
3666                 }
3667                 if(suppressKibitz) next_out = i;
3668                 savingComment = FALSE;
3669                 suppressKibitz = 0;
3670                 switch (started) {
3671                   case STARTED_MOVES:
3672                   case STARTED_MOVES_NOHIDE:
3673                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674                     parse[parse_pos + i - oldi] = NULLCHAR;
3675                     ParseGameHistory(parse);
3676 #if ZIPPY
3677                     if (appData.zippyPlay && first.initDone) {
3678                         FeedMovesToProgram(&first, forwardMostMove);
3679                         if (gameMode == IcsPlayingWhite) {
3680                             if (WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("black\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, TRUE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, TRUE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         } else if (gameMode == IcsPlayingBlack) {
3704                             if (!WhiteOnMove(forwardMostMove)) {
3705                                 if (first.sendTime) {
3706                                   if (first.useColors) {
3707                                     SendToProgram("white\n", &first);
3708                                   }
3709                                   SendTimeRemaining(&first, FALSE);
3710                                 }
3711                                 if (first.useColors) {
3712                                   SendToProgram("black\n", &first);
3713                                 }
3714                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715                                 first.maybeThinking = TRUE;
3716                             } else {
3717                                 if (first.usePlayother) {
3718                                   if (first.sendTime) {
3719                                     SendTimeRemaining(&first, FALSE);
3720                                   }
3721                                   SendToProgram("playother\n", &first);
3722                                   firstMove = FALSE;
3723                                 } else {
3724                                   firstMove = TRUE;
3725                                 }
3726                             }
3727                         }
3728                     }
3729 #endif
3730                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3731                         /* Moves came from oldmoves or moves command
3732                            while we weren't doing anything else.
3733                            */
3734                         currentMove = forwardMostMove;
3735                         ClearHighlights();/*!!could figure this out*/
3736                         flipView = appData.flipView;
3737                         DrawPosition(TRUE, boards[currentMove]);
3738                         DisplayBothClocks();
3739                         snprintf(str, MSG_SIZ, "%s %s %s",
3740                                 gameInfo.white, _("vs."),  gameInfo.black);
3741                         DisplayTitle(str);
3742                         gameMode = IcsIdle;
3743                     } else {
3744                         /* Moves were history of an active game */
3745                         if (gameInfo.resultDetails != NULL) {
3746                             free(gameInfo.resultDetails);
3747                             gameInfo.resultDetails = NULL;
3748                         }
3749                     }
3750                     HistorySet(parseList, backwardMostMove,
3751                                forwardMostMove, currentMove-1);
3752                     DisplayMove(currentMove - 1);
3753                     if (started == STARTED_MOVES) next_out = i;
3754                     started = STARTED_NONE;
3755                     ics_getting_history = H_FALSE;
3756                     break;
3757
3758                   case STARTED_OBSERVE:
3759                     started = STARTED_NONE;
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                     break;
3763
3764                   default:
3765                     break;
3766                 }
3767                 if(bookHit) { // [HGM] book: simulate book reply
3768                     static char bookMove[MSG_SIZ]; // a bit generous?
3769
3770                     programStats.nodes = programStats.depth = programStats.time =
3771                     programStats.score = programStats.got_only_move = 0;
3772                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3773
3774                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775                     strcat(bookMove, bookHit);
3776                     HandleMachineMove(bookMove, &first);
3777                 }
3778                 continue;
3779             }
3780
3781             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782                  started == STARTED_HOLDINGS ||
3783                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784                 /* Accumulate characters in move list or board */
3785                 parse[parse_pos++] = buf[i];
3786             }
3787
3788             /* Start of game messages.  Mostly we detect start of game
3789                when the first board image arrives.  On some versions
3790                of the ICS, though, we need to do a "refresh" after starting
3791                to observe in order to get the current board right away. */
3792             if (looking_at(buf, &i, "Adding game * to observation list")) {
3793                 started = STARTED_OBSERVE;
3794                 continue;
3795             }
3796
3797             /* Handle auto-observe */
3798             if (appData.autoObserve &&
3799                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3801                 char *player;
3802                 /* Choose the player that was highlighted, if any. */
3803                 if (star_match[0][0] == '\033' ||
3804                     star_match[1][0] != '\033') {
3805                     player = star_match[0];
3806                 } else {
3807                     player = star_match[2];
3808                 }
3809                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810                         ics_prefix, StripHighlightAndTitle(player));
3811                 SendToICS(str);
3812
3813                 /* Save ratings from notify string */
3814                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815                 player1Rating = string_to_rating(star_match[1]);
3816                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817                 player2Rating = string_to_rating(star_match[3]);
3818
3819                 if (appData.debugMode)
3820                   fprintf(debugFP,
3821                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3822                           player1Name, player1Rating,
3823                           player2Name, player2Rating);
3824
3825                 continue;
3826             }
3827
3828             /* Deal with automatic examine mode after a game,
3829                and with IcsObserving -> IcsExamining transition */
3830             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831                 looking_at(buf, &i, "has made you an examiner of game *")) {
3832
3833                 int gamenum = atoi(star_match[0]);
3834                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835                     gamenum == ics_gamenum) {
3836                     /* We were already playing or observing this game;
3837                        no need to refetch history */
3838                     gameMode = IcsExamining;
3839                     if (pausing) {
3840                         pauseExamForwardMostMove = forwardMostMove;
3841                     } else if (currentMove < forwardMostMove) {
3842                         ForwardInner(forwardMostMove);
3843                     }
3844                 } else {
3845                     /* I don't think this case really can happen */
3846                     SendToICS(ics_prefix);
3847                     SendToICS("refresh\n");
3848                 }
3849                 continue;
3850             }
3851
3852             /* Error messages */
3853 //          if (ics_user_moved) {
3854             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855                 if (looking_at(buf, &i, "Illegal move") ||
3856                     looking_at(buf, &i, "Not a legal move") ||
3857                     looking_at(buf, &i, "Your king is in check") ||
3858                     looking_at(buf, &i, "It isn't your turn") ||
3859                     looking_at(buf, &i, "It is not your move")) {
3860                     /* Illegal move */
3861                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862                         currentMove = forwardMostMove-1;
3863                         DisplayMove(currentMove - 1); /* before DMError */
3864                         DrawPosition(FALSE, boards[currentMove]);
3865                         SwitchClocks(forwardMostMove-1); // [HGM] race
3866                         DisplayBothClocks();
3867                     }
3868                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3869                     ics_user_moved = 0;
3870                     continue;
3871                 }
3872             }
3873
3874             if (looking_at(buf, &i, "still have time") ||
3875                 looking_at(buf, &i, "not out of time") ||
3876                 looking_at(buf, &i, "either player is out of time") ||
3877                 looking_at(buf, &i, "has timeseal; checking")) {
3878                 /* We must have called his flag a little too soon */
3879                 whiteFlag = blackFlag = FALSE;
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "added * seconds to") ||
3884                 looking_at(buf, &i, "seconds were added to")) {
3885                 /* Update the clocks */
3886                 SendToICS(ics_prefix);
3887                 SendToICS("refresh\n");
3888                 continue;
3889             }
3890
3891             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892                 ics_clock_paused = TRUE;
3893                 StopClocks();
3894                 continue;
3895             }
3896
3897             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898                 ics_clock_paused = FALSE;
3899                 StartClocks();
3900                 continue;
3901             }
3902
3903             /* Grab player ratings from the Creating: message.
3904                Note we have to check for the special case when
3905                the ICS inserts things like [white] or [black]. */
3906             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3908                 /* star_matches:
3909                    0    player 1 name (not necessarily white)
3910                    1    player 1 rating
3911                    2    empty, white, or black (IGNORED)
3912                    3    player 2 name (not necessarily black)
3913                    4    player 2 rating
3914
3915                    The names/ratings are sorted out when the game
3916                    actually starts (below).
3917                 */
3918                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919                 player1Rating = string_to_rating(star_match[1]);
3920                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921                 player2Rating = string_to_rating(star_match[4]);
3922
3923                 if (appData.debugMode)
3924                   fprintf(debugFP,
3925                           "Ratings from 'Creating:' %s %d, %s %d\n",
3926                           player1Name, player1Rating,
3927                           player2Name, player2Rating);
3928
3929                 continue;
3930             }
3931
3932             /* Improved generic start/end-of-game messages */
3933             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935                 /* If tkind == 0: */
3936                 /* star_match[0] is the game number */
3937                 /*           [1] is the white player's name */
3938                 /*           [2] is the black player's name */
3939                 /* For end-of-game: */
3940                 /*           [3] is the reason for the game end */
3941                 /*           [4] is a PGN end game-token, preceded by " " */
3942                 /* For start-of-game: */
3943                 /*           [3] begins with "Creating" or "Continuing" */
3944                 /*           [4] is " *" or empty (don't care). */
3945                 int gamenum = atoi(star_match[0]);
3946                 char *whitename, *blackname, *why, *endtoken;
3947                 ChessMove endtype = EndOfFile;
3948
3949                 if (tkind == 0) {
3950                   whitename = star_match[1];
3951                   blackname = star_match[2];
3952                   why = star_match[3];
3953                   endtoken = star_match[4];
3954                 } else {
3955                   whitename = star_match[1];
3956                   blackname = star_match[3];
3957                   why = star_match[5];
3958                   endtoken = star_match[6];
3959                 }
3960
3961                 /* Game start messages */
3962                 if (strncmp(why, "Creating ", 9) == 0 ||
3963                     strncmp(why, "Continuing ", 11) == 0) {
3964                     gs_gamenum = gamenum;
3965                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3968 #if ZIPPY
3969                     if (appData.zippyPlay) {
3970                         ZippyGameStart(whitename, blackname);
3971                     }
3972 #endif /*ZIPPY*/
3973                     partnerBoardValid = FALSE; // [HGM] bughouse
3974                     continue;
3975                 }
3976
3977                 /* Game end messages */
3978                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979                     ics_gamenum != gamenum) {
3980                     continue;
3981                 }
3982                 while (endtoken[0] == ' ') endtoken++;
3983                 switch (endtoken[0]) {
3984                   case '*':
3985                   default:
3986                     endtype = GameUnfinished;
3987                     break;
3988                   case '0':
3989                     endtype = BlackWins;
3990                     break;
3991                   case '1':
3992                     if (endtoken[1] == '/')
3993                       endtype = GameIsDrawn;
3994                     else
3995                       endtype = WhiteWins;
3996                     break;
3997                 }
3998                 GameEnds(endtype, why, GE_ICS);
3999 #if ZIPPY
4000                 if (appData.zippyPlay && first.initDone) {
4001                     ZippyGameEnd(endtype, why);
4002                     if (first.pr == NoProc) {
4003                       /* Start the next process early so that we'll
4004                          be ready for the next challenge */
4005                       StartChessProgram(&first);
4006                     }
4007                     /* Send "new" early, in case this command takes
4008                        a long time to finish, so that we'll be ready
4009                        for the next challenge. */
4010                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4011                     Reset(TRUE, TRUE);
4012                 }
4013 #endif /*ZIPPY*/
4014                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4015                 continue;
4016             }
4017
4018             if (looking_at(buf, &i, "Removing game * from observation") ||
4019                 looking_at(buf, &i, "no longer observing game *") ||
4020                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021                 if (gameMode == IcsObserving &&
4022                     atoi(star_match[0]) == ics_gamenum)
4023                   {
4024                       /* icsEngineAnalyze */
4025                       if (appData.icsEngineAnalyze) {
4026                             ExitAnalyzeMode();
4027                             ModeHighlight();
4028                       }
4029                       StopClocks();
4030                       gameMode = IcsIdle;
4031                       ics_gamenum = -1;
4032                       ics_user_moved = FALSE;
4033                   }
4034                 continue;
4035             }
4036
4037             if (looking_at(buf, &i, "no longer examining game *")) {
4038                 if (gameMode == IcsExamining &&
4039                     atoi(star_match[0]) == ics_gamenum)
4040                   {
4041                       gameMode = IcsIdle;
4042                       ics_gamenum = -1;
4043                       ics_user_moved = FALSE;
4044                   }
4045                 continue;
4046             }
4047
4048             /* Advance leftover_start past any newlines we find,
4049                so only partial lines can get reparsed */
4050             if (looking_at(buf, &i, "\n")) {
4051                 prevColor = curColor;
4052                 if (curColor != ColorNormal) {
4053                     if (oldi > next_out) {
4054                         SendToPlayer(&buf[next_out], oldi - next_out);
4055                         next_out = oldi;
4056                     }
4057                     Colorize(ColorNormal, FALSE);
4058                     curColor = ColorNormal;
4059                 }
4060                 if (started == STARTED_BOARD) {
4061                     started = STARTED_NONE;
4062                     parse[parse_pos] = NULLCHAR;
4063                     ParseBoard12(parse);
4064                     ics_user_moved = 0;
4065
4066                     /* Send premove here */
4067                     if (appData.premove) {
4068                       char str[MSG_SIZ];
4069                       if (currentMove == 0 &&
4070                           gameMode == IcsPlayingWhite &&
4071                           appData.premoveWhite) {
4072                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073                         if (appData.debugMode)
4074                           fprintf(debugFP, "Sending premove:\n");
4075                         SendToICS(str);
4076                       } else if (currentMove == 1 &&
4077                                  gameMode == IcsPlayingBlack &&
4078                                  appData.premoveBlack) {
4079                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080                         if (appData.debugMode)
4081                           fprintf(debugFP, "Sending premove:\n");
4082                         SendToICS(str);
4083                       } else if (gotPremove) {
4084                         gotPremove = 0;
4085                         ClearPremoveHighlights();
4086                         if (appData.debugMode)
4087                           fprintf(debugFP, "Sending premove:\n");
4088                           UserMoveEvent(premoveFromX, premoveFromY,
4089                                         premoveToX, premoveToY,
4090                                         premovePromoChar);
4091                       }
4092                     }
4093
4094                     /* Usually suppress following prompt */
4095                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097                         if (looking_at(buf, &i, "*% ")) {
4098                             savingComment = FALSE;
4099                             suppressKibitz = 0;
4100                         }
4101                     }
4102                     next_out = i;
4103                 } else if (started == STARTED_HOLDINGS) {
4104                     int gamenum;
4105                     char new_piece[MSG_SIZ];
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     if (appData.debugMode)
4109                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110                                                         parse, currentMove);
4111                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4112                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113                         if (gameInfo.variant == VariantNormal) {
4114                           /* [HGM] We seem to switch variant during a game!
4115                            * Presumably no holdings were displayed, so we have
4116                            * to move the position two files to the right to
4117                            * create room for them!
4118                            */
4119                           VariantClass newVariant;
4120                           switch(gameInfo.boardWidth) { // base guess on board width
4121                                 case 9:  newVariant = VariantShogi; break;
4122                                 case 10: newVariant = VariantGreat; break;
4123                                 default: newVariant = VariantCrazyhouse; break;
4124                           }
4125                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126                           /* Get a move list just to see the header, which
4127                              will tell us whether this is really bug or zh */
4128                           if (ics_getting_history == H_FALSE) {
4129                             ics_getting_history = H_REQUESTED;
4130                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4131                             SendToICS(str);
4132                           }
4133                         }
4134                         new_piece[0] = NULLCHAR;
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to board holdings area */
4141                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4144 #if ZIPPY
4145                         if (appData.zippyPlay && first.initDone) {
4146                             ZippyHoldings(white_holding, black_holding,
4147                                           new_piece);
4148                         }
4149 #endif /*ZIPPY*/
4150                         if (tinyLayout || smallLayout) {
4151                             char wh[16], bh[16];
4152                             PackHolding(wh, white_holding);
4153                             PackHolding(bh, black_holding);
4154                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155                                     gameInfo.white, gameInfo.black);
4156                         } else {
4157                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158                                     gameInfo.white, white_holding, _("vs."),
4159                                     gameInfo.black, black_holding);
4160                         }
4161                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162                         DrawPosition(FALSE, boards[currentMove]);
4163                         DisplayTitle(str);
4164                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165                         sscanf(parse, "game %d white [%s black [%s <- %s",
4166                                &gamenum, white_holding, black_holding,
4167                                new_piece);
4168                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4169                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4170                         /* [HGM] copy holdings to partner-board holdings area */
4171                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4176                       }
4177                     }
4178                     /* Suppress following prompt */
4179                     if (looking_at(buf, &i, "*% ")) {
4180                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181                         savingComment = FALSE;
4182                         suppressKibitz = 0;
4183                     }
4184                     next_out = i;
4185                 }
4186                 continue;
4187             }
4188
4189             i++;                /* skip unparsed character and loop back */
4190         }
4191
4192         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 //          SendToPlayer(&buf[next_out], i - next_out);
4195             started != STARTED_HOLDINGS && leftover_start > next_out) {
4196             SendToPlayer(&buf[next_out], leftover_start - next_out);
4197             next_out = i;
4198         }
4199
4200         leftover_len = buf_len - leftover_start;
4201         /* if buffer ends with something we couldn't parse,
4202            reparse it after appending the next read */
4203
4204     } else if (count == 0) {
4205         RemoveInputSource(isr);
4206         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4207     } else {
4208         DisplayFatalError(_("Error reading from ICS"), error, 1);
4209     }
4210 }
4211
4212
4213 /* Board style 12 looks like this:
4214
4215    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4216
4217  * The "<12> " is stripped before it gets to this routine.  The two
4218  * trailing 0's (flip state and clock ticking) are later addition, and
4219  * some chess servers may not have them, or may have only the first.
4220  * Additional trailing fields may be added in the future.
4221  */
4222
4223 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4224
4225 #define RELATION_OBSERVING_PLAYED    0
4226 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE      1
4228 #define RELATION_PLAYING_NOTMYMOVE  -1
4229 #define RELATION_EXAMINING           2
4230 #define RELATION_ISOLATED_BOARD     -3
4231 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4232
4233 void
4234 ParseBoard12 (char *string)
4235 {
4236 #if ZIPPY
4237     int i, takeback;
4238     char *bookHit = NULL; // [HGM] book
4239 #endif
4240     GameMode newGameMode;
4241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244     char to_play, board_chars[200];
4245     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246     char black[32], white[32];
4247     Board board;
4248     int prevMove = currentMove;
4249     int ticking = 2;
4250     ChessMove moveType;
4251     int fromX, fromY, toX, toY;
4252     char promoChar;
4253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254     Boolean weird = FALSE, reqFlag = FALSE;
4255
4256     fromX = fromY = toX = toY = -1;
4257
4258     newGame = FALSE;
4259
4260     if (appData.debugMode)
4261       fprintf(debugFP, "Parsing board: %s\n", string);
4262
4263     move_str[0] = NULLCHAR;
4264     elapsed_time[0] = NULLCHAR;
4265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4266         int  i = 0, j;
4267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268             if(string[i] == ' ') { ranks++; files = 0; }
4269             else files++;
4270             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4271             i++;
4272         }
4273         for(j = 0; j <i; j++) board_chars[j] = string[j];
4274         board_chars[i] = '\0';
4275         string += i + 1;
4276     }
4277     n = sscanf(string, PATTERN, &to_play, &double_push,
4278                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279                &gamenum, white, black, &relation, &basetime, &increment,
4280                &white_stren, &black_stren, &white_time, &black_time,
4281                &moveNum, str, elapsed_time, move_str, &ics_flip,
4282                &ticking);
4283
4284     if (n < 21) {
4285         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286         DisplayError(str, 0);
4287         return;
4288     }
4289
4290     /* Convert the move number to internal form */
4291     moveNum = (moveNum - 1) * 2;
4292     if (to_play == 'B') moveNum++;
4293     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4295                         0, 1);
4296       return;
4297     }
4298
4299     switch (relation) {
4300       case RELATION_OBSERVING_PLAYED:
4301       case RELATION_OBSERVING_STATIC:
4302         if (gamenum == -1) {
4303             /* Old ICC buglet */
4304             relation = RELATION_OBSERVING_STATIC;
4305         }
4306         newGameMode = IcsObserving;
4307         break;
4308       case RELATION_PLAYING_MYMOVE:
4309       case RELATION_PLAYING_NOTMYMOVE:
4310         newGameMode =
4311           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312             IcsPlayingWhite : IcsPlayingBlack;
4313         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4314         break;
4315       case RELATION_EXAMINING:
4316         newGameMode = IcsExamining;
4317         break;
4318       case RELATION_ISOLATED_BOARD:
4319       default:
4320         /* Just display this board.  If user was doing something else,
4321            we will forget about it until the next board comes. */
4322         newGameMode = IcsIdle;
4323         break;
4324       case RELATION_STARTING_POSITION:
4325         newGameMode = gameMode;
4326         break;
4327     }
4328
4329     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334       static int lastBgGame = -1;
4335       char *toSqr;
4336       for (k = 0; k < ranks; k++) {
4337         for (j = 0; j < files; j++)
4338           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339         if(gameInfo.holdingsWidth > 1) {
4340              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4342         }
4343       }
4344       CopyBoard(partnerBoard, board);
4345       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349       if(toSqr = strchr(str, '-')) {
4350         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4356       if(twoBoards) {
4357           DisplayWhiteClock(white_time*fac, to_play == 'W');
4358           DisplayBlackClock(black_time*fac, to_play != 'W');
4359           activePartner = to_play;
4360           if(gamenum != lastBgGame) {
4361               char buf[MSG_SIZ];
4362               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4363               DisplayTitle(buf);
4364           }
4365           lastBgGame = gamenum;
4366           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370       if(!twoBoards) DisplayMessage(partnerStatus, "");
4371         partnerBoardValid = TRUE;
4372       return;
4373     }
4374
4375     if(appData.dualBoard && appData.bgObserve) {
4376         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377             SendToICS(ics_prefix), SendToICS("pobserve\n");
4378         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4379             char buf[MSG_SIZ];
4380             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4381             SendToICS(buf);
4382         }
4383     }
4384
4385     /* Modify behavior for initial board display on move listing
4386        of wild games.
4387        */
4388     switch (ics_getting_history) {
4389       case H_FALSE:
4390       case H_REQUESTED:
4391         break;
4392       case H_GOT_REQ_HEADER:
4393       case H_GOT_UNREQ_HEADER:
4394         /* This is the initial position of the current game */
4395         gamenum = ics_gamenum;
4396         moveNum = 0;            /* old ICS bug workaround */
4397         if (to_play == 'B') {
4398           startedFromSetupPosition = TRUE;
4399           blackPlaysFirst = TRUE;
4400           moveNum = 1;
4401           if (forwardMostMove == 0) forwardMostMove = 1;
4402           if (backwardMostMove == 0) backwardMostMove = 1;
4403           if (currentMove == 0) currentMove = 1;
4404         }
4405         newGameMode = gameMode;
4406         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4407         break;
4408       case H_GOT_UNWANTED_HEADER:
4409         /* This is an initial board that we don't want */
4410         return;
4411       case H_GETTING_MOVES:
4412         /* Should not happen */
4413         DisplayError(_("Error gathering move list: extra board"), 0);
4414         ics_getting_history = H_FALSE;
4415         return;
4416     }
4417
4418    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4421      /* [HGM] We seem to have switched variant unexpectedly
4422       * Try to guess new variant from board size
4423       */
4424           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4429           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432           /* Get a move list just to see the header, which
4433              will tell us whether this is really bug or zh */
4434           if (ics_getting_history == H_FALSE) {
4435             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437             SendToICS(str);
4438           }
4439     }
4440
4441     /* Take action if this is the first board of a new game, or of a
4442        different game than is currently being displayed.  */
4443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444         relation == RELATION_ISOLATED_BOARD) {
4445
4446         /* Forget the old game and get the history (if any) of the new one */
4447         if (gameMode != BeginningOfGame) {
4448           Reset(TRUE, TRUE);
4449         }
4450         newGame = TRUE;
4451         if (appData.autoRaiseBoard) BoardToTop();
4452         prevMove = -3;
4453         if (gamenum == -1) {
4454             newGameMode = IcsIdle;
4455         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456                    appData.getMoveList && !reqFlag) {
4457             /* Need to get game history */
4458             ics_getting_history = H_REQUESTED;
4459             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4460             SendToICS(str);
4461         }
4462
4463         /* Initially flip the board to have black on the bottom if playing
4464            black or if the ICS flip flag is set, but let the user change
4465            it with the Flip View button. */
4466         flipView = appData.autoFlipView ?
4467           (newGameMode == IcsPlayingBlack) || ics_flip :
4468           appData.flipView;
4469
4470         /* Done with values from previous mode; copy in new ones */
4471         gameMode = newGameMode;
4472         ModeHighlight();
4473         ics_gamenum = gamenum;
4474         if (gamenum == gs_gamenum) {
4475             int klen = strlen(gs_kind);
4476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478             gameInfo.event = StrSave(str);
4479         } else {
4480             gameInfo.event = StrSave("ICS game");
4481         }
4482         gameInfo.site = StrSave(appData.icsHost);
4483         gameInfo.date = PGNDate();
4484         gameInfo.round = StrSave("-");
4485         gameInfo.white = StrSave(white);
4486         gameInfo.black = StrSave(black);
4487         timeControl = basetime * 60 * 1000;
4488         timeControl_2 = 0;
4489         timeIncrement = increment * 1000;
4490         movesPerSession = 0;
4491         gameInfo.timeControl = TimeControlTagValue();
4492         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496     setbuf(debugFP, NULL);
4497   }
4498
4499         gameInfo.outOfBook = NULL;
4500
4501         /* Do we have the ratings? */
4502         if (strcmp(player1Name, white) == 0 &&
4503             strcmp(player2Name, black) == 0) {
4504             if (appData.debugMode)
4505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506                       player1Rating, player2Rating);
4507             gameInfo.whiteRating = player1Rating;
4508             gameInfo.blackRating = player2Rating;
4509         } else if (strcmp(player2Name, white) == 0 &&
4510                    strcmp(player1Name, black) == 0) {
4511             if (appData.debugMode)
4512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513                       player2Rating, player1Rating);
4514             gameInfo.whiteRating = player2Rating;
4515             gameInfo.blackRating = player1Rating;
4516         }
4517         player1Name[0] = player2Name[0] = NULLCHAR;
4518
4519         /* Silence shouts if requested */
4520         if (appData.quietPlay &&
4521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522             SendToICS(ics_prefix);
4523             SendToICS("set shout 0\n");
4524         }
4525     }
4526
4527     /* Deal with midgame name changes */
4528     if (!newGame) {
4529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530             if (gameInfo.white) free(gameInfo.white);
4531             gameInfo.white = StrSave(white);
4532         }
4533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534             if (gameInfo.black) free(gameInfo.black);
4535             gameInfo.black = StrSave(black);
4536         }
4537     }
4538
4539     /* Throw away game result if anything actually changes in examine mode */
4540     if (gameMode == IcsExamining && !newGame) {
4541         gameInfo.result = GameUnfinished;
4542         if (gameInfo.resultDetails != NULL) {
4543             free(gameInfo.resultDetails);
4544             gameInfo.resultDetails = NULL;
4545         }
4546     }
4547
4548     /* In pausing && IcsExamining mode, we ignore boards coming
4549        in if they are in a different variation than we are. */
4550     if (pauseExamInvalid) return;
4551     if (pausing && gameMode == IcsExamining) {
4552         if (moveNum <= pauseExamForwardMostMove) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557     }
4558
4559   if (appData.debugMode) {
4560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4561   }
4562     /* Parse the board */
4563     for (k = 0; k < ranks; k++) {
4564       for (j = 0; j < files; j++)
4565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566       if(gameInfo.holdingsWidth > 1) {
4567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4569       }
4570     }
4571     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572       board[5][BOARD_RGHT+1] = WhiteAngel;
4573       board[6][BOARD_RGHT+1] = WhiteMarshall;
4574       board[1][0] = BlackMarshall;
4575       board[2][0] = BlackAngel;
4576       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4577     }
4578     CopyBoard(boards[moveNum], board);
4579     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4580     if (moveNum == 0) {
4581         startedFromSetupPosition =
4582           !CompareBoards(board, initialPosition);
4583         if(startedFromSetupPosition)
4584             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4585     }
4586
4587     /* [HGM] Set castling rights. Take the outermost Rooks,
4588        to make it also work for FRC opening positions. Note that board12
4589        is really defective for later FRC positions, as it has no way to
4590        indicate which Rook can castle if they are on the same side of King.
4591        For the initial position we grant rights to the outermost Rooks,
4592        and remember thos rights, and we then copy them on positions
4593        later in an FRC game. This means WB might not recognize castlings with
4594        Rooks that have moved back to their original position as illegal,
4595        but in ICS mode that is not its job anyway.
4596     */
4597     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4599
4600         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601             if(board[0][i] == WhiteRook) j = i;
4602         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604             if(board[0][i] == WhiteRook) j = i;
4605         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4612
4613         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618             if(board[BOARD_HEIGHT-1][k] == bKing)
4619                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620         if(gameInfo.variant == VariantTwoKings) {
4621             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4624         }
4625     } else { int r;
4626         r = boards[moveNum][CASTLING][0] = initialRights[0];
4627         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628         r = boards[moveNum][CASTLING][1] = initialRights[1];
4629         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630         r = boards[moveNum][CASTLING][3] = initialRights[3];
4631         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632         r = boards[moveNum][CASTLING][4] = initialRights[4];
4633         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634         /* wildcastle kludge: always assume King has rights */
4635         r = boards[moveNum][CASTLING][2] = initialRights[2];
4636         r = boards[moveNum][CASTLING][5] = initialRights[5];
4637     }
4638     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639     boards[moveNum][EP_STATUS] = EP_NONE;
4640     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4643
4644
4645     if (ics_getting_history == H_GOT_REQ_HEADER ||
4646         ics_getting_history == H_GOT_UNREQ_HEADER) {
4647         /* This was an initial position from a move list, not
4648            the current position */
4649         return;
4650     }
4651
4652     /* Update currentMove and known move number limits */
4653     newMove = newGame || moveNum > forwardMostMove;
4654
4655     if (newGame) {
4656         forwardMostMove = backwardMostMove = currentMove = moveNum;
4657         if (gameMode == IcsExamining && moveNum == 0) {
4658           /* Workaround for ICS limitation: we are not told the wild
4659              type when starting to examine a game.  But if we ask for
4660              the move list, the move list header will tell us */
4661             ics_getting_history = H_REQUESTED;
4662             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4663             SendToICS(str);
4664         }
4665     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4667 #if ZIPPY
4668         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669         /* [HGM] applied this also to an engine that is silently watching        */
4670         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672             gameInfo.variant == currentlyInitializedVariant) {
4673           takeback = forwardMostMove - moveNum;
4674           for (i = 0; i < takeback; i++) {
4675             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676             SendToProgram("undo\n", &first);
4677           }
4678         }
4679 #endif
4680
4681         forwardMostMove = moveNum;
4682         if (!pausing || currentMove > forwardMostMove)
4683           currentMove = forwardMostMove;
4684     } else {
4685         /* New part of history that is not contiguous with old part */
4686         if (pausing && gameMode == IcsExamining) {
4687             pauseExamInvalid = TRUE;
4688             forwardMostMove = pauseExamForwardMostMove;
4689             return;
4690         }
4691         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4692 #if ZIPPY
4693             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694                 // [HGM] when we will receive the move list we now request, it will be
4695                 // fed to the engine from the first move on. So if the engine is not
4696                 // in the initial position now, bring it there.
4697                 InitChessProgram(&first, 0);
4698             }
4699 #endif
4700             ics_getting_history = H_REQUESTED;
4701             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4702             SendToICS(str);
4703         }
4704         forwardMostMove = backwardMostMove = currentMove = moveNum;
4705     }
4706
4707     /* Update the clocks */
4708     if (strchr(elapsed_time, '.')) {
4709       /* Time is in ms */
4710       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4712     } else {
4713       /* Time is in seconds */
4714       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4716     }
4717
4718
4719 #if ZIPPY
4720     if (appData.zippyPlay && newGame &&
4721         gameMode != IcsObserving && gameMode != IcsIdle &&
4722         gameMode != IcsExamining)
4723       ZippyFirstBoard(moveNum, basetime, increment);
4724 #endif
4725
4726     /* Put the move on the move list, first converting
4727        to canonical algebraic form. */
4728     if (moveNum > 0) {
4729   if (appData.debugMode) {
4730     int f = forwardMostMove;
4731     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735     fprintf(debugFP, "moveNum = %d\n", moveNum);
4736     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737     setbuf(debugFP, NULL);
4738   }
4739         if (moveNum <= backwardMostMove) {
4740             /* We don't know what the board looked like before
4741                this move.  Punt. */
4742           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746         } else if (strcmp(move_str, "none") == 0) {
4747             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748             /* Again, we don't know what the board looked like;
4749                this is really the start of the game. */
4750             parseList[moveNum - 1][0] = NULLCHAR;
4751             moveList[moveNum - 1][0] = NULLCHAR;
4752             backwardMostMove = moveNum;
4753             startedFromSetupPosition = TRUE;
4754             fromX = fromY = toX = toY = -1;
4755         } else {
4756           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757           //                 So we parse the long-algebraic move string in stead of the SAN move
4758           int valid; char buf[MSG_SIZ], *prom;
4759
4760           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762           // str looks something like "Q/a1-a2"; kill the slash
4763           if(str[1] == '/')
4764             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767                 strcat(buf, prom); // long move lacks promo specification!
4768           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769                 if(appData.debugMode)
4770                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771                 safeStrCpy(move_str, buf, MSG_SIZ);
4772           }
4773           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774                                 &fromX, &fromY, &toX, &toY, &promoChar)
4775                || ParseOneMove(buf, moveNum - 1, &moveType,
4776                                 &fromX, &fromY, &toX, &toY, &promoChar);
4777           // end of long SAN patch
4778           if (valid) {
4779             (void) CoordsToAlgebraic(boards[moveNum - 1],
4780                                      PosFlags(moveNum - 1),
4781                                      fromY, fromX, toY, toX, promoChar,
4782                                      parseList[moveNum-1]);
4783             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4784               case MT_NONE:
4785               case MT_STALEMATE:
4786               default:
4787                 break;
4788               case MT_CHECK:
4789                 if(gameInfo.variant != VariantShogi)
4790                     strcat(parseList[moveNum - 1], "+");
4791                 break;
4792               case MT_CHECKMATE:
4793               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794                 strcat(parseList[moveNum - 1], "#");
4795                 break;
4796             }
4797             strcat(parseList[moveNum - 1], " ");
4798             strcat(parseList[moveNum - 1], elapsed_time);
4799             /* currentMoveString is set as a side-effect of ParseOneMove */
4800             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802             strcat(moveList[moveNum - 1], "\n");
4803
4804             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807                 ChessSquare old, new = boards[moveNum][k][j];
4808                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810                   if(old == new) continue;
4811                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812                   else if(new == WhiteWazir || new == BlackWazir) {
4813                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815                       else boards[moveNum][k][j] = old; // preserve type of Gold
4816                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4818               }
4819           } else {
4820             /* Move from ICS was illegal!?  Punt. */
4821             if (appData.debugMode) {
4822               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4824             }
4825             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826             strcat(parseList[moveNum - 1], " ");
4827             strcat(parseList[moveNum - 1], elapsed_time);
4828             moveList[moveNum - 1][0] = NULLCHAR;
4829             fromX = fromY = toX = toY = -1;
4830           }
4831         }
4832   if (appData.debugMode) {
4833     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834     setbuf(debugFP, NULL);
4835   }
4836
4837 #if ZIPPY
4838         /* Send move to chess program (BEFORE animating it). */
4839         if (appData.zippyPlay && !newGame && newMove &&
4840            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4841
4842             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4846                             move_str);
4847                     DisplayError(str, 0);
4848                 } else {
4849                     if (first.sendTime) {
4850                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4851                     }
4852                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853                     if (firstMove && !bookHit) {
4854                         firstMove = FALSE;
4855                         if (first.useColors) {
4856                           SendToProgram(gameMode == IcsPlayingWhite ?
4857                                         "white\ngo\n" :
4858                                         "black\ngo\n", &first);
4859                         } else {
4860                           SendToProgram("go\n", &first);
4861                         }
4862                         first.maybeThinking = TRUE;
4863                     }
4864                 }
4865             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866               if (moveList[moveNum - 1][0] == NULLCHAR) {
4867                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868                 DisplayError(str, 0);
4869               } else {
4870                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871                 SendMoveToProgram(moveNum - 1, &first);
4872               }
4873             }
4874         }
4875 #endif
4876     }
4877
4878     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879         /* If move comes from a remote source, animate it.  If it
4880            isn't remote, it will have already been animated. */
4881         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4883         }
4884         if (!pausing && appData.highlightLastMove) {
4885             SetHighlights(fromX, fromY, toX, toY);
4886         }
4887     }
4888
4889     /* Start the clocks */
4890     whiteFlag = blackFlag = FALSE;
4891     appData.clockMode = !(basetime == 0 && increment == 0);
4892     if (ticking == 0) {
4893       ics_clock_paused = TRUE;
4894       StopClocks();
4895     } else if (ticking == 1) {
4896       ics_clock_paused = FALSE;
4897     }
4898     if (gameMode == IcsIdle ||
4899         relation == RELATION_OBSERVING_STATIC ||
4900         relation == RELATION_EXAMINING ||
4901         ics_clock_paused)
4902       DisplayBothClocks();
4903     else
4904       StartClocks();
4905
4906     /* Display opponents and material strengths */
4907     if (gameInfo.variant != VariantBughouse &&
4908         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909         if (tinyLayout || smallLayout) {
4910             if(gameInfo.variant == VariantNormal)
4911               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4913                     basetime, increment);
4914             else
4915               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4917                     basetime, increment, (int) gameInfo.variant);
4918         } else {
4919             if(gameInfo.variant == VariantNormal)
4920               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922                     basetime, increment);
4923             else
4924               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926                     basetime, increment, VariantName(gameInfo.variant));
4927         }
4928         DisplayTitle(str);
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4931   }
4932     }
4933
4934
4935     /* Display the board */
4936     if (!pausing && !appData.noGUI) {
4937
4938       if (appData.premove)
4939           if (!gotPremove ||
4940              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942               ClearPremoveHighlights();
4943
4944       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946       DrawPosition(j, boards[currentMove]);
4947
4948       DisplayMove(moveNum - 1);
4949       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4952         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4953       }
4954     }
4955
4956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4957 #if ZIPPY
4958     if(bookHit) { // [HGM] book: simulate book reply
4959         static char bookMove[MSG_SIZ]; // a bit generous?
4960
4961         programStats.nodes = programStats.depth = programStats.time =
4962         programStats.score = programStats.got_only_move = 0;
4963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4964
4965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966         strcat(bookMove, bookHit);
4967         HandleMachineMove(bookMove, &first);
4968     }
4969 #endif
4970 }
4971
4972 void
4973 GetMoveListEvent ()
4974 {
4975     char buf[MSG_SIZ];
4976     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977         ics_getting_history = H_REQUESTED;
4978         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4979         SendToICS(buf);
4980     }
4981 }
4982
4983 void
4984 SendToBoth (char *msg)
4985 {   // to make it easy to keep two engines in step in dual analysis
4986     SendToProgram(msg, &first);
4987     if(second.analyzing) SendToProgram(msg, &second);
4988 }
4989
4990 void
4991 AnalysisPeriodicEvent (int force)
4992 {
4993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994          && !force) || !appData.periodicUpdates)
4995       return;
4996
4997     /* Send . command to Crafty to collect stats */
4998     SendToBoth(".\n");
4999
5000     /* Don't send another until we get a response (this makes
5001        us stop sending to old Crafty's which don't understand
5002        the "." command (sending illegal cmds resets node count & time,
5003        which looks bad)) */
5004     programStats.ok_to_send = 0;
5005 }
5006
5007 void
5008 ics_update_width (int new_width)
5009 {
5010         ics_printf("set width %d\n", new_width);
5011 }
5012
5013 void
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5015 {
5016     char buf[MSG_SIZ];
5017
5018     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019         // null move in variant where engine does not understand it (for analysis purposes)
5020         SendBoard(cps, moveNum + 1); // send position after move in stead.
5021         return;
5022     }
5023     if (cps->useUsermove) {
5024       SendToProgram("usermove ", cps);
5025     }
5026     if (cps->useSAN) {
5027       char *space;
5028       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5029         int len = space - parseList[moveNum];
5030         memcpy(buf, parseList[moveNum], len);
5031         buf[len++] = '\n';
5032         buf[len] = NULLCHAR;
5033       } else {
5034         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5035       }
5036       SendToProgram(buf, cps);
5037     } else {
5038       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5039         AlphaRank(moveList[moveNum], 4);
5040         SendToProgram(moveList[moveNum], cps);
5041         AlphaRank(moveList[moveNum], 4); // and back
5042       } else
5043       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5044        * the engine. It would be nice to have a better way to identify castle
5045        * moves here. */
5046       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5047                                                                          && cps->useOOCastle) {
5048         int fromX = moveList[moveNum][0] - AAA;
5049         int fromY = moveList[moveNum][1] - ONE;
5050         int toX = moveList[moveNum][2] - AAA;
5051         int toY = moveList[moveNum][3] - ONE;
5052         if((boards[moveNum][fromY][fromX] == WhiteKing
5053             && boards[moveNum][toY][toX] == WhiteRook)
5054            || (boards[moveNum][fromY][fromX] == BlackKing
5055                && boards[moveNum][toY][toX] == BlackRook)) {
5056           if(toX > fromX) SendToProgram("O-O\n", cps);
5057           else SendToProgram("O-O-O\n", cps);
5058         }
5059         else SendToProgram(moveList[moveNum], cps);
5060       } else
5061       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5062         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5063           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5064           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5065                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066         } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5071         } else
5072           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5073                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074         SendToProgram(buf, cps);
5075       }
5076       else SendToProgram(moveList[moveNum], cps);
5077       /* End of additions by Tord */
5078     }
5079
5080     /* [HGM] setting up the opening has brought engine in force mode! */
5081     /*       Send 'go' if we are in a mode where machine should play. */
5082     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5083         (gameMode == TwoMachinesPlay   ||
5084 #if ZIPPY
5085          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5086 #endif
5087          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5088         SendToProgram("go\n", cps);
5089   if (appData.debugMode) {
5090     fprintf(debugFP, "(extra)\n");
5091   }
5092     }
5093     setboardSpoiledMachineBlack = 0;
5094 }
5095
5096 void
5097 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5098 {
5099     char user_move[MSG_SIZ];
5100     char suffix[4];
5101
5102     if(gameInfo.variant == VariantSChess && promoChar) {
5103         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5104         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5105     } else suffix[0] = NULLCHAR;
5106
5107     switch (moveType) {
5108       default:
5109         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5110                 (int)moveType, fromX, fromY, toX, toY);
5111         DisplayError(user_move + strlen("say "), 0);
5112         break;
5113       case WhiteKingSideCastle:
5114       case BlackKingSideCastle:
5115       case WhiteQueenSideCastleWild:
5116       case BlackQueenSideCastleWild:
5117       /* PUSH Fabien */
5118       case WhiteHSideCastleFR:
5119       case BlackHSideCastleFR:
5120       /* POP Fabien */
5121         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5122         break;
5123       case WhiteQueenSideCastle:
5124       case BlackQueenSideCastle:
5125       case WhiteKingSideCastleWild:
5126       case BlackKingSideCastleWild:
5127       /* PUSH Fabien */
5128       case WhiteASideCastleFR:
5129       case BlackASideCastleFR:
5130       /* POP Fabien */
5131         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5132         break;
5133       case WhiteNonPromotion:
5134       case BlackNonPromotion:
5135         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5136         break;
5137       case WhitePromotion:
5138       case BlackPromotion:
5139         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5140            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5141           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143                 PieceToChar(WhiteFerz));
5144         else if(gameInfo.variant == VariantGreat)
5145           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5146                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5147                 PieceToChar(WhiteMan));
5148         else
5149           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5151                 promoChar);
5152         break;
5153       case WhiteDrop:
5154       case BlackDrop:
5155       drop:
5156         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5157                  ToUpper(PieceToChar((ChessSquare) fromX)),
5158                  AAA + toX, ONE + toY);
5159         break;
5160       case IllegalMove:  /* could be a variant we don't quite understand */
5161         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5162       case NormalMove:
5163       case WhiteCapturesEnPassant:
5164       case BlackCapturesEnPassant:
5165         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5166                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5167         break;
5168     }
5169     SendToICS(user_move);
5170     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5171         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5172 }
5173
5174 void
5175 UploadGameEvent ()
5176 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5177     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5178     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5179     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5180       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5181       return;
5182     }
5183     if(gameMode != IcsExamining) { // is this ever not the case?
5184         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5185
5186         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5187           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5188         } else { // on FICS we must first go to general examine mode
5189           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5190         }
5191         if(gameInfo.variant != VariantNormal) {
5192             // try figure out wild number, as xboard names are not always valid on ICS
5193             for(i=1; i<=36; i++) {
5194               snprintf(buf, MSG_SIZ, "wild/%d", i);
5195                 if(StringToVariant(buf) == gameInfo.variant) break;
5196             }
5197             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5198             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5199             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5200         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5201         SendToICS(ics_prefix);
5202         SendToICS(buf);
5203         if(startedFromSetupPosition || backwardMostMove != 0) {
5204           fen = PositionToFEN(backwardMostMove, NULL, 1);
5205           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5206             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5207             SendToICS(buf);
5208           } else { // FICS: everything has to set by separate bsetup commands
5209             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5210             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5211             SendToICS(buf);
5212             if(!WhiteOnMove(backwardMostMove)) {
5213                 SendToICS("bsetup tomove black\n");
5214             }
5215             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5216             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5217             SendToICS(buf);
5218             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5219             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5220             SendToICS(buf);
5221             i = boards[backwardMostMove][EP_STATUS];
5222             if(i >= 0) { // set e.p.
5223               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5224                 SendToICS(buf);
5225             }
5226             bsetup++;
5227           }
5228         }
5229       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5230             SendToICS("bsetup done\n"); // switch to normal examining.
5231     }
5232     for(i = backwardMostMove; i<last; i++) {
5233         char buf[20];
5234         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5235         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5236             int len = strlen(moveList[i]);
5237             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5238             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5239         }
5240         SendToICS(buf);
5241     }
5242     SendToICS(ics_prefix);
5243     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5244 }
5245
5246 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5247
5248 void
5249 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5250 {
5251     if (rf == DROP_RANK) {
5252       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5253       sprintf(move, "%c@%c%c\n",
5254                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5255     } else {
5256         if (promoChar == 'x' || promoChar == NULLCHAR) {
5257           sprintf(move, "%c%c%c%c\n",
5258                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5259           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5260         } else {
5261             sprintf(move, "%c%c%c%c%c\n",
5262                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5263         }
5264     }
5265 }
5266
5267 void
5268 ProcessICSInitScript (FILE *f)
5269 {
5270     char buf[MSG_SIZ];
5271
5272     while (fgets(buf, MSG_SIZ, f)) {
5273         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5274     }
5275
5276     fclose(f);
5277 }
5278
5279
5280 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5281 static ClickType lastClickType;
5282
5283 void
5284 Sweep (int step)
5285 {
5286     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5287     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5288     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5289     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5290     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5291     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5292     do {
5293         promoSweep -= step;
5294         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5295         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5296         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5297         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5298         if(!step) step = -1;
5299     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5300             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5301             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5302     if(toX >= 0) {
5303         int victim = boards[currentMove][toY][toX];
5304         boards[currentMove][toY][toX] = promoSweep;
5305         DrawPosition(FALSE, boards[currentMove]);
5306         boards[currentMove][toY][toX] = victim;
5307     } else
5308     ChangeDragPiece(promoSweep);
5309 }
5310
5311 int
5312 PromoScroll (int x, int y)
5313 {
5314   int step = 0;
5315
5316   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5317   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5318   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5319   if(!step) return FALSE;
5320   lastX = x; lastY = y;
5321   if((promoSweep < BlackPawn) == flipView) step = -step;
5322   if(step > 0) selectFlag = 1;
5323   if(!selectFlag) Sweep(step);
5324   return FALSE;
5325 }
5326
5327 void
5328 NextPiece (int step)
5329 {
5330     ChessSquare piece = boards[currentMove][toY][toX];
5331     do {
5332         pieceSweep -= step;
5333         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5334         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5335         if(!step) step = -1;
5336     } while(PieceToChar(pieceSweep) == '.');
5337     boards[currentMove][toY][toX] = pieceSweep;
5338     DrawPosition(FALSE, boards[currentMove]);
5339     boards[currentMove][toY][toX] = piece;
5340 }
5341 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5342 void
5343 AlphaRank (char *move, int n)
5344 {
5345 //    char *p = move, c; int x, y;
5346
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5349     }
5350
5351     if(move[1]=='*' &&
5352        move[2]>='0' && move[2]<='9' &&
5353        move[3]>='a' && move[3]<='x'    ) {
5354         move[1] = '@';
5355         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5356         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5357     } else
5358     if(move[0]>='0' && move[0]<='9' &&
5359        move[1]>='a' && move[1]<='x' &&
5360        move[2]>='0' && move[2]<='9' &&
5361        move[3]>='a' && move[3]<='x'    ) {
5362         /* input move, Shogi -> normal */
5363         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5364         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5365         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5366         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5367     } else
5368     if(move[1]=='@' &&
5369        move[3]>='0' && move[3]<='9' &&
5370        move[2]>='a' && move[2]<='x'    ) {
5371         move[1] = '*';
5372         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5373         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5374     } else
5375     if(
5376        move[0]>='a' && move[0]<='x' &&
5377        move[3]>='0' && move[3]<='9' &&
5378        move[2]>='a' && move[2]<='x'    ) {
5379          /* output move, normal -> Shogi */
5380         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5381         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5382         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5383         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5385     }
5386     if (appData.debugMode) {
5387         fprintf(debugFP, "   out = '%s'\n", move);
5388     }
5389 }
5390
5391 char yy_textstr[8000];
5392
5393 /* Parser for moves from gnuchess, ICS, or user typein box */
5394 Boolean
5395 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5396 {
5397     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5398
5399     switch (*moveType) {
5400       case WhitePromotion:
5401       case BlackPromotion:
5402       case WhiteNonPromotion:
5403       case BlackNonPromotion:
5404       case NormalMove:
5405       case WhiteCapturesEnPassant:
5406       case BlackCapturesEnPassant:
5407       case WhiteKingSideCastle:
5408       case WhiteQueenSideCastle:
5409       case BlackKingSideCastle:
5410       case BlackQueenSideCastle:
5411       case WhiteKingSideCastleWild:
5412       case WhiteQueenSideCastleWild:
5413       case BlackKingSideCastleWild:
5414       case BlackQueenSideCastleWild:
5415       /* Code added by Tord: */
5416       case WhiteHSideCastleFR:
5417       case WhiteASideCastleFR:
5418       case BlackHSideCastleFR:
5419       case BlackASideCastleFR:
5420       /* End of code added by Tord */
5421       case IllegalMove:         /* bug or odd chess variant */
5422         *fromX = currentMoveString[0] - AAA;
5423         *fromY = currentMoveString[1] - ONE;
5424         *toX = currentMoveString[2] - AAA;
5425         *toY = currentMoveString[3] - ONE;
5426         *promoChar = currentMoveString[4];
5427         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5428             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5429     if (appData.debugMode) {
5430         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5431     }
5432             *fromX = *fromY = *toX = *toY = 0;
5433             return FALSE;
5434         }
5435         if (appData.testLegality) {
5436           return (*moveType != IllegalMove);
5437         } else {
5438           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5439                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5440         }
5441
5442       case WhiteDrop:
5443       case BlackDrop:
5444         *fromX = *moveType == WhiteDrop ?
5445           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5446           (int) CharToPiece(ToLower(currentMoveString[0]));
5447         *fromY = DROP_RANK;
5448         *toX = currentMoveString[2] - AAA;
5449         *toY = currentMoveString[3] - ONE;
5450         *promoChar = NULLCHAR;
5451         return TRUE;
5452
5453       case AmbiguousMove:
5454       case ImpossibleMove:
5455       case EndOfFile:
5456       case ElapsedTime:
5457       case Comment:
5458       case PGNTag:
5459       case NAG:
5460       case WhiteWins:
5461       case BlackWins:
5462       case GameIsDrawn:
5463       default:
5464     if (appData.debugMode) {
5465         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5466     }
5467         /* bug? */
5468         *fromX = *fromY = *toX = *toY = 0;
5469         *promoChar = NULLCHAR;
5470         return FALSE;
5471     }
5472 }
5473
5474 Boolean pushed = FALSE;
5475 char *lastParseAttempt;
5476
5477 void
5478 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5479 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5480   int fromX, fromY, toX, toY; char promoChar;
5481   ChessMove moveType;
5482   Boolean valid;
5483   int nr = 0;
5484
5485   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5486   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5487     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5488     pushed = TRUE;
5489   }
5490   endPV = forwardMostMove;
5491   do {
5492     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5493     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5494     lastParseAttempt = pv;
5495     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5496     if(!valid && nr == 0 &&
5497        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5498         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5499         // Hande case where played move is different from leading PV move
5500         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5501         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5502         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5503         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5504           endPV += 2; // if position different, keep this
5505           moveList[endPV-1][0] = fromX + AAA;
5506           moveList[endPV-1][1] = fromY + ONE;
5507           moveList[endPV-1][2] = toX + AAA;
5508           moveList[endPV-1][3] = toY + ONE;
5509           parseList[endPV-1][0] = NULLCHAR;
5510           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5511         }
5512       }
5513     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5514     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5515     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5516     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5517         valid++; // allow comments in PV
5518         continue;
5519     }
5520     nr++;
5521     if(endPV+1 > framePtr) break; // no space, truncate
5522     if(!valid) break;
5523     endPV++;
5524     CopyBoard(boards[endPV], boards[endPV-1]);
5525     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5526     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5527     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5528     CoordsToAlgebraic(boards[endPV - 1],
5529                              PosFlags(endPV - 1),
5530                              fromY, fromX, toY, toX, promoChar,
5531                              parseList[endPV - 1]);
5532   } while(valid);
5533   if(atEnd == 2) return; // used hidden, for PV conversion
5534   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5535   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5536   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5537                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5538   DrawPosition(TRUE, boards[currentMove]);
5539 }
5540
5541 int
5542 MultiPV (ChessProgramState *cps)
5543 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5544         int i;
5545         for(i=0; i<cps->nrOptions; i++)
5546             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5547                 return i;
5548         return -1;
5549 }
5550
5551 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5552
5553 Boolean
5554 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5555 {
5556         int startPV, multi, lineStart, origIndex = index;
5557         char *p, buf2[MSG_SIZ];
5558         ChessProgramState *cps = (pane ? &second : &first);
5559
5560         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5561         lastX = x; lastY = y;
5562         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5563         lineStart = startPV = index;
5564         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5565         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5566         index = startPV;
5567         do{ while(buf[index] && buf[index] != '\n') index++;
5568         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5569         buf[index] = 0;
5570         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5571                 int n = cps->option[multi].value;
5572                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5573                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5574                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5575                 cps->option[multi].value = n;
5576                 *start = *end = 0;
5577                 return FALSE;
5578         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5579                 ExcludeClick(origIndex - lineStart);
5580                 return FALSE;
5581         }
5582         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5583         *start = startPV; *end = index-1;
5584         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5585         return TRUE;
5586 }
5587
5588 char *
5589 PvToSAN (char *pv)
5590 {
5591         static char buf[10*MSG_SIZ];
5592         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5593         *buf = NULLCHAR;
5594         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5595         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5596         for(i = forwardMostMove; i<endPV; i++){
5597             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5598             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5599             k += strlen(buf+k);
5600         }
5601         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5602         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5603         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5604         endPV = savedEnd;
5605         return buf;
5606 }
5607
5608 Boolean
5609 LoadPV (int x, int y)
5610 { // called on right mouse click to load PV
5611   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5612   lastX = x; lastY = y;
5613   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5614   extendGame = FALSE;
5615   return TRUE;
5616 }
5617
5618 void
5619 UnLoadPV ()
5620 {
5621   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5622   if(endPV < 0) return;
5623   if(appData.autoCopyPV) CopyFENToClipboard();
5624   endPV = -1;
5625   if(extendGame && currentMove > forwardMostMove) {
5626         Boolean saveAnimate = appData.animate;
5627         if(pushed) {
5628             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5629                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5630             } else storedGames--; // abandon shelved tail of original game
5631         }
5632         pushed = FALSE;
5633         forwardMostMove = currentMove;
5634         currentMove = oldFMM;
5635         appData.animate = FALSE;
5636         ToNrEvent(forwardMostMove);
5637         appData.animate = saveAnimate;
5638   }
5639   currentMove = forwardMostMove;
5640   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5641   ClearPremoveHighlights();
5642   DrawPosition(TRUE, boards[currentMove]);
5643 }
5644
5645 void
5646 MovePV (int x, int y, int h)
5647 { // step through PV based on mouse coordinates (called on mouse move)
5648   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5649
5650   // we must somehow check if right button is still down (might be released off board!)
5651   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5652   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5653   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5654   if(!step) return;
5655   lastX = x; lastY = y;
5656
5657   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5658   if(endPV < 0) return;
5659   if(y < margin) step = 1; else
5660   if(y > h - margin) step = -1;
5661   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5662   currentMove += step;
5663   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5664   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5665                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5666   DrawPosition(FALSE, boards[currentMove]);
5667 }
5668
5669
5670 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5671 // All positions will have equal probability, but the current method will not provide a unique
5672 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5673 #define DARK 1
5674 #define LITE 2
5675 #define ANY 3
5676
5677 int squaresLeft[4];
5678 int piecesLeft[(int)BlackPawn];
5679 int seed, nrOfShuffles;
5680
5681 void
5682 GetPositionNumber ()
5683 {       // sets global variable seed
5684         int i;
5685
5686         seed = appData.defaultFrcPosition;
5687         if(seed < 0) { // randomize based on time for negative FRC position numbers
5688                 for(i=0; i<50; i++) seed += random();
5689                 seed = random() ^ random() >> 8 ^ random() << 8;
5690                 if(seed<0) seed = -seed;
5691         }
5692 }
5693
5694 int
5695 put (Board board, int pieceType, int rank, int n, int shade)
5696 // put the piece on the (n-1)-th empty squares of the given shade
5697 {
5698         int i;
5699
5700         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5701                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5702                         board[rank][i] = (ChessSquare) pieceType;
5703                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5704                         squaresLeft[ANY]--;
5705                         piecesLeft[pieceType]--;
5706                         return i;
5707                 }
5708         }
5709         return -1;
5710 }
5711
5712
5713 void
5714 AddOnePiece (Board board, int pieceType, int rank, int shade)
5715 // calculate where the next piece goes, (any empty square), and put it there
5716 {
5717         int i;
5718
5719         i = seed % squaresLeft[shade];
5720         nrOfShuffles *= squaresLeft[shade];
5721         seed /= squaresLeft[shade];
5722         put(board, pieceType, rank, i, shade);
5723 }
5724
5725 void
5726 AddTwoPieces (Board board, int pieceType, int rank)
5727 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5728 {
5729         int i, n=squaresLeft[ANY], j=n-1, k;
5730
5731         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5732         i = seed % k;  // pick one
5733         nrOfShuffles *= k;
5734         seed /= k;
5735         while(i >= j) i -= j--;
5736         j = n - 1 - j; i += j;
5737         put(board, pieceType, rank, j, ANY);
5738         put(board, pieceType, rank, i, ANY);
5739 }
5740
5741 void
5742 SetUpShuffle (Board board, int number)
5743 {
5744         int i, p, first=1;
5745
5746         GetPositionNumber(); nrOfShuffles = 1;
5747
5748         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5749         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5750         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5751
5752         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5753
5754         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5755             p = (int) board[0][i];
5756             if(p < (int) BlackPawn) piecesLeft[p] ++;
5757             board[0][i] = EmptySquare;
5758         }
5759
5760         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5761             // shuffles restricted to allow normal castling put KRR first
5762             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5763                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5764             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5765                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5766             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5767                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5768             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5769                 put(board, WhiteRook, 0, 0, ANY);
5770             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5771         }
5772
5773         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5774             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5775             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5776                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5777                 while(piecesLeft[p] >= 2) {
5778                     AddOnePiece(board, p, 0, LITE);
5779                     AddOnePiece(board, p, 0, DARK);
5780                 }
5781                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5782             }
5783
5784         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5785             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5786             // but we leave King and Rooks for last, to possibly obey FRC restriction
5787             if(p == (int)WhiteRook) continue;
5788             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5789             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5790         }
5791
5792         // now everything is placed, except perhaps King (Unicorn) and Rooks
5793
5794         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5795             // Last King gets castling rights
5796             while(piecesLeft[(int)WhiteUnicorn]) {
5797                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5798                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5799             }
5800
5801             while(piecesLeft[(int)WhiteKing]) {
5802                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5803                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5804             }
5805
5806
5807         } else {
5808             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5809             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5810         }
5811
5812         // Only Rooks can be left; simply place them all
5813         while(piecesLeft[(int)WhiteRook]) {
5814                 i = put(board, WhiteRook, 0, 0, ANY);
5815                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5816                         if(first) {
5817                                 first=0;
5818                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5819                         }
5820                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5821                 }
5822         }
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5824             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5825         }
5826
5827         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5828 }
5829
5830 int
5831 SetCharTable (char *table, const char * map)
5832 /* [HGM] moved here from winboard.c because of its general usefulness */
5833 /*       Basically a safe strcpy that uses the last character as King */
5834 {
5835     int result = FALSE; int NrPieces;
5836
5837     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5838                     && NrPieces >= 12 && !(NrPieces&1)) {
5839         int i; /* [HGM] Accept even length from 12 to 34 */
5840
5841         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5842         for( i=0; i<NrPieces/2-1; i++ ) {
5843             table[i] = map[i];
5844             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5845         }
5846         table[(int) WhiteKing]  = map[NrPieces/2-1];
5847         table[(int) BlackKing]  = map[NrPieces-1];
5848
5849         result = TRUE;
5850     }
5851
5852     return result;
5853 }
5854
5855 void
5856 Prelude (Board board)
5857 {       // [HGM] superchess: random selection of exo-pieces
5858         int i, j, k; ChessSquare p;
5859         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5860
5861         GetPositionNumber(); // use FRC position number
5862
5863         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5864             SetCharTable(pieceToChar, appData.pieceToCharTable);
5865             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5866                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5867         }
5868
5869         j = seed%4;                 seed /= 4;
5870         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5871         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5872         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5873         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5878         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5886         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5887         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5888         put(board, exoPieces[0],    0, 0, ANY);
5889         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5890 }
5891
5892 void
5893 InitPosition (int redraw)
5894 {
5895     ChessSquare (* pieces)[BOARD_FILES];
5896     int i, j, pawnRow=1, pieceRows=1, overrule,
5897     oldx = gameInfo.boardWidth,
5898     oldy = gameInfo.boardHeight,
5899     oldh = gameInfo.holdingsWidth;
5900     static int oldv;
5901
5902     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5903
5904     /* [AS] Initialize pv info list [HGM] and game status */
5905     {
5906         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5907             pvInfoList[i].depth = 0;
5908             boards[i][EP_STATUS] = EP_NONE;
5909             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5910         }
5911
5912         initialRulePlies = 0; /* 50-move counter start */
5913
5914         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5915         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5916     }
5917
5918
5919     /* [HGM] logic here is completely changed. In stead of full positions */
5920     /* the initialized data only consist of the two backranks. The switch */
5921     /* selects which one we will use, which is than copied to the Board   */
5922     /* initialPosition, which for the rest is initialized by Pawns and    */
5923     /* empty squares. This initial position is then copied to boards[0],  */
5924     /* possibly after shuffling, so that it remains available.            */
5925
5926     gameInfo.holdingsWidth = 0; /* default board sizes */
5927     gameInfo.boardWidth    = 8;
5928     gameInfo.boardHeight   = 8;
5929     gameInfo.holdingsSize  = 0;
5930     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5931     for(i=0; i<BOARD_FILES-2; i++)
5932       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5933     initialPosition[EP_STATUS] = EP_NONE;
5934     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5935     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5936          SetCharTable(pieceNickName, appData.pieceNickNames);
5937     else SetCharTable(pieceNickName, "............");
5938     pieces = FIDEArray;
5939
5940     switch (gameInfo.variant) {
5941     case VariantFischeRandom:
5942       shuffleOpenings = TRUE;
5943     default:
5944       break;
5945     case VariantShatranj:
5946       pieces = ShatranjArray;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5949       break;
5950     case VariantMakruk:
5951       pieces = makrukArray;
5952       nrCastlingRights = 0;
5953       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5954       break;
5955     case VariantASEAN:
5956       pieces = aseanArray;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5959       break;
5960     case VariantTwoKings:
5961       pieces = twoKingsArray;
5962       break;
5963     case VariantGrand:
5964       pieces = GrandArray;
5965       nrCastlingRights = 0;
5966       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5967       gameInfo.boardWidth = 10;
5968       gameInfo.boardHeight = 10;
5969       gameInfo.holdingsSize = 7;
5970       break;
5971     case VariantCapaRandom:
5972       shuffleOpenings = TRUE;
5973     case VariantCapablanca:
5974       pieces = CapablancaArray;
5975       gameInfo.boardWidth = 10;
5976       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5977       break;
5978     case VariantGothic:
5979       pieces = GothicArray;
5980       gameInfo.boardWidth = 10;
5981       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5982       break;
5983     case VariantSChess:
5984       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5985       gameInfo.holdingsSize = 7;
5986       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5987       break;
5988     case VariantJanus:
5989       pieces = JanusArray;
5990       gameInfo.boardWidth = 10;
5991       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5992       nrCastlingRights = 6;
5993         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5994         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5995         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5996         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5997         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5998         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5999       break;
6000     case VariantFalcon:
6001       pieces = FalconArray;
6002       gameInfo.boardWidth = 10;
6003       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6004       break;
6005     case VariantXiangqi:
6006       pieces = XiangqiArray;
6007       gameInfo.boardWidth  = 9;
6008       gameInfo.boardHeight = 10;
6009       nrCastlingRights = 0;
6010       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6011       break;
6012     case VariantShogi:
6013       pieces = ShogiArray;
6014       gameInfo.boardWidth  = 9;
6015       gameInfo.boardHeight = 9;
6016       gameInfo.holdingsSize = 7;
6017       nrCastlingRights = 0;
6018       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6019       break;
6020     case VariantChu:
6021       pieces = ChuArray; pieceRows = 3;
6022       gameInfo.boardWidth  = 12;
6023       gameInfo.boardHeight = 12;
6024       nrCastlingRights = 0;
6025       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6026                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6027       break;
6028     case VariantCourier:
6029       pieces = CourierArray;
6030       gameInfo.boardWidth  = 12;
6031       nrCastlingRights = 0;
6032       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6033       break;
6034     case VariantKnightmate:
6035       pieces = KnightmateArray;
6036       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6037       break;
6038     case VariantSpartan:
6039       pieces = SpartanArray;
6040       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6041       break;
6042     case VariantLion:
6043       pieces = lionArray;
6044       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6045       break;
6046     case VariantFairy:
6047       pieces = fairyArray;
6048       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6049       break;
6050     case VariantGreat:
6051       pieces = GreatArray;
6052       gameInfo.boardWidth = 10;
6053       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6054       gameInfo.holdingsSize = 8;
6055       break;
6056     case VariantSuper:
6057       pieces = FIDEArray;
6058       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6059       gameInfo.holdingsSize = 8;
6060       startedFromSetupPosition = TRUE;
6061       break;
6062     case VariantCrazyhouse:
6063     case VariantBughouse:
6064       pieces = FIDEArray;
6065       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6066       gameInfo.holdingsSize = 5;
6067       break;
6068     case VariantWildCastle:
6069       pieces = FIDEArray;
6070       /* !!?shuffle with kings guaranteed to be on d or e file */
6071       shuffleOpenings = 1;
6072       break;
6073     case VariantNoCastle:
6074       pieces = FIDEArray;
6075       nrCastlingRights = 0;
6076       /* !!?unconstrained back-rank shuffle */
6077       shuffleOpenings = 1;
6078       break;
6079     }
6080
6081     overrule = 0;
6082     if(appData.NrFiles >= 0) {
6083         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6084         gameInfo.boardWidth = appData.NrFiles;
6085     }
6086     if(appData.NrRanks >= 0) {
6087         gameInfo.boardHeight = appData.NrRanks;
6088     }
6089     if(appData.holdingsSize >= 0) {
6090         i = appData.holdingsSize;
6091         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6092         gameInfo.holdingsSize = i;
6093     }
6094     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6095     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6096         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6097
6098     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6099     if(pawnRow < 1) pawnRow = 1;
6100     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6101     if(gameInfo.variant == VariantChu) pawnRow = 3;
6102
6103     /* User pieceToChar list overrules defaults */
6104     if(appData.pieceToCharTable != NULL)
6105         SetCharTable(pieceToChar, appData.pieceToCharTable);
6106
6107     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6108
6109         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6110             s = (ChessSquare) 0; /* account holding counts in guard band */
6111         for( i=0; i<BOARD_HEIGHT; i++ )
6112             initialPosition[i][j] = s;
6113
6114         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6115         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6116         initialPosition[pawnRow][j] = WhitePawn;
6117         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6118         if(gameInfo.variant == VariantXiangqi) {
6119             if(j&1) {
6120                 initialPosition[pawnRow][j] =
6121                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6122                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6123                    initialPosition[2][j] = WhiteCannon;
6124                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6125                 }
6126             }
6127         }
6128         if(gameInfo.variant == VariantChu) {
6129              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6130                initialPosition[pawnRow+1][j] = WhiteCobra,
6131                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6132              for(i=1; i<pieceRows; i++) {
6133                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6134                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6135              }
6136         }
6137         if(gameInfo.variant == VariantGrand) {
6138             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6139                initialPosition[0][j] = WhiteRook;
6140                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6141             }
6142         }
6143         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6144     }
6145     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6146
6147             j=BOARD_LEFT+1;
6148             initialPosition[1][j] = WhiteBishop;
6149             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6150             j=BOARD_RGHT-2;
6151             initialPosition[1][j] = WhiteRook;
6152             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6153     }
6154
6155     if( nrCastlingRights == -1) {
6156         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6157         /*       This sets default castling rights from none to normal corners   */
6158         /* Variants with other castling rights must set them themselves above    */
6159         nrCastlingRights = 6;
6160
6161         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6162         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6163         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6164         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6165         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6166         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6167      }
6168
6169      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6170      if(gameInfo.variant == VariantGreat) { // promotion commoners
6171         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6172         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6173         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6174         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6175      }
6176      if( gameInfo.variant == VariantSChess ) {
6177       initialPosition[1][0] = BlackMarshall;
6178       initialPosition[2][0] = BlackAngel;
6179       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6180       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6181       initialPosition[1][1] = initialPosition[2][1] =
6182       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6183      }
6184   if (appData.debugMode) {
6185     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6186   }
6187     if(shuffleOpenings) {
6188         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6189         startedFromSetupPosition = TRUE;
6190     }
6191     if(startedFromPositionFile) {
6192       /* [HGM] loadPos: use PositionFile for every new game */
6193       CopyBoard(initialPosition, filePosition);
6194       for(i=0; i<nrCastlingRights; i++)
6195           initialRights[i] = filePosition[CASTLING][i];
6196       startedFromSetupPosition = TRUE;
6197     }
6198
6199     CopyBoard(boards[0], initialPosition);
6200
6201     if(oldx != gameInfo.boardWidth ||
6202        oldy != gameInfo.boardHeight ||
6203        oldv != gameInfo.variant ||
6204        oldh != gameInfo.holdingsWidth
6205                                          )
6206             InitDrawingSizes(-2 ,0);
6207
6208     oldv = gameInfo.variant;
6209     if (redraw)
6210       DrawPosition(TRUE, boards[currentMove]);
6211 }
6212
6213 void
6214 SendBoard (ChessProgramState *cps, int moveNum)
6215 {
6216     char message[MSG_SIZ];
6217
6218     if (cps->useSetboard) {
6219       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6220       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6221       SendToProgram(message, cps);
6222       free(fen);
6223
6224     } else {
6225       ChessSquare *bp;
6226       int i, j, left=0, right=BOARD_WIDTH;
6227       /* Kludge to set black to move, avoiding the troublesome and now
6228        * deprecated "black" command.
6229        */
6230       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6231         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6232
6233       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6234
6235       SendToProgram("edit\n", cps);
6236       SendToProgram("#\n", cps);
6237       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6238         bp = &boards[moveNum][i][left];
6239         for (j = left; j < right; j++, bp++) {
6240           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6241           if ((int) *bp < (int) BlackPawn) {
6242             if(j == BOARD_RGHT+1)
6243                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6244             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6245             if(message[0] == '+' || message[0] == '~') {
6246               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6247                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6248                         AAA + j, ONE + i);
6249             }
6250             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6251                 message[1] = BOARD_RGHT   - 1 - j + '1';
6252                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6253             }
6254             SendToProgram(message, cps);
6255           }
6256         }
6257       }
6258
6259       SendToProgram("c\n", cps);
6260       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6261         bp = &boards[moveNum][i][left];
6262         for (j = left; j < right; j++, bp++) {
6263           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6264           if (((int) *bp != (int) EmptySquare)
6265               && ((int) *bp >= (int) BlackPawn)) {
6266             if(j == BOARD_LEFT-2)
6267                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6268             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6269                     AAA + j, ONE + i);
6270             if(message[0] == '+' || message[0] == '~') {
6271               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6272                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6273                         AAA + j, ONE + i);
6274             }
6275             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6276                 message[1] = BOARD_RGHT   - 1 - j + '1';
6277                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6278             }
6279             SendToProgram(message, cps);
6280           }
6281         }
6282       }
6283
6284       SendToProgram(".\n", cps);
6285     }
6286     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6287 }
6288
6289 char exclusionHeader[MSG_SIZ];
6290 int exCnt, excludePtr;
6291 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6292 static Exclusion excluTab[200];
6293 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6294
6295 static void
6296 WriteMap (int s)
6297 {
6298     int j;
6299     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6300     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6301 }
6302
6303 static void
6304 ClearMap ()
6305 {
6306     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6307     excludePtr = 24; exCnt = 0;
6308     WriteMap(0);
6309 }
6310
6311 static void
6312 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6313 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6314     char buf[2*MOVE_LEN], *p;
6315     Exclusion *e = excluTab;
6316     int i;
6317     for(i=0; i<exCnt; i++)
6318         if(e[i].ff == fromX && e[i].fr == fromY &&
6319            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6320     if(i == exCnt) { // was not in exclude list; add it
6321         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6322         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6323             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6324             return; // abort
6325         }
6326         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6327         excludePtr++; e[i].mark = excludePtr++;
6328         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6329         exCnt++;
6330     }
6331     exclusionHeader[e[i].mark] = state;
6332 }
6333
6334 static int
6335 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6336 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6337     char buf[MSG_SIZ];
6338     int j, k;
6339     ChessMove moveType;
6340     if((signed char)promoChar == -1) { // kludge to indicate best move
6341         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6342             return 1; // if unparsable, abort
6343     }
6344     // update exclusion map (resolving toggle by consulting existing state)
6345     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6346     j = k%8; k >>= 3;
6347     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6348     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6349          excludeMap[k] |=   1<<j;
6350     else excludeMap[k] &= ~(1<<j);
6351     // update header
6352     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6353     // inform engine
6354     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6355     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6356     SendToBoth(buf);
6357     return (state == '+');
6358 }
6359
6360 static void
6361 ExcludeClick (int index)
6362 {
6363     int i, j;
6364     Exclusion *e = excluTab;
6365     if(index < 25) { // none, best or tail clicked
6366         if(index < 13) { // none: include all
6367             WriteMap(0); // clear map
6368             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6369             SendToBoth("include all\n"); // and inform engine
6370         } else if(index > 18) { // tail
6371             if(exclusionHeader[19] == '-') { // tail was excluded
6372                 SendToBoth("include all\n");
6373                 WriteMap(0); // clear map completely
6374                 // now re-exclude selected moves
6375                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6376                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6377             } else { // tail was included or in mixed state
6378                 SendToBoth("exclude all\n");
6379                 WriteMap(0xFF); // fill map completely
6380                 // now re-include selected moves
6381                 j = 0; // count them
6382                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6383                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6384                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6385             }
6386         } else { // best
6387             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6388         }
6389     } else {
6390         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6391             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6392             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6393             break;
6394         }
6395     }
6396 }
6397
6398 ChessSquare
6399 DefaultPromoChoice (int white)
6400 {
6401     ChessSquare result;
6402     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6403        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6404         result = WhiteFerz; // no choice
6405     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6406         result= WhiteKing; // in Suicide Q is the last thing we want
6407     else if(gameInfo.variant == VariantSpartan)
6408         result = white ? WhiteQueen : WhiteAngel;
6409     else result = WhiteQueen;
6410     if(!white) result = WHITE_TO_BLACK result;
6411     return result;
6412 }
6413
6414 static int autoQueen; // [HGM] oneclick
6415
6416 int
6417 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6418 {
6419     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6420     /* [HGM] add Shogi promotions */
6421     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6422     ChessSquare piece;
6423     ChessMove moveType;
6424     Boolean premove;
6425
6426     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6427     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6428
6429     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6430       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6431         return FALSE;
6432
6433     piece = boards[currentMove][fromY][fromX];
6434     if(gameInfo.variant == VariantChu) {
6435         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6436         promotionZoneSize = BOARD_HEIGHT/3;
6437         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6438     } else if(gameInfo.variant == VariantShogi) {
6439         promotionZoneSize = BOARD_HEIGHT/3;
6440         highestPromotingPiece = (int)WhiteAlfil;
6441     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6442         promotionZoneSize = 3;
6443     }
6444
6445     // Treat Lance as Pawn when it is not representing Amazon
6446     if(gameInfo.variant != VariantSuper) {
6447         if(piece == WhiteLance) piece = WhitePawn; else
6448         if(piece == BlackLance) piece = BlackPawn;
6449     }
6450
6451     // next weed out all moves that do not touch the promotion zone at all
6452     if((int)piece >= BlackPawn) {
6453         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6454              return FALSE;
6455         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6456     } else {
6457         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6458            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6459     }
6460
6461     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6462
6463     // weed out mandatory Shogi promotions
6464     if(gameInfo.variant == VariantShogi) {
6465         if(piece >= BlackPawn) {
6466             if(toY == 0 && piece == BlackPawn ||
6467                toY == 0 && piece == BlackQueen ||
6468                toY <= 1 && piece == BlackKnight) {
6469                 *promoChoice = '+';
6470                 return FALSE;
6471             }
6472         } else {
6473             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6474                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6475                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6476                 *promoChoice = '+';
6477                 return FALSE;
6478             }
6479         }
6480     }
6481
6482     // weed out obviously illegal Pawn moves
6483     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6484         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6485         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6486         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6487         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6488         // note we are not allowed to test for valid (non-)capture, due to premove
6489     }
6490
6491     // we either have a choice what to promote to, or (in Shogi) whether to promote
6492     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6493        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6494         *promoChoice = PieceToChar(BlackFerz);  // no choice
6495         return FALSE;
6496     }
6497     // no sense asking what we must promote to if it is going to explode...
6498     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6499         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6500         return FALSE;
6501     }
6502     // give caller the default choice even if we will not make it
6503     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6504     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6505     if(        sweepSelect && gameInfo.variant != VariantGreat
6506                            && gameInfo.variant != VariantGrand
6507                            && gameInfo.variant != VariantSuper) return FALSE;
6508     if(autoQueen) return FALSE; // predetermined
6509
6510     // suppress promotion popup on illegal moves that are not premoves
6511     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6512               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6513     if(appData.testLegality && !premove) {
6514         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6515                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6516         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6517             return FALSE;
6518     }
6519
6520     return TRUE;
6521 }
6522
6523 int
6524 InPalace (int row, int column)
6525 {   /* [HGM] for Xiangqi */
6526     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6527          column < (BOARD_WIDTH + 4)/2 &&
6528          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6529     return FALSE;
6530 }
6531
6532 int
6533 PieceForSquare (int x, int y)
6534 {
6535   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6536      return -1;
6537   else
6538      return boards[currentMove][y][x];
6539 }
6540
6541 int
6542 OKToStartUserMove (int x, int y)
6543 {
6544     ChessSquare from_piece;
6545     int white_piece;
6546
6547     if (matchMode) return FALSE;
6548     if (gameMode == EditPosition) return TRUE;
6549
6550     if (x >= 0 && y >= 0)
6551       from_piece = boards[currentMove][y][x];
6552     else
6553       from_piece = EmptySquare;
6554
6555     if (from_piece == EmptySquare) return FALSE;
6556
6557     white_piece = (int)from_piece >= (int)WhitePawn &&
6558       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6559
6560     switch (gameMode) {
6561       case AnalyzeFile:
6562       case TwoMachinesPlay:
6563       case EndOfGame:
6564         return FALSE;
6565
6566       case IcsObserving:
6567       case IcsIdle:
6568         return FALSE;
6569
6570       case MachinePlaysWhite:
6571       case IcsPlayingBlack:
6572         if (appData.zippyPlay) return FALSE;
6573         if (white_piece) {
6574             DisplayMoveError(_("You are playing Black"));
6575             return FALSE;
6576         }
6577         break;
6578
6579       case MachinePlaysBlack:
6580       case IcsPlayingWhite:
6581         if (appData.zippyPlay) return FALSE;
6582         if (!white_piece) {
6583             DisplayMoveError(_("You are playing White"));
6584             return FALSE;
6585         }
6586         break;
6587
6588       case PlayFromGameFile:
6589             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6590       case EditGame:
6591         if (!white_piece && WhiteOnMove(currentMove)) {
6592             DisplayMoveError(_("It is White's turn"));
6593             return FALSE;
6594         }
6595         if (white_piece && !WhiteOnMove(currentMove)) {
6596             DisplayMoveError(_("It is Black's turn"));
6597             return FALSE;
6598         }
6599         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6600             /* Editing correspondence game history */
6601             /* Could disallow this or prompt for confirmation */
6602             cmailOldMove = -1;
6603         }
6604         break;
6605
6606       case BeginningOfGame:
6607         if (appData.icsActive) return FALSE;
6608         if (!appData.noChessProgram) {
6609             if (!white_piece) {
6610                 DisplayMoveError(_("You are playing White"));
6611                 return FALSE;
6612             }
6613         }
6614         break;
6615
6616       case Training:
6617         if (!white_piece && WhiteOnMove(currentMove)) {
6618             DisplayMoveError(_("It is White's turn"));
6619             return FALSE;
6620         }
6621         if (white_piece && !WhiteOnMove(currentMove)) {
6622             DisplayMoveError(_("It is Black's turn"));
6623             return FALSE;
6624         }
6625         break;
6626
6627       default:
6628       case IcsExamining:
6629         break;
6630     }
6631     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6632         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6633         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6634         && gameMode != AnalyzeFile && gameMode != Training) {
6635         DisplayMoveError(_("Displayed position is not current"));
6636         return FALSE;
6637     }
6638     return TRUE;
6639 }
6640
6641 Boolean
6642 OnlyMove (int *x, int *y, Boolean captures)
6643 {
6644     DisambiguateClosure cl;
6645     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6646     switch(gameMode) {
6647       case MachinePlaysBlack:
6648       case IcsPlayingWhite:
6649       case BeginningOfGame:
6650         if(!WhiteOnMove(currentMove)) return FALSE;
6651         break;
6652       case MachinePlaysWhite:
6653       case IcsPlayingBlack:
6654         if(WhiteOnMove(currentMove)) return FALSE;
6655         break;
6656       case EditGame:
6657         break;
6658       default:
6659         return FALSE;
6660     }
6661     cl.pieceIn = EmptySquare;
6662     cl.rfIn = *y;
6663     cl.ffIn = *x;
6664     cl.rtIn = -1;
6665     cl.ftIn = -1;
6666     cl.promoCharIn = NULLCHAR;
6667     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6668     if( cl.kind == NormalMove ||
6669         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6670         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6671         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6672       fromX = cl.ff;
6673       fromY = cl.rf;
6674       *x = cl.ft;
6675       *y = cl.rt;
6676       return TRUE;
6677     }
6678     if(cl.kind != ImpossibleMove) return FALSE;
6679     cl.pieceIn = EmptySquare;
6680     cl.rfIn = -1;
6681     cl.ffIn = -1;
6682     cl.rtIn = *y;
6683     cl.ftIn = *x;
6684     cl.promoCharIn = NULLCHAR;
6685     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6686     if( cl.kind == NormalMove ||
6687         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6688         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6689         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6690       fromX = cl.ff;
6691       fromY = cl.rf;
6692       *x = cl.ft;
6693       *y = cl.rt;
6694       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6695       return TRUE;
6696     }
6697     return FALSE;
6698 }
6699
6700 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6701 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6702 int lastLoadGameUseList = FALSE;
6703 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6704 ChessMove lastLoadGameStart = EndOfFile;
6705 int doubleClick;
6706
6707 void
6708 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6709 {
6710     ChessMove moveType;
6711     ChessSquare pup;
6712     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6713
6714     /* Check if the user is playing in turn.  This is complicated because we
6715        let the user "pick up" a piece before it is his turn.  So the piece he
6716        tried to pick up may have been captured by the time he puts it down!
6717        Therefore we use the color the user is supposed to be playing in this
6718        test, not the color of the piece that is currently on the starting
6719        square---except in EditGame mode, where the user is playing both
6720        sides; fortunately there the capture race can't happen.  (It can
6721        now happen in IcsExamining mode, but that's just too bad.  The user
6722        will get a somewhat confusing message in that case.)
6723        */
6724
6725     switch (gameMode) {
6726       case AnalyzeFile:
6727       case TwoMachinesPlay:
6728       case EndOfGame:
6729       case IcsObserving:
6730       case IcsIdle:
6731         /* We switched into a game mode where moves are not accepted,
6732            perhaps while the mouse button was down. */
6733         return;
6734
6735       case MachinePlaysWhite:
6736         /* User is moving for Black */
6737         if (WhiteOnMove(currentMove)) {
6738             DisplayMoveError(_("It is White's turn"));
6739             return;
6740         }
6741         break;
6742
6743       case MachinePlaysBlack:
6744         /* User is moving for White */
6745         if (!WhiteOnMove(currentMove)) {
6746             DisplayMoveError(_("It is Black's turn"));
6747             return;
6748         }
6749         break;
6750
6751       case PlayFromGameFile:
6752             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6753       case EditGame:
6754       case IcsExamining:
6755       case BeginningOfGame:
6756       case AnalyzeMode:
6757       case Training:
6758         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6759         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6760             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6761             /* User is moving for Black */
6762             if (WhiteOnMove(currentMove)) {
6763                 DisplayMoveError(_("It is White's turn"));
6764                 return;
6765             }
6766         } else {
6767             /* User is moving for White */
6768             if (!WhiteOnMove(currentMove)) {
6769                 DisplayMoveError(_("It is Black's turn"));
6770                 return;
6771             }
6772         }
6773         break;
6774
6775       case IcsPlayingBlack:
6776         /* User is moving for Black */
6777         if (WhiteOnMove(currentMove)) {
6778             if (!appData.premove) {
6779                 DisplayMoveError(_("It is White's turn"));
6780             } else if (toX >= 0 && toY >= 0) {
6781                 premoveToX = toX;
6782                 premoveToY = toY;
6783                 premoveFromX = fromX;
6784                 premoveFromY = fromY;
6785                 premovePromoChar = promoChar;
6786                 gotPremove = 1;
6787                 if (appData.debugMode)
6788                     fprintf(debugFP, "Got premove: fromX %d,"
6789                             "fromY %d, toX %d, toY %d\n",
6790                             fromX, fromY, toX, toY);
6791             }
6792             return;
6793         }
6794         break;
6795
6796       case IcsPlayingWhite:
6797         /* User is moving for White */
6798         if (!WhiteOnMove(currentMove)) {
6799             if (!appData.premove) {
6800                 DisplayMoveError(_("It is Black's turn"));
6801             } else if (toX >= 0 && toY >= 0) {
6802                 premoveToX = toX;
6803                 premoveToY = toY;
6804                 premoveFromX = fromX;
6805                 premoveFromY = fromY;
6806                 premovePromoChar = promoChar;
6807                 gotPremove = 1;
6808                 if (appData.debugMode)
6809                     fprintf(debugFP, "Got premove: fromX %d,"
6810                             "fromY %d, toX %d, toY %d\n",
6811                             fromX, fromY, toX, toY);
6812             }
6813             return;
6814         }
6815         break;
6816
6817       default:
6818         break;
6819
6820       case EditPosition:
6821         /* EditPosition, empty square, or different color piece;
6822            click-click move is possible */
6823         if (toX == -2 || toY == -2) {
6824             boards[0][fromY][fromX] = EmptySquare;
6825             DrawPosition(FALSE, boards[currentMove]);
6826             return;
6827         } else if (toX >= 0 && toY >= 0) {
6828             boards[0][toY][toX] = boards[0][fromY][fromX];
6829             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6830                 if(boards[0][fromY][0] != EmptySquare) {
6831                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6832                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6833                 }
6834             } else
6835             if(fromX == BOARD_RGHT+1) {
6836                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6837                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6838                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6839                 }
6840             } else
6841             boards[0][fromY][fromX] = gatingPiece;
6842             DrawPosition(FALSE, boards[currentMove]);
6843             return;
6844         }
6845         return;
6846     }
6847
6848     if(toX < 0 || toY < 0) return;
6849     pup = boards[currentMove][toY][toX];
6850
6851     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6852     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6853          if( pup != EmptySquare ) return;
6854          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6855            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6856                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6857            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6858            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6859            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6860            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6861          fromY = DROP_RANK;
6862     }
6863
6864     /* [HGM] always test for legality, to get promotion info */
6865     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6866                                          fromY, fromX, toY, toX, promoChar);
6867
6868     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6869
6870     /* [HGM] but possibly ignore an IllegalMove result */
6871     if (appData.testLegality) {
6872         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6873             DisplayMoveError(_("Illegal move"));
6874             return;
6875         }
6876     }
6877
6878     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6879         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6880              ClearPremoveHighlights(); // was included
6881         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6882         return;
6883     }
6884
6885     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6886 }
6887
6888 /* Common tail of UserMoveEvent and DropMenuEvent */
6889 int
6890 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6891 {
6892     char *bookHit = 0;
6893
6894     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6895         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6896         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6897         if(WhiteOnMove(currentMove)) {
6898             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6899         } else {
6900             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6901         }
6902     }
6903
6904     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6905        move type in caller when we know the move is a legal promotion */
6906     if(moveType == NormalMove && promoChar)
6907         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6908
6909     /* [HGM] <popupFix> The following if has been moved here from
6910        UserMoveEvent(). Because it seemed to belong here (why not allow
6911        piece drops in training games?), and because it can only be
6912        performed after it is known to what we promote. */
6913     if (gameMode == Training) {
6914       /* compare the move played on the board to the next move in the
6915        * game. If they match, display the move and the opponent's response.
6916        * If they don't match, display an error message.
6917        */
6918       int saveAnimate;
6919       Board testBoard;
6920       CopyBoard(testBoard, boards[currentMove]);
6921       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6922
6923       if (CompareBoards(testBoard, boards[currentMove+1])) {
6924         ForwardInner(currentMove+1);
6925
6926         /* Autoplay the opponent's response.
6927          * if appData.animate was TRUE when Training mode was entered,
6928          * the response will be animated.
6929          */
6930         saveAnimate = appData.animate;
6931         appData.animate = animateTraining;
6932         ForwardInner(currentMove+1);
6933         appData.animate = saveAnimate;
6934
6935         /* check for the end of the game */
6936         if (currentMove >= forwardMostMove) {
6937           gameMode = PlayFromGameFile;
6938           ModeHighlight();
6939           SetTrainingModeOff();
6940           DisplayInformation(_("End of game"));
6941         }
6942       } else {
6943         DisplayError(_("Incorrect move"), 0);
6944       }
6945       return 1;
6946     }
6947
6948   /* Ok, now we know that the move is good, so we can kill
6949      the previous line in Analysis Mode */
6950   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6951                                 && currentMove < forwardMostMove) {
6952     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6953     else forwardMostMove = currentMove;
6954   }
6955
6956   ClearMap();
6957
6958   /* If we need the chess program but it's dead, restart it */
6959   ResurrectChessProgram();
6960
6961   /* A user move restarts a paused game*/
6962   if (pausing)
6963     PauseEvent();
6964
6965   thinkOutput[0] = NULLCHAR;
6966
6967   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6968
6969   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6970     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6971     return 1;
6972   }
6973
6974   if (gameMode == BeginningOfGame) {
6975     if (appData.noChessProgram) {
6976       gameMode = EditGame;
6977       SetGameInfo();
6978     } else {
6979       char buf[MSG_SIZ];
6980       gameMode = MachinePlaysBlack;
6981       StartClocks();
6982       SetGameInfo();
6983       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6984       DisplayTitle(buf);
6985       if (first.sendName) {
6986         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6987         SendToProgram(buf, &first);
6988       }
6989       StartClocks();
6990     }
6991     ModeHighlight();
6992   }
6993
6994   /* Relay move to ICS or chess engine */
6995   if (appData.icsActive) {
6996     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6997         gameMode == IcsExamining) {
6998       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6999         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7000         SendToICS("draw ");
7001         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7002       }
7003       // also send plain move, in case ICS does not understand atomic claims
7004       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7005       ics_user_moved = 1;
7006     }
7007   } else {
7008     if (first.sendTime && (gameMode == BeginningOfGame ||
7009                            gameMode == MachinePlaysWhite ||
7010                            gameMode == MachinePlaysBlack)) {
7011       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7012     }
7013     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7014          // [HGM] book: if program might be playing, let it use book
7015         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7016         first.maybeThinking = TRUE;
7017     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7018         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7019         SendBoard(&first, currentMove+1);
7020         if(second.analyzing) {
7021             if(!second.useSetboard) SendToProgram("undo\n", &second);
7022             SendBoard(&second, currentMove+1);
7023         }
7024     } else {
7025         SendMoveToProgram(forwardMostMove-1, &first);
7026         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7027     }
7028     if (currentMove == cmailOldMove + 1) {
7029       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7030     }
7031   }
7032
7033   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7034
7035   switch (gameMode) {
7036   case EditGame:
7037     if(appData.testLegality)
7038     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7039     case MT_NONE:
7040     case MT_CHECK:
7041       break;
7042     case MT_CHECKMATE:
7043     case MT_STAINMATE:
7044       if (WhiteOnMove(currentMove)) {
7045         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7046       } else {
7047         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7048       }
7049       break;
7050     case MT_STALEMATE:
7051       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7052       break;
7053     }
7054     break;
7055
7056   case MachinePlaysBlack:
7057   case MachinePlaysWhite:
7058     /* disable certain menu options while machine is thinking */
7059     SetMachineThinkingEnables();
7060     break;
7061
7062   default:
7063     break;
7064   }
7065
7066   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7067   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7068
7069   if(bookHit) { // [HGM] book: simulate book reply
7070         static char bookMove[MSG_SIZ]; // a bit generous?
7071
7072         programStats.nodes = programStats.depth = programStats.time =
7073         programStats.score = programStats.got_only_move = 0;
7074         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7075
7076         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7077         strcat(bookMove, bookHit);
7078         HandleMachineMove(bookMove, &first);
7079   }
7080   return 1;
7081 }
7082
7083 void
7084 MarkByFEN(char *fen)
7085 {
7086         int r, f;
7087         if(!appData.markers || !appData.highlightDragging) return;
7088         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7089         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7090         while(*fen) {
7091             int s = 0;
7092             marker[r][f] = 0;
7093             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7094             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7095             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7096             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7097             if(*fen == 'T') marker[r][f++] = 0; else
7098             if(*fen == 'Y') marker[r][f++] = 1; else
7099             if(*fen == 'G') marker[r][f++] = 3; else
7100             if(*fen == 'B') marker[r][f++] = 4; else
7101             if(*fen == 'C') marker[r][f++] = 5; else
7102             if(*fen == 'M') marker[r][f++] = 6; else
7103             if(*fen == 'W') marker[r][f++] = 7; else
7104             if(*fen == 'D') marker[r][f++] = 8; else
7105             if(*fen == 'R') marker[r][f++] = 2; else {
7106                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7107               f += s; fen -= s>0;
7108             }
7109             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7110             if(r < 0) break;
7111             fen++;
7112         }
7113         DrawPosition(TRUE, NULL);
7114 }
7115
7116 void
7117 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7118 {
7119     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7120     Markers *m = (Markers *) closure;
7121     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7122         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7123                          || kind == WhiteCapturesEnPassant
7124                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7125     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7126 }
7127
7128 void
7129 MarkTargetSquares (int clear)
7130 {
7131   int x, y, sum=0;
7132   if(clear) { // no reason to ever suppress clearing
7133     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7134     if(!sum) return; // nothing was cleared,no redraw needed
7135   } else {
7136     int capt = 0;
7137     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7138        !appData.testLegality || gameMode == EditPosition) return;
7139     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7140     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7141       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7142       if(capt)
7143       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7144     }
7145   }
7146   DrawPosition(FALSE, NULL);
7147 }
7148
7149 int
7150 Explode (Board board, int fromX, int fromY, int toX, int toY)
7151 {
7152     if(gameInfo.variant == VariantAtomic &&
7153        (board[toY][toX] != EmptySquare ||                     // capture?
7154         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7155                          board[fromY][fromX] == BlackPawn   )
7156       )) {
7157         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7158         return TRUE;
7159     }
7160     return FALSE;
7161 }
7162
7163 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7164
7165 int
7166 CanPromote (ChessSquare piece, int y)
7167 {
7168         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7169         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7170         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7171            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7172            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7173          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7174         return (piece == BlackPawn && y == 1 ||
7175                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7176                 piece == BlackLance && y == 1 ||
7177                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7178 }
7179
7180 void
7181 HoverEvent (int xPix, int yPix, int x, int y)
7182 {
7183         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7184         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7185         int r, f;
7186         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7187         if(!first.highlight) return;
7188         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7189         if(x == oldX && y == oldY) return; // only do something if we enter new square
7190         oldFromX = fromX; oldFromY = fromY;
7191         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7192           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7193             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7194         else if(oldX != x || oldY != y) {
7195           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7196           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7197             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7198           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7199             char buf[MSG_SIZ];
7200             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7201             SendToProgram(buf, &first);
7202           }
7203           oldX = x; oldY = y;
7204 //        SetHighlights(fromX, fromY, x, y);
7205         }
7206 }
7207
7208 void ReportClick(char *action, int x, int y)
7209 {
7210         char buf[MSG_SIZ]; // Inform engine of what user does
7211         int r, f;
7212         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7213           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7214         if(!first.highlight || gameMode == EditPosition) return;
7215         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7216         SendToProgram(buf, &first);
7217 }
7218
7219 void
7220 LeftClick (ClickType clickType, int xPix, int yPix)
7221 {
7222     int x, y;
7223     Boolean saveAnimate;
7224     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7225     char promoChoice = NULLCHAR;
7226     ChessSquare piece;
7227     static TimeMark lastClickTime, prevClickTime;
7228
7229     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7230
7231     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7232
7233     if (clickType == Press) ErrorPopDown();
7234     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7235
7236     x = EventToSquare(xPix, BOARD_WIDTH);
7237     y = EventToSquare(yPix, BOARD_HEIGHT);
7238     if (!flipView && y >= 0) {
7239         y = BOARD_HEIGHT - 1 - y;
7240     }
7241     if (flipView && x >= 0) {
7242         x = BOARD_WIDTH - 1 - x;
7243     }
7244
7245     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7246         defaultPromoChoice = promoSweep;
7247         promoSweep = EmptySquare;   // terminate sweep
7248         promoDefaultAltered = TRUE;
7249         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7250     }
7251
7252     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7253         if(clickType == Release) return; // ignore upclick of click-click destination
7254         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7255         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7256         if(gameInfo.holdingsWidth &&
7257                 (WhiteOnMove(currentMove)
7258                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7259                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7260             // click in right holdings, for determining promotion piece
7261             ChessSquare p = boards[currentMove][y][x];
7262             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7263             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7264             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7265                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7266                 fromX = fromY = -1;
7267                 return;
7268             }
7269         }
7270         DrawPosition(FALSE, boards[currentMove]);
7271         return;
7272     }
7273
7274     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7275     if(clickType == Press
7276             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7277               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7278               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7279         return;
7280
7281     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7282         // could be static click on premove from-square: abort premove
7283         gotPremove = 0;
7284         ClearPremoveHighlights();
7285     }
7286
7287     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7288         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7289
7290     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7291         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7292                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7293         defaultPromoChoice = DefaultPromoChoice(side);
7294     }
7295
7296     autoQueen = appData.alwaysPromoteToQueen;
7297
7298     if (fromX == -1) {
7299       int originalY = y;
7300       gatingPiece = EmptySquare;
7301       if (clickType != Press) {
7302         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7303             DragPieceEnd(xPix, yPix); dragging = 0;
7304             DrawPosition(FALSE, NULL);
7305         }
7306         return;
7307       }
7308       doubleClick = FALSE;
7309       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7310         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7311       }
7312       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7313       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7314          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7315          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7316             /* First square */
7317             if (OKToStartUserMove(fromX, fromY)) {
7318                 second = 0;
7319                 ReportClick("lift", x, y);
7320                 MarkTargetSquares(0);
7321                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7322                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7323                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7324                     promoSweep = defaultPromoChoice;
7325                     selectFlag = 0; lastX = xPix; lastY = yPix;
7326                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7327                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7328                 }
7329                 if (appData.highlightDragging) {
7330                     SetHighlights(fromX, fromY, -1, -1);
7331                 } else {
7332                     ClearHighlights();
7333                 }
7334             } else fromX = fromY = -1;
7335             return;
7336         }
7337     }
7338
7339     /* fromX != -1 */
7340     if (clickType == Press && gameMode != EditPosition) {
7341         ChessSquare fromP;
7342         ChessSquare toP;
7343         int frc;
7344
7345         // ignore off-board to clicks
7346         if(y < 0 || x < 0) return;
7347
7348         /* Check if clicking again on the same color piece */
7349         fromP = boards[currentMove][fromY][fromX];
7350         toP = boards[currentMove][y][x];
7351         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7352         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7353            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7354              WhitePawn <= toP && toP <= WhiteKing &&
7355              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7356              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7357             (BlackPawn <= fromP && fromP <= BlackKing &&
7358              BlackPawn <= toP && toP <= BlackKing &&
7359              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7360              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7361             /* Clicked again on same color piece -- changed his mind */
7362             second = (x == fromX && y == fromY);
7363             killX = killY = -1;
7364             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7365                 second = FALSE; // first double-click rather than scond click
7366                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7367             }
7368             promoDefaultAltered = FALSE;
7369             MarkTargetSquares(1);
7370            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7371             if (appData.highlightDragging) {
7372                 SetHighlights(x, y, -1, -1);
7373             } else {
7374                 ClearHighlights();
7375             }
7376             if (OKToStartUserMove(x, y)) {
7377                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7378                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7379                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7380                  gatingPiece = boards[currentMove][fromY][fromX];
7381                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7382                 fromX = x;
7383                 fromY = y; dragging = 1;
7384                 ReportClick("lift", x, y);
7385                 MarkTargetSquares(0);
7386                 DragPieceBegin(xPix, yPix, FALSE);
7387                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7388                     promoSweep = defaultPromoChoice;
7389                     selectFlag = 0; lastX = xPix; lastY = yPix;
7390                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7391                 }
7392             }
7393            }
7394            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7395            second = FALSE;
7396         }
7397         // ignore clicks on holdings
7398         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7399     }
7400
7401     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7402         DragPieceEnd(xPix, yPix); dragging = 0;
7403         if(clearFlag) {
7404             // a deferred attempt to click-click move an empty square on top of a piece
7405             boards[currentMove][y][x] = EmptySquare;
7406             ClearHighlights();
7407             DrawPosition(FALSE, boards[currentMove]);
7408             fromX = fromY = -1; clearFlag = 0;
7409             return;
7410         }
7411         if (appData.animateDragging) {
7412             /* Undo animation damage if any */
7413             DrawPosition(FALSE, NULL);
7414         }
7415         if (second || sweepSelecting) {
7416             /* Second up/down in same square; just abort move */
7417             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7418             second = sweepSelecting = 0;
7419             fromX = fromY = -1;
7420             gatingPiece = EmptySquare;
7421             MarkTargetSquares(1);
7422             ClearHighlights();
7423             gotPremove = 0;
7424             ClearPremoveHighlights();
7425         } else {
7426             /* First upclick in same square; start click-click mode */
7427             SetHighlights(x, y, -1, -1);
7428         }
7429         return;
7430     }
7431
7432     clearFlag = 0;
7433
7434     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7435         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7436         DisplayMessage(_("only marked squares are legal"),"");
7437         DrawPosition(TRUE, NULL);
7438         return; // ignore to-click
7439     }
7440
7441     /* we now have a different from- and (possibly off-board) to-square */
7442     /* Completed move */
7443     if(!sweepSelecting) {
7444         toX = x;
7445         toY = y;
7446     }
7447
7448     saveAnimate = appData.animate;
7449     if (clickType == Press) {
7450         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7451             // must be Edit Position mode with empty-square selected
7452             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7453             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7454             return;
7455         }
7456         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7457             dragging = 1;
7458             return;
7459         }
7460         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7461             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7462         } else
7463         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7464         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7465           if(appData.sweepSelect) {
7466             ChessSquare piece = boards[currentMove][fromY][fromX];
7467             promoSweep = defaultPromoChoice;
7468             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7469             selectFlag = 0; lastX = xPix; lastY = yPix;
7470             Sweep(0); // Pawn that is going to promote: preview promotion piece
7471             sweepSelecting = 1;
7472             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7473             MarkTargetSquares(1);
7474           }
7475           return; // promo popup appears on up-click
7476         }
7477         /* Finish clickclick move */
7478         if (appData.animate || appData.highlightLastMove) {
7479             SetHighlights(fromX, fromY, toX, toY);
7480         } else {
7481             ClearHighlights();
7482         }
7483     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7484         sweepSelecting = 0;
7485         if (appData.animate || appData.highlightLastMove) {
7486             SetHighlights(fromX, fromY, toX, toY);
7487         } else {
7488             ClearHighlights();
7489         }
7490     } else {
7491 #if 0
7492 // [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
7493         /* Finish drag move */
7494         if (appData.highlightLastMove) {
7495             SetHighlights(fromX, fromY, toX, toY);
7496         } else {
7497             ClearHighlights();
7498         }
7499 #endif
7500         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7501           dragging *= 2;            // flag button-less dragging if we are dragging
7502           MarkTargetSquares(1);
7503           if(x == killX && y == killY) killX = killY = -1; else {
7504             killX = x; killY = y;     //remeber this square as intermediate
7505             MarkTargetSquares(0);
7506             ReportClick("put", x, y); // and inform engine
7507             ReportClick("lift", x, y);
7508             return;
7509           }
7510         }
7511         DragPieceEnd(xPix, yPix); dragging = 0;
7512         /* Don't animate move and drag both */
7513         appData.animate = FALSE;
7514     }
7515
7516     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7517     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7518         ChessSquare piece = boards[currentMove][fromY][fromX];
7519         if(gameMode == EditPosition && piece != EmptySquare &&
7520            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7521             int n;
7522
7523             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7524                 n = PieceToNumber(piece - (int)BlackPawn);
7525                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7526                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7527                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7528             } else
7529             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7530                 n = PieceToNumber(piece);
7531                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7532                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7533                 boards[currentMove][n][BOARD_WIDTH-2]++;
7534             }
7535             boards[currentMove][fromY][fromX] = EmptySquare;
7536         }
7537         ClearHighlights();
7538         fromX = fromY = -1;
7539         MarkTargetSquares(1);
7540         DrawPosition(TRUE, boards[currentMove]);
7541         return;
7542     }
7543
7544     // off-board moves should not be highlighted
7545     if(x < 0 || y < 0) ClearHighlights();
7546     else ReportClick("put", x, y);
7547
7548     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7549
7550     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7551         SetHighlights(fromX, fromY, toX, toY);
7552         MarkTargetSquares(1);
7553         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7554             // [HGM] super: promotion to captured piece selected from holdings
7555             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7556             promotionChoice = TRUE;
7557             // kludge follows to temporarily execute move on display, without promoting yet
7558             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7559             boards[currentMove][toY][toX] = p;
7560             DrawPosition(FALSE, boards[currentMove]);
7561             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7562             boards[currentMove][toY][toX] = q;
7563             DisplayMessage("Click in holdings to choose piece", "");
7564             return;
7565         }
7566         PromotionPopUp();
7567     } else {
7568         int oldMove = currentMove;
7569         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7570         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7571         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7572         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7573            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7574             DrawPosition(TRUE, boards[currentMove]);
7575         MarkTargetSquares(1);
7576         fromX = fromY = -1;
7577     }
7578     appData.animate = saveAnimate;
7579     if (appData.animate || appData.animateDragging) {
7580         /* Undo animation damage if needed */
7581         DrawPosition(FALSE, NULL);
7582     }
7583 }
7584
7585 int
7586 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7587 {   // front-end-free part taken out of PieceMenuPopup
7588     int whichMenu; int xSqr, ySqr;
7589
7590     if(seekGraphUp) { // [HGM] seekgraph
7591         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7592         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7593         return -2;
7594     }
7595
7596     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7597          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7598         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7599         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7600         if(action == Press)   {
7601             originalFlip = flipView;
7602             flipView = !flipView; // temporarily flip board to see game from partners perspective
7603             DrawPosition(TRUE, partnerBoard);
7604             DisplayMessage(partnerStatus, "");
7605             partnerUp = TRUE;
7606         } else if(action == Release) {
7607             flipView = originalFlip;
7608             DrawPosition(TRUE, boards[currentMove]);
7609             partnerUp = FALSE;
7610         }
7611         return -2;
7612     }
7613
7614     xSqr = EventToSquare(x, BOARD_WIDTH);
7615     ySqr = EventToSquare(y, BOARD_HEIGHT);
7616     if (action == Release) {
7617         if(pieceSweep != EmptySquare) {
7618             EditPositionMenuEvent(pieceSweep, toX, toY);
7619             pieceSweep = EmptySquare;
7620         } else UnLoadPV(); // [HGM] pv
7621     }
7622     if (action != Press) return -2; // return code to be ignored
7623     switch (gameMode) {
7624       case IcsExamining:
7625         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7626       case EditPosition:
7627         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7628         if (xSqr < 0 || ySqr < 0) return -1;
7629         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7630         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7631         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7632         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7633         NextPiece(0);
7634         return 2; // grab
7635       case IcsObserving:
7636         if(!appData.icsEngineAnalyze) return -1;
7637       case IcsPlayingWhite:
7638       case IcsPlayingBlack:
7639         if(!appData.zippyPlay) goto noZip;
7640       case AnalyzeMode:
7641       case AnalyzeFile:
7642       case MachinePlaysWhite:
7643       case MachinePlaysBlack:
7644       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7645         if (!appData.dropMenu) {
7646           LoadPV(x, y);
7647           return 2; // flag front-end to grab mouse events
7648         }
7649         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7650            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7651       case EditGame:
7652       noZip:
7653         if (xSqr < 0 || ySqr < 0) return -1;
7654         if (!appData.dropMenu || appData.testLegality &&
7655             gameInfo.variant != VariantBughouse &&
7656             gameInfo.variant != VariantCrazyhouse) return -1;
7657         whichMenu = 1; // drop menu
7658         break;
7659       default:
7660         return -1;
7661     }
7662
7663     if (((*fromX = xSqr) < 0) ||
7664         ((*fromY = ySqr) < 0)) {
7665         *fromX = *fromY = -1;
7666         return -1;
7667     }
7668     if (flipView)
7669       *fromX = BOARD_WIDTH - 1 - *fromX;
7670     else
7671       *fromY = BOARD_HEIGHT - 1 - *fromY;
7672
7673     return whichMenu;
7674 }
7675
7676 void
7677 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7678 {
7679 //    char * hint = lastHint;
7680     FrontEndProgramStats stats;
7681
7682     stats.which = cps == &first ? 0 : 1;
7683     stats.depth = cpstats->depth;
7684     stats.nodes = cpstats->nodes;
7685     stats.score = cpstats->score;
7686     stats.time = cpstats->time;
7687     stats.pv = cpstats->movelist;
7688     stats.hint = lastHint;
7689     stats.an_move_index = 0;
7690     stats.an_move_count = 0;
7691
7692     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7693         stats.hint = cpstats->move_name;
7694         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7695         stats.an_move_count = cpstats->nr_moves;
7696     }
7697
7698     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
7699
7700     SetProgramStats( &stats );
7701 }
7702
7703 void
7704 ClearEngineOutputPane (int which)
7705 {
7706     static FrontEndProgramStats dummyStats;
7707     dummyStats.which = which;
7708     dummyStats.pv = "#";
7709     SetProgramStats( &dummyStats );
7710 }
7711
7712 #define MAXPLAYERS 500
7713
7714 char *
7715 TourneyStandings (int display)
7716 {
7717     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7718     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7719     char result, *p, *names[MAXPLAYERS];
7720
7721     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7722         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7723     names[0] = p = strdup(appData.participants);
7724     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7725
7726     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7727
7728     while(result = appData.results[nr]) {
7729         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7730         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7731         wScore = bScore = 0;
7732         switch(result) {
7733           case '+': wScore = 2; break;
7734           case '-': bScore = 2; break;
7735           case '=': wScore = bScore = 1; break;
7736           case ' ':
7737           case '*': return strdup("busy"); // tourney not finished
7738         }
7739         score[w] += wScore;
7740         score[b] += bScore;
7741         games[w]++;
7742         games[b]++;
7743         nr++;
7744     }
7745     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7746     for(w=0; w<nPlayers; w++) {
7747         bScore = -1;
7748         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7749         ranking[w] = b; points[w] = bScore; score[b] = -2;
7750     }
7751     p = malloc(nPlayers*34+1);
7752     for(w=0; w<nPlayers && w<display; w++)
7753         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7754     free(names[0]);
7755     return p;
7756 }
7757
7758 void
7759 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7760 {       // count all piece types
7761         int p, f, r;
7762         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7763         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7764         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7765                 p = board[r][f];
7766                 pCnt[p]++;
7767                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7768                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7769                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7770                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7771                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7772                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7773         }
7774 }
7775
7776 int
7777 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7778 {
7779         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7780         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7781
7782         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7783         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7784         if(myPawns == 2 && nMine == 3) // KPP
7785             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7786         if(myPawns == 1 && nMine == 2) // KP
7787             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7788         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7789             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7790         if(myPawns) return FALSE;
7791         if(pCnt[WhiteRook+side])
7792             return pCnt[BlackRook-side] ||
7793                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7794                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7795                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7796         if(pCnt[WhiteCannon+side]) {
7797             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7798             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7799         }
7800         if(pCnt[WhiteKnight+side])
7801             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7802         return FALSE;
7803 }
7804
7805 int
7806 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7807 {
7808         VariantClass v = gameInfo.variant;
7809
7810         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7811         if(v == VariantShatranj) return TRUE; // always winnable through baring
7812         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7813         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7814
7815         if(v == VariantXiangqi) {
7816                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7817
7818                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7819                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7820                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7821                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7822                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7823                 if(stale) // we have at least one last-rank P plus perhaps C
7824                     return majors // KPKX
7825                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7826                 else // KCA*E*
7827                     return pCnt[WhiteFerz+side] // KCAK
7828                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7829                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7830                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7831
7832         } else if(v == VariantKnightmate) {
7833                 if(nMine == 1) return FALSE;
7834                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7835         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7836                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7837
7838                 if(nMine == 1) return FALSE; // bare King
7839                 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
7840                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7841                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7842                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7843                 if(pCnt[WhiteKnight+side])
7844                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7845                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7846                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7847                 if(nBishops)
7848                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7849                 if(pCnt[WhiteAlfil+side])
7850                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7851                 if(pCnt[WhiteWazir+side])
7852                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7853         }
7854
7855         return TRUE;
7856 }
7857
7858 int
7859 CompareWithRights (Board b1, Board b2)
7860 {
7861     int rights = 0;
7862     if(!CompareBoards(b1, b2)) return FALSE;
7863     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7864     /* compare castling rights */
7865     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7866            rights++; /* King lost rights, while rook still had them */
7867     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7868         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7869            rights++; /* but at least one rook lost them */
7870     }
7871     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7872            rights++;
7873     if( b1[CASTLING][5] != NoRights ) {
7874         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7875            rights++;
7876     }
7877     return rights == 0;
7878 }
7879
7880 int
7881 Adjudicate (ChessProgramState *cps)
7882 {       // [HGM] some adjudications useful with buggy engines
7883         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7884         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7885         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7886         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7887         int k, drop, count = 0; static int bare = 1;
7888         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7889         Boolean canAdjudicate = !appData.icsActive;
7890
7891         // most tests only when we understand the game, i.e. legality-checking on
7892             if( appData.testLegality )
7893             {   /* [HGM] Some more adjudications for obstinate engines */
7894                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7895                 static int moveCount = 6;
7896                 ChessMove result;
7897                 char *reason = NULL;
7898
7899                 /* Count what is on board. */
7900                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7901
7902                 /* Some material-based adjudications that have to be made before stalemate test */
7903                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7904                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7905                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7906                      if(canAdjudicate && appData.checkMates) {
7907                          if(engineOpponent)
7908                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7909                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7910                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7911                          return 1;
7912                      }
7913                 }
7914
7915                 /* Bare King in Shatranj (loses) or Losers (wins) */
7916                 if( nrW == 1 || nrB == 1) {
7917                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7918                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7919                      if(canAdjudicate && appData.checkMates) {
7920                          if(engineOpponent)
7921                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7922                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7923                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7924                          return 1;
7925                      }
7926                   } else
7927                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7928                   {    /* bare King */
7929                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7930                         if(canAdjudicate && appData.checkMates) {
7931                             /* but only adjudicate if adjudication enabled */
7932                             if(engineOpponent)
7933                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7934                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7935                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7936                             return 1;
7937                         }
7938                   }
7939                 } else bare = 1;
7940
7941
7942             // don't wait for engine to announce game end if we can judge ourselves
7943             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7944               case MT_CHECK:
7945                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7946                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7947                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7948                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7949                             checkCnt++;
7950                         if(checkCnt >= 2) {
7951                             reason = "Xboard adjudication: 3rd check";
7952                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7953                             break;
7954                         }
7955                     }
7956                 }
7957               case MT_NONE:
7958               default:
7959                 break;
7960               case MT_STALEMATE:
7961               case MT_STAINMATE:
7962                 reason = "Xboard adjudication: Stalemate";
7963                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7964                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7965                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7966                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7967                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7968                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7969                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7970                                                                         EP_CHECKMATE : EP_WINS);
7971                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7972                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7973                 }
7974                 break;
7975               case MT_CHECKMATE:
7976                 reason = "Xboard adjudication: Checkmate";
7977                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7978                 if(gameInfo.variant == VariantShogi) {
7979                     if(forwardMostMove > backwardMostMove
7980                        && moveList[forwardMostMove-1][1] == '@'
7981                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7982                         reason = "XBoard adjudication: pawn-drop mate";
7983                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7984                     }
7985                 }
7986                 break;
7987             }
7988
7989                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7990                     case EP_STALEMATE:
7991                         result = GameIsDrawn; break;
7992                     case EP_CHECKMATE:
7993                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7994                     case EP_WINS:
7995                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7996                     default:
7997                         result = EndOfFile;
7998                 }
7999                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8000                     if(engineOpponent)
8001                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8002                     GameEnds( result, reason, GE_XBOARD );
8003                     return 1;
8004                 }
8005
8006                 /* Next absolutely insufficient mating material. */
8007                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8008                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8009                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8010
8011                      /* always flag draws, for judging claims */
8012                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8013
8014                      if(canAdjudicate && appData.materialDraws) {
8015                          /* but only adjudicate them if adjudication enabled */
8016                          if(engineOpponent) {
8017                            SendToProgram("force\n", engineOpponent); // suppress reply
8018                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8019                          }
8020                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8021                          return 1;
8022                      }
8023                 }
8024
8025                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8026                 if(gameInfo.variant == VariantXiangqi ?
8027                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8028                  : nrW + nrB == 4 &&
8029                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8030                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8031                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8032                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8033                    ) ) {
8034                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8035                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8036                           if(engineOpponent) {
8037                             SendToProgram("force\n", engineOpponent); // suppress reply
8038                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8039                           }
8040                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8041                           return 1;
8042                      }
8043                 } else moveCount = 6;
8044             }
8045
8046         // Repetition draws and 50-move rule can be applied independently of legality testing
8047
8048                 /* Check for rep-draws */
8049                 count = 0;
8050                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8051                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8052                 for(k = forwardMostMove-2;
8053                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8054                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8055                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8056                     k-=2)
8057                 {   int rights=0;
8058                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8059                         /* compare castling rights */
8060                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8061                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8062                                 rights++; /* King lost rights, while rook still had them */
8063                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8064                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8065                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8066                                    rights++; /* but at least one rook lost them */
8067                         }
8068                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8069                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8070                                 rights++;
8071                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8072                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8073                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8074                                    rights++;
8075                         }
8076                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8077                             && appData.drawRepeats > 1) {
8078                              /* adjudicate after user-specified nr of repeats */
8079                              int result = GameIsDrawn;
8080                              char *details = "XBoard adjudication: repetition draw";
8081                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8082                                 // [HGM] xiangqi: check for forbidden perpetuals
8083                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8084                                 for(m=forwardMostMove; m>k; m-=2) {
8085                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8086                                         ourPerpetual = 0; // the current mover did not always check
8087                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8088                                         hisPerpetual = 0; // the opponent did not always check
8089                                 }
8090                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8091                                                                         ourPerpetual, hisPerpetual);
8092                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8093                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8094                                     details = "Xboard adjudication: perpetual checking";
8095                                 } else
8096                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8097                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8098                                 } else
8099                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8100                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8101                                         result = BlackWins;
8102                                         details = "Xboard adjudication: repetition";
8103                                     }
8104                                 } else // it must be XQ
8105                                 // Now check for perpetual chases
8106                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8107                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8108                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8109                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8110                                         static char resdet[MSG_SIZ];
8111                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8112                                         details = resdet;
8113                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8114                                     } else
8115                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8116                                         break; // Abort repetition-checking loop.
8117                                 }
8118                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8119                              }
8120                              if(engineOpponent) {
8121                                SendToProgram("force\n", engineOpponent); // suppress reply
8122                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8123                              }
8124                              GameEnds( result, details, GE_XBOARD );
8125                              return 1;
8126                         }
8127                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8128                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8129                     }
8130                 }
8131
8132                 /* Now we test for 50-move draws. Determine ply count */
8133                 count = forwardMostMove;
8134                 /* look for last irreversble move */
8135                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8136                     count--;
8137                 /* if we hit starting position, add initial plies */
8138                 if( count == backwardMostMove )
8139                     count -= initialRulePlies;
8140                 count = forwardMostMove - count;
8141                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8142                         // adjust reversible move counter for checks in Xiangqi
8143                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8144                         if(i < backwardMostMove) i = backwardMostMove;
8145                         while(i <= forwardMostMove) {
8146                                 lastCheck = inCheck; // check evasion does not count
8147                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8148                                 if(inCheck || lastCheck) count--; // check does not count
8149                                 i++;
8150                         }
8151                 }
8152                 if( count >= 100)
8153                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8154                          /* this is used to judge if draw claims are legal */
8155                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8156                          if(engineOpponent) {
8157                            SendToProgram("force\n", engineOpponent); // suppress reply
8158                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8159                          }
8160                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8161                          return 1;
8162                 }
8163
8164                 /* if draw offer is pending, treat it as a draw claim
8165                  * when draw condition present, to allow engines a way to
8166                  * claim draws before making their move to avoid a race
8167                  * condition occurring after their move
8168                  */
8169                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8170                          char *p = NULL;
8171                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8172                              p = "Draw claim: 50-move rule";
8173                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8174                              p = "Draw claim: 3-fold repetition";
8175                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8176                              p = "Draw claim: insufficient mating material";
8177                          if( p != NULL && canAdjudicate) {
8178                              if(engineOpponent) {
8179                                SendToProgram("force\n", engineOpponent); // suppress reply
8180                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8181                              }
8182                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8183                              return 1;
8184                          }
8185                 }
8186
8187                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8188                     if(engineOpponent) {
8189                       SendToProgram("force\n", engineOpponent); // suppress reply
8190                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8191                     }
8192                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8193                     return 1;
8194                 }
8195         return 0;
8196 }
8197
8198 char *
8199 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8200 {   // [HGM] book: this routine intercepts moves to simulate book replies
8201     char *bookHit = NULL;
8202
8203     //first determine if the incoming move brings opponent into his book
8204     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8205         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8206     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8207     if(bookHit != NULL && !cps->bookSuspend) {
8208         // make sure opponent is not going to reply after receiving move to book position
8209         SendToProgram("force\n", cps);
8210         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8211     }
8212     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8213     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8214     // now arrange restart after book miss
8215     if(bookHit) {
8216         // after a book hit we never send 'go', and the code after the call to this routine
8217         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8218         char buf[MSG_SIZ], *move = bookHit;
8219         if(cps->useSAN) {
8220             int fromX, fromY, toX, toY;
8221             char promoChar;
8222             ChessMove moveType;
8223             move = buf + 30;
8224             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8225                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8226                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8227                                     PosFlags(forwardMostMove),
8228                                     fromY, fromX, toY, toX, promoChar, move);
8229             } else {
8230                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8231                 bookHit = NULL;
8232             }
8233         }
8234         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8235         SendToProgram(buf, cps);
8236         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8237     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8238         SendToProgram("go\n", cps);
8239         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8240     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8241         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8242             SendToProgram("go\n", cps);
8243         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8244     }
8245     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8246 }
8247
8248 int
8249 LoadError (char *errmess, ChessProgramState *cps)
8250 {   // unloads engine and switches back to -ncp mode if it was first
8251     if(cps->initDone) return FALSE;
8252     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8253     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8254     cps->pr = NoProc;
8255     if(cps == &first) {
8256         appData.noChessProgram = TRUE;
8257         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8258         gameMode = BeginningOfGame; ModeHighlight();
8259         SetNCPMode();
8260     }
8261     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8262     DisplayMessage("", ""); // erase waiting message
8263     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8264     return TRUE;
8265 }
8266
8267 char *savedMessage;
8268 ChessProgramState *savedState;
8269 void
8270 DeferredBookMove (void)
8271 {
8272         if(savedState->lastPing != savedState->lastPong)
8273                     ScheduleDelayedEvent(DeferredBookMove, 10);
8274         else
8275         HandleMachineMove(savedMessage, savedState);
8276 }
8277
8278 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8279 static ChessProgramState *stalledEngine;
8280 static char stashedInputMove[MSG_SIZ];
8281
8282 void
8283 HandleMachineMove (char *message, ChessProgramState *cps)
8284 {
8285     static char firstLeg[20];
8286     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8287     char realname[MSG_SIZ];
8288     int fromX, fromY, toX, toY;
8289     ChessMove moveType;
8290     char promoChar;
8291     char *p, *pv=buf1;
8292     int machineWhite, oldError;
8293     char *bookHit;
8294
8295     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8296         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8297         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8298             DisplayError(_("Invalid pairing from pairing engine"), 0);
8299             return;
8300         }
8301         pairingReceived = 1;
8302         NextMatchGame();
8303         return; // Skim the pairing messages here.
8304     }
8305
8306     oldError = cps->userError; cps->userError = 0;
8307
8308 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8309     /*
8310      * Kludge to ignore BEL characters
8311      */
8312     while (*message == '\007') message++;
8313
8314     /*
8315      * [HGM] engine debug message: ignore lines starting with '#' character
8316      */
8317     if(cps->debug && *message == '#') return;
8318
8319     /*
8320      * Look for book output
8321      */
8322     if (cps == &first && bookRequested) {
8323         if (message[0] == '\t' || message[0] == ' ') {
8324             /* Part of the book output is here; append it */
8325             strcat(bookOutput, message);
8326             strcat(bookOutput, "  \n");
8327             return;
8328         } else if (bookOutput[0] != NULLCHAR) {
8329             /* All of book output has arrived; display it */
8330             char *p = bookOutput;
8331             while (*p != NULLCHAR) {
8332                 if (*p == '\t') *p = ' ';
8333                 p++;
8334             }
8335             DisplayInformation(bookOutput);
8336             bookRequested = FALSE;
8337             /* Fall through to parse the current output */
8338         }
8339     }
8340
8341     /*
8342      * Look for machine move.
8343      */
8344     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8345         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8346     {
8347         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8348             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8349             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8350             stalledEngine = cps;
8351             if(appData.ponderNextMove) { // bring opponent out of ponder
8352                 if(gameMode == TwoMachinesPlay) {
8353                     if(cps->other->pause)
8354                         PauseEngine(cps->other);
8355                     else
8356                         SendToProgram("easy\n", cps->other);
8357                 }
8358             }
8359             StopClocks();
8360             return;
8361         }
8362
8363         /* This method is only useful on engines that support ping */
8364         if (cps->lastPing != cps->lastPong) {
8365           if (gameMode == BeginningOfGame) {
8366             /* Extra move from before last new; ignore */
8367             if (appData.debugMode) {
8368                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8369             }
8370           } else {
8371             if (appData.debugMode) {
8372                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8373                         cps->which, gameMode);
8374             }
8375
8376             SendToProgram("undo\n", cps);
8377           }
8378           return;
8379         }
8380
8381         switch (gameMode) {
8382           case BeginningOfGame:
8383             /* Extra move from before last reset; ignore */
8384             if (appData.debugMode) {
8385                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8386             }
8387             return;
8388
8389           case EndOfGame:
8390           case IcsIdle:
8391           default:
8392             /* Extra move after we tried to stop.  The mode test is
8393                not a reliable way of detecting this problem, but it's
8394                the best we can do on engines that don't support ping.
8395             */
8396             if (appData.debugMode) {
8397                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8398                         cps->which, gameMode);
8399             }
8400             SendToProgram("undo\n", cps);
8401             return;
8402
8403           case MachinePlaysWhite:
8404           case IcsPlayingWhite:
8405             machineWhite = TRUE;
8406             break;
8407
8408           case MachinePlaysBlack:
8409           case IcsPlayingBlack:
8410             machineWhite = FALSE;
8411             break;
8412
8413           case TwoMachinesPlay:
8414             machineWhite = (cps->twoMachinesColor[0] == 'w');
8415             break;
8416         }
8417         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8418             if (appData.debugMode) {
8419                 fprintf(debugFP,
8420                         "Ignoring move out of turn by %s, gameMode %d"
8421                         ", forwardMost %d\n",
8422                         cps->which, gameMode, forwardMostMove);
8423             }
8424             return;
8425         }
8426
8427         if(cps->alphaRank) AlphaRank(machineMove, 4);
8428
8429         // [HGM] lion: (some very limited) support for Alien protocol
8430         killX = killY = -1;
8431         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8432             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8433             return;
8434         } else if(firstLeg[0]) { // there was a previous leg;
8435             // only support case where same piece makes two step (and don't even test that!)
8436             char buf[20], *p = machineMove+1, *q = buf+1, f;
8437             safeStrCpy(buf, machineMove, 20);
8438             while(isdigit(*q)) q++; // find start of to-square
8439             safeStrCpy(machineMove, firstLeg, 20);
8440             while(isdigit(*p)) p++;
8441             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8442             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8443             firstLeg[0] = NULLCHAR;
8444         }
8445
8446         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8447                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8448             /* Machine move could not be parsed; ignore it. */
8449           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8450                     machineMove, _(cps->which));
8451             DisplayMoveError(buf1);
8452             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8453                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8454             if (gameMode == TwoMachinesPlay) {
8455               GameEnds(machineWhite ? BlackWins : WhiteWins,
8456                        buf1, GE_XBOARD);
8457             }
8458             return;
8459         }
8460
8461         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8462         /* So we have to redo legality test with true e.p. status here,  */
8463         /* to make sure an illegal e.p. capture does not slip through,   */
8464         /* to cause a forfeit on a justified illegal-move complaint      */
8465         /* of the opponent.                                              */
8466         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8467            ChessMove moveType;
8468            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8469                              fromY, fromX, toY, toX, promoChar);
8470             if(moveType == IllegalMove) {
8471               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8472                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8473                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8474                            buf1, GE_XBOARD);
8475                 return;
8476            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8477            /* [HGM] Kludge to handle engines that send FRC-style castling
8478               when they shouldn't (like TSCP-Gothic) */
8479            switch(moveType) {
8480              case WhiteASideCastleFR:
8481              case BlackASideCastleFR:
8482                toX+=2;
8483                currentMoveString[2]++;
8484                break;
8485              case WhiteHSideCastleFR:
8486              case BlackHSideCastleFR:
8487                toX--;
8488                currentMoveString[2]--;
8489                break;
8490              default: ; // nothing to do, but suppresses warning of pedantic compilers
8491            }
8492         }
8493         hintRequested = FALSE;
8494         lastHint[0] = NULLCHAR;
8495         bookRequested = FALSE;
8496         /* Program may be pondering now */
8497         cps->maybeThinking = TRUE;
8498         if (cps->sendTime == 2) cps->sendTime = 1;
8499         if (cps->offeredDraw) cps->offeredDraw--;
8500
8501         /* [AS] Save move info*/
8502         pvInfoList[ forwardMostMove ].score = programStats.score;
8503         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8504         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8505
8506         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8507
8508         /* Test suites abort the 'game' after one move */
8509         if(*appData.finger) {
8510            static FILE *f;
8511            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8512            if(!f) f = fopen(appData.finger, "w");
8513            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8514            else { DisplayFatalError("Bad output file", errno, 0); return; }
8515            free(fen);
8516            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8517         }
8518
8519         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8520         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8521             int count = 0;
8522
8523             while( count < adjudicateLossPlies ) {
8524                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8525
8526                 if( count & 1 ) {
8527                     score = -score; /* Flip score for winning side */
8528                 }
8529
8530                 if( score > adjudicateLossThreshold ) {
8531                     break;
8532                 }
8533
8534                 count++;
8535             }
8536
8537             if( count >= adjudicateLossPlies ) {
8538                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8539
8540                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8541                     "Xboard adjudication",
8542                     GE_XBOARD );
8543
8544                 return;
8545             }
8546         }
8547
8548         if(Adjudicate(cps)) {
8549             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8550             return; // [HGM] adjudicate: for all automatic game ends
8551         }
8552
8553 #if ZIPPY
8554         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8555             first.initDone) {
8556           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8557                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8558                 SendToICS("draw ");
8559                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8560           }
8561           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8562           ics_user_moved = 1;
8563           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8564                 char buf[3*MSG_SIZ];
8565
8566                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8567                         programStats.score / 100.,
8568                         programStats.depth,
8569                         programStats.time / 100.,
8570                         (unsigned int)programStats.nodes,
8571                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8572                         programStats.movelist);
8573                 SendToICS(buf);
8574 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8575           }
8576         }
8577 #endif
8578
8579         /* [AS] Clear stats for next move */
8580         ClearProgramStats();
8581         thinkOutput[0] = NULLCHAR;
8582         hiddenThinkOutputState = 0;
8583
8584         bookHit = NULL;
8585         if (gameMode == TwoMachinesPlay) {
8586             /* [HGM] relaying draw offers moved to after reception of move */
8587             /* and interpreting offer as claim if it brings draw condition */
8588             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8589                 SendToProgram("draw\n", cps->other);
8590             }
8591             if (cps->other->sendTime) {
8592                 SendTimeRemaining(cps->other,
8593                                   cps->other->twoMachinesColor[0] == 'w');
8594             }
8595             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8596             if (firstMove && !bookHit) {
8597                 firstMove = FALSE;
8598                 if (cps->other->useColors) {
8599                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8600                 }
8601                 SendToProgram("go\n", cps->other);
8602             }
8603             cps->other->maybeThinking = TRUE;
8604         }
8605
8606         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8607
8608         if (!pausing && appData.ringBellAfterMoves) {
8609             RingBell();
8610         }
8611
8612         /*
8613          * Reenable menu items that were disabled while
8614          * machine was thinking
8615          */
8616         if (gameMode != TwoMachinesPlay)
8617             SetUserThinkingEnables();
8618
8619         // [HGM] book: after book hit opponent has received move and is now in force mode
8620         // force the book reply into it, and then fake that it outputted this move by jumping
8621         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8622         if(bookHit) {
8623                 static char bookMove[MSG_SIZ]; // a bit generous?
8624
8625                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8626                 strcat(bookMove, bookHit);
8627                 message = bookMove;
8628                 cps = cps->other;
8629                 programStats.nodes = programStats.depth = programStats.time =
8630                 programStats.score = programStats.got_only_move = 0;
8631                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8632
8633                 if(cps->lastPing != cps->lastPong) {
8634                     savedMessage = message; // args for deferred call
8635                     savedState = cps;
8636                     ScheduleDelayedEvent(DeferredBookMove, 10);
8637                     return;
8638                 }
8639                 goto FakeBookMove;
8640         }
8641
8642         return;
8643     }
8644
8645     /* Set special modes for chess engines.  Later something general
8646      *  could be added here; for now there is just one kludge feature,
8647      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8648      *  when "xboard" is given as an interactive command.
8649      */
8650     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8651         cps->useSigint = FALSE;
8652         cps->useSigterm = FALSE;
8653     }
8654     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8655       ParseFeatures(message+8, cps);
8656       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8657     }
8658
8659     if (!strncmp(message, "setup ", 6) && 
8660         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8661                                         ) { // [HGM] allow first engine to define opening position
8662       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8663       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8664       *buf = NULLCHAR;
8665       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8666       if(startedFromSetupPosition) return;
8667       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8668       if(dummy >= 3) {
8669         while(message[s] && message[s++] != ' ');
8670         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8671            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8672             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8673             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8674           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8675           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8676         }
8677       }
8678       ParseFEN(boards[0], &dummy, message+s, FALSE);
8679       DrawPosition(TRUE, boards[0]);
8680       startedFromSetupPosition = TRUE;
8681       return;
8682     }
8683     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8684      * want this, I was asked to put it in, and obliged.
8685      */
8686     if (!strncmp(message, "setboard ", 9)) {
8687         Board initial_position;
8688
8689         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8690
8691         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8692             DisplayError(_("Bad FEN received from engine"), 0);
8693             return ;
8694         } else {
8695            Reset(TRUE, FALSE);
8696            CopyBoard(boards[0], initial_position);
8697            initialRulePlies = FENrulePlies;
8698            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8699            else gameMode = MachinePlaysBlack;
8700            DrawPosition(FALSE, boards[currentMove]);
8701         }
8702         return;
8703     }
8704
8705     /*
8706      * Look for communication commands
8707      */
8708     if (!strncmp(message, "telluser ", 9)) {
8709         if(message[9] == '\\' && message[10] == '\\')
8710             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8711         PlayTellSound();
8712         DisplayNote(message + 9);
8713         return;
8714     }
8715     if (!strncmp(message, "tellusererror ", 14)) {
8716         cps->userError = 1;
8717         if(message[14] == '\\' && message[15] == '\\')
8718             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8719         PlayTellSound();
8720         DisplayError(message + 14, 0);
8721         return;
8722     }
8723     if (!strncmp(message, "tellopponent ", 13)) {
8724       if (appData.icsActive) {
8725         if (loggedOn) {
8726           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8727           SendToICS(buf1);
8728         }
8729       } else {
8730         DisplayNote(message + 13);
8731       }
8732       return;
8733     }
8734     if (!strncmp(message, "tellothers ", 11)) {
8735       if (appData.icsActive) {
8736         if (loggedOn) {
8737           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8738           SendToICS(buf1);
8739         }
8740       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8741       return;
8742     }
8743     if (!strncmp(message, "tellall ", 8)) {
8744       if (appData.icsActive) {
8745         if (loggedOn) {
8746           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8747           SendToICS(buf1);
8748         }
8749       } else {
8750         DisplayNote(message + 8);
8751       }
8752       return;
8753     }
8754     if (strncmp(message, "warning", 7) == 0) {
8755         /* Undocumented feature, use tellusererror in new code */
8756         DisplayError(message, 0);
8757         return;
8758     }
8759     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8760         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8761         strcat(realname, " query");
8762         AskQuestion(realname, buf2, buf1, cps->pr);
8763         return;
8764     }
8765     /* Commands from the engine directly to ICS.  We don't allow these to be
8766      *  sent until we are logged on. Crafty kibitzes have been known to
8767      *  interfere with the login process.
8768      */
8769     if (loggedOn) {
8770         if (!strncmp(message, "tellics ", 8)) {
8771             SendToICS(message + 8);
8772             SendToICS("\n");
8773             return;
8774         }
8775         if (!strncmp(message, "tellicsnoalias ", 15)) {
8776             SendToICS(ics_prefix);
8777             SendToICS(message + 15);
8778             SendToICS("\n");
8779             return;
8780         }
8781         /* The following are for backward compatibility only */
8782         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8783             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8784             SendToICS(ics_prefix);
8785             SendToICS(message);
8786             SendToICS("\n");
8787             return;
8788         }
8789     }
8790     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8791         return;
8792     }
8793     if(!strncmp(message, "highlight ", 10)) {
8794         if(appData.testLegality && appData.markers) return;
8795         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8796         return;
8797     }
8798     if(!strncmp(message, "click ", 6)) {
8799         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8800         if(appData.testLegality || !appData.oneClick) return;
8801         sscanf(message+6, "%c%d%c", &f, &y, &c);
8802         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8803         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8804         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8805         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8806         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8807         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8808             LeftClick(Release, lastLeftX, lastLeftY);
8809         controlKey  = (c == ',');
8810         LeftClick(Press, x, y);
8811         LeftClick(Release, x, y);
8812         first.highlight = f;
8813         return;
8814     }
8815     /*
8816      * If the move is illegal, cancel it and redraw the board.
8817      * Also deal with other error cases.  Matching is rather loose
8818      * here to accommodate engines written before the spec.
8819      */
8820     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8821         strncmp(message, "Error", 5) == 0) {
8822         if (StrStr(message, "name") ||
8823             StrStr(message, "rating") || StrStr(message, "?") ||
8824             StrStr(message, "result") || StrStr(message, "board") ||
8825             StrStr(message, "bk") || StrStr(message, "computer") ||
8826             StrStr(message, "variant") || StrStr(message, "hint") ||
8827             StrStr(message, "random") || StrStr(message, "depth") ||
8828             StrStr(message, "accepted")) {
8829             return;
8830         }
8831         if (StrStr(message, "protover")) {
8832           /* Program is responding to input, so it's apparently done
8833              initializing, and this error message indicates it is
8834              protocol version 1.  So we don't need to wait any longer
8835              for it to initialize and send feature commands. */
8836           FeatureDone(cps, 1);
8837           cps->protocolVersion = 1;
8838           return;
8839         }
8840         cps->maybeThinking = FALSE;
8841
8842         if (StrStr(message, "draw")) {
8843             /* Program doesn't have "draw" command */
8844             cps->sendDrawOffers = 0;
8845             return;
8846         }
8847         if (cps->sendTime != 1 &&
8848             (StrStr(message, "time") || StrStr(message, "otim"))) {
8849           /* Program apparently doesn't have "time" or "otim" command */
8850           cps->sendTime = 0;
8851           return;
8852         }
8853         if (StrStr(message, "analyze")) {
8854             cps->analysisSupport = FALSE;
8855             cps->analyzing = FALSE;
8856 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8857             EditGameEvent(); // [HGM] try to preserve loaded game
8858             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8859             DisplayError(buf2, 0);
8860             return;
8861         }
8862         if (StrStr(message, "(no matching move)st")) {
8863           /* Special kludge for GNU Chess 4 only */
8864           cps->stKludge = TRUE;
8865           SendTimeControl(cps, movesPerSession, timeControl,
8866                           timeIncrement, appData.searchDepth,
8867                           searchTime);
8868           return;
8869         }
8870         if (StrStr(message, "(no matching move)sd")) {
8871           /* Special kludge for GNU Chess 4 only */
8872           cps->sdKludge = TRUE;
8873           SendTimeControl(cps, movesPerSession, timeControl,
8874                           timeIncrement, appData.searchDepth,
8875                           searchTime);
8876           return;
8877         }
8878         if (!StrStr(message, "llegal")) {
8879             return;
8880         }
8881         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8882             gameMode == IcsIdle) return;
8883         if (forwardMostMove <= backwardMostMove) return;
8884         if (pausing) PauseEvent();
8885       if(appData.forceIllegal) {
8886             // [HGM] illegal: machine refused move; force position after move into it
8887           SendToProgram("force\n", cps);
8888           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8889                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8890                 // when black is to move, while there might be nothing on a2 or black
8891                 // might already have the move. So send the board as if white has the move.
8892                 // But first we must change the stm of the engine, as it refused the last move
8893                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8894                 if(WhiteOnMove(forwardMostMove)) {
8895                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8896                     SendBoard(cps, forwardMostMove); // kludgeless board
8897                 } else {
8898                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8899                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8900                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8901                 }
8902           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8903             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8904                  gameMode == TwoMachinesPlay)
8905               SendToProgram("go\n", cps);
8906             return;
8907       } else
8908         if (gameMode == PlayFromGameFile) {
8909             /* Stop reading this game file */
8910             gameMode = EditGame;
8911             ModeHighlight();
8912         }
8913         /* [HGM] illegal-move claim should forfeit game when Xboard */
8914         /* only passes fully legal moves                            */
8915         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8916             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8917                                 "False illegal-move claim", GE_XBOARD );
8918             return; // do not take back move we tested as valid
8919         }
8920         currentMove = forwardMostMove-1;
8921         DisplayMove(currentMove-1); /* before DisplayMoveError */
8922         SwitchClocks(forwardMostMove-1); // [HGM] race
8923         DisplayBothClocks();
8924         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8925                 parseList[currentMove], _(cps->which));
8926         DisplayMoveError(buf1);
8927         DrawPosition(FALSE, boards[currentMove]);
8928
8929         SetUserThinkingEnables();
8930         return;
8931     }
8932     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8933         /* Program has a broken "time" command that
8934            outputs a string not ending in newline.
8935            Don't use it. */
8936         cps->sendTime = 0;
8937     }
8938
8939     /*
8940      * If chess program startup fails, exit with an error message.
8941      * Attempts to recover here are futile. [HGM] Well, we try anyway
8942      */
8943     if ((StrStr(message, "unknown host") != NULL)
8944         || (StrStr(message, "No remote directory") != NULL)
8945         || (StrStr(message, "not found") != NULL)
8946         || (StrStr(message, "No such file") != NULL)
8947         || (StrStr(message, "can't alloc") != NULL)
8948         || (StrStr(message, "Permission denied") != NULL)) {
8949
8950         cps->maybeThinking = FALSE;
8951         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8952                 _(cps->which), cps->program, cps->host, message);
8953         RemoveInputSource(cps->isr);
8954         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8955             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8956             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8957         }
8958         return;
8959     }
8960
8961     /*
8962      * Look for hint output
8963      */
8964     if (sscanf(message, "Hint: %s", buf1) == 1) {
8965         if (cps == &first && hintRequested) {
8966             hintRequested = FALSE;
8967             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8968                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8969                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8970                                     PosFlags(forwardMostMove),
8971                                     fromY, fromX, toY, toX, promoChar, buf1);
8972                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8973                 DisplayInformation(buf2);
8974             } else {
8975                 /* Hint move could not be parsed!? */
8976               snprintf(buf2, sizeof(buf2),
8977                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8978                         buf1, _(cps->which));
8979                 DisplayError(buf2, 0);
8980             }
8981         } else {
8982           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8983         }
8984         return;
8985     }
8986
8987     /*
8988      * Ignore other messages if game is not in progress
8989      */
8990     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8991         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8992
8993     /*
8994      * look for win, lose, draw, or draw offer
8995      */
8996     if (strncmp(message, "1-0", 3) == 0) {
8997         char *p, *q, *r = "";
8998         p = strchr(message, '{');
8999         if (p) {
9000             q = strchr(p, '}');
9001             if (q) {
9002                 *q = NULLCHAR;
9003                 r = p + 1;
9004             }
9005         }
9006         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9007         return;
9008     } else if (strncmp(message, "0-1", 3) == 0) {
9009         char *p, *q, *r = "";
9010         p = strchr(message, '{');
9011         if (p) {
9012             q = strchr(p, '}');
9013             if (q) {
9014                 *q = NULLCHAR;
9015                 r = p + 1;
9016             }
9017         }
9018         /* Kludge for Arasan 4.1 bug */
9019         if (strcmp(r, "Black resigns") == 0) {
9020             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9021             return;
9022         }
9023         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9024         return;
9025     } else if (strncmp(message, "1/2", 3) == 0) {
9026         char *p, *q, *r = "";
9027         p = strchr(message, '{');
9028         if (p) {
9029             q = strchr(p, '}');
9030             if (q) {
9031                 *q = NULLCHAR;
9032                 r = p + 1;
9033             }
9034         }
9035
9036         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9037         return;
9038
9039     } else if (strncmp(message, "White resign", 12) == 0) {
9040         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9041         return;
9042     } else if (strncmp(message, "Black resign", 12) == 0) {
9043         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9044         return;
9045     } else if (strncmp(message, "White matches", 13) == 0 ||
9046                strncmp(message, "Black matches", 13) == 0   ) {
9047         /* [HGM] ignore GNUShogi noises */
9048         return;
9049     } else if (strncmp(message, "White", 5) == 0 &&
9050                message[5] != '(' &&
9051                StrStr(message, "Black") == NULL) {
9052         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9053         return;
9054     } else if (strncmp(message, "Black", 5) == 0 &&
9055                message[5] != '(') {
9056         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9057         return;
9058     } else if (strcmp(message, "resign") == 0 ||
9059                strcmp(message, "computer resigns") == 0) {
9060         switch (gameMode) {
9061           case MachinePlaysBlack:
9062           case IcsPlayingBlack:
9063             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9064             break;
9065           case MachinePlaysWhite:
9066           case IcsPlayingWhite:
9067             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9068             break;
9069           case TwoMachinesPlay:
9070             if (cps->twoMachinesColor[0] == 'w')
9071               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9072             else
9073               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9074             break;
9075           default:
9076             /* can't happen */
9077             break;
9078         }
9079         return;
9080     } else if (strncmp(message, "opponent mates", 14) == 0) {
9081         switch (gameMode) {
9082           case MachinePlaysBlack:
9083           case IcsPlayingBlack:
9084             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9085             break;
9086           case MachinePlaysWhite:
9087           case IcsPlayingWhite:
9088             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9089             break;
9090           case TwoMachinesPlay:
9091             if (cps->twoMachinesColor[0] == 'w')
9092               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9093             else
9094               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9095             break;
9096           default:
9097             /* can't happen */
9098             break;
9099         }
9100         return;
9101     } else if (strncmp(message, "computer mates", 14) == 0) {
9102         switch (gameMode) {
9103           case MachinePlaysBlack:
9104           case IcsPlayingBlack:
9105             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9106             break;
9107           case MachinePlaysWhite:
9108           case IcsPlayingWhite:
9109             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9110             break;
9111           case TwoMachinesPlay:
9112             if (cps->twoMachinesColor[0] == 'w')
9113               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9114             else
9115               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9116             break;
9117           default:
9118             /* can't happen */
9119             break;
9120         }
9121         return;
9122     } else if (strncmp(message, "checkmate", 9) == 0) {
9123         if (WhiteOnMove(forwardMostMove)) {
9124             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9125         } else {
9126             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9127         }
9128         return;
9129     } else if (strstr(message, "Draw") != NULL ||
9130                strstr(message, "game is a draw") != NULL) {
9131         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9132         return;
9133     } else if (strstr(message, "offer") != NULL &&
9134                strstr(message, "draw") != NULL) {
9135 #if ZIPPY
9136         if (appData.zippyPlay && first.initDone) {
9137             /* Relay offer to ICS */
9138             SendToICS(ics_prefix);
9139             SendToICS("draw\n");
9140         }
9141 #endif
9142         cps->offeredDraw = 2; /* valid until this engine moves twice */
9143         if (gameMode == TwoMachinesPlay) {
9144             if (cps->other->offeredDraw) {
9145                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9146             /* [HGM] in two-machine mode we delay relaying draw offer      */
9147             /* until after we also have move, to see if it is really claim */
9148             }
9149         } else if (gameMode == MachinePlaysWhite ||
9150                    gameMode == MachinePlaysBlack) {
9151           if (userOfferedDraw) {
9152             DisplayInformation(_("Machine accepts your draw offer"));
9153             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9154           } else {
9155             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9156           }
9157         }
9158     }
9159
9160
9161     /*
9162      * Look for thinking output
9163      */
9164     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9165           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9166                                 ) {
9167         int plylev, mvleft, mvtot, curscore, time;
9168         char mvname[MOVE_LEN];
9169         u64 nodes; // [DM]
9170         char plyext;
9171         int ignore = FALSE;
9172         int prefixHint = FALSE;
9173         mvname[0] = NULLCHAR;
9174
9175         switch (gameMode) {
9176           case MachinePlaysBlack:
9177           case IcsPlayingBlack:
9178             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9179             break;
9180           case MachinePlaysWhite:
9181           case IcsPlayingWhite:
9182             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9183             break;
9184           case AnalyzeMode:
9185           case AnalyzeFile:
9186             break;
9187           case IcsObserving: /* [DM] icsEngineAnalyze */
9188             if (!appData.icsEngineAnalyze) ignore = TRUE;
9189             break;
9190           case TwoMachinesPlay:
9191             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9192                 ignore = TRUE;
9193             }
9194             break;
9195           default:
9196             ignore = TRUE;
9197             break;
9198         }
9199
9200         if (!ignore) {
9201             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9202             buf1[0] = NULLCHAR;
9203             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9204                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9205
9206                 if (plyext != ' ' && plyext != '\t') {
9207                     time *= 100;
9208                 }
9209
9210                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9211                 if( cps->scoreIsAbsolute &&
9212                     ( gameMode == MachinePlaysBlack ||
9213                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9214                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9215                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9216                      !WhiteOnMove(currentMove)
9217                     ) )
9218                 {
9219                     curscore = -curscore;
9220                 }
9221
9222                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9223
9224                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9225                         char buf[MSG_SIZ];
9226                         FILE *f;
9227                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9228                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9229                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9230                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9231                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9232                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9233                                 fclose(f);
9234                         }
9235                         else
9236                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9237                           DisplayError(_("failed writing PV"), 0);
9238                 }
9239
9240                 tempStats.depth = plylev;
9241                 tempStats.nodes = nodes;
9242                 tempStats.time = time;
9243                 tempStats.score = curscore;
9244                 tempStats.got_only_move = 0;
9245
9246                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9247                         int ticklen;
9248
9249                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9250                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9251                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9252                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9253                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9254                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9255                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9256                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9257                 }
9258
9259                 /* Buffer overflow protection */
9260                 if (pv[0] != NULLCHAR) {
9261                     if (strlen(pv) >= sizeof(tempStats.movelist)
9262                         && appData.debugMode) {
9263                         fprintf(debugFP,
9264                                 "PV is too long; using the first %u bytes.\n",
9265                                 (unsigned) sizeof(tempStats.movelist) - 1);
9266                     }
9267
9268                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9269                 } else {
9270                     sprintf(tempStats.movelist, " no PV\n");
9271                 }
9272
9273                 if (tempStats.seen_stat) {
9274                     tempStats.ok_to_send = 1;
9275                 }
9276
9277                 if (strchr(tempStats.movelist, '(') != NULL) {
9278                     tempStats.line_is_book = 1;
9279                     tempStats.nr_moves = 0;
9280                     tempStats.moves_left = 0;
9281                 } else {
9282                     tempStats.line_is_book = 0;
9283                 }
9284
9285                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9286                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9287
9288                 SendProgramStatsToFrontend( cps, &tempStats );
9289
9290                 /*
9291                     [AS] Protect the thinkOutput buffer from overflow... this
9292                     is only useful if buf1 hasn't overflowed first!
9293                 */
9294                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9295                          plylev,
9296                          (gameMode == TwoMachinesPlay ?
9297                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9298                          ((double) curscore) / 100.0,
9299                          prefixHint ? lastHint : "",
9300                          prefixHint ? " " : "" );
9301
9302                 if( buf1[0] != NULLCHAR ) {
9303                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9304
9305                     if( strlen(pv) > max_len ) {
9306                         if( appData.debugMode) {
9307                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9308                         }
9309                         pv[max_len+1] = '\0';
9310                     }
9311
9312                     strcat( thinkOutput, pv);
9313                 }
9314
9315                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9316                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9317                     DisplayMove(currentMove - 1);
9318                 }
9319                 return;
9320
9321             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9322                 /* crafty (9.25+) says "(only move) <move>"
9323                  * if there is only 1 legal move
9324                  */
9325                 sscanf(p, "(only move) %s", buf1);
9326                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9327                 sprintf(programStats.movelist, "%s (only move)", buf1);
9328                 programStats.depth = 1;
9329                 programStats.nr_moves = 1;
9330                 programStats.moves_left = 1;
9331                 programStats.nodes = 1;
9332                 programStats.time = 1;
9333                 programStats.got_only_move = 1;
9334
9335                 /* Not really, but we also use this member to
9336                    mean "line isn't going to change" (Crafty
9337                    isn't searching, so stats won't change) */
9338                 programStats.line_is_book = 1;
9339
9340                 SendProgramStatsToFrontend( cps, &programStats );
9341
9342                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9343                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9344                     DisplayMove(currentMove - 1);
9345                 }
9346                 return;
9347             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9348                               &time, &nodes, &plylev, &mvleft,
9349                               &mvtot, mvname) >= 5) {
9350                 /* The stat01: line is from Crafty (9.29+) in response
9351                    to the "." command */
9352                 programStats.seen_stat = 1;
9353                 cps->maybeThinking = TRUE;
9354
9355                 if (programStats.got_only_move || !appData.periodicUpdates)
9356                   return;
9357
9358                 programStats.depth = plylev;
9359                 programStats.time = time;
9360                 programStats.nodes = nodes;
9361                 programStats.moves_left = mvleft;
9362                 programStats.nr_moves = mvtot;
9363                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9364                 programStats.ok_to_send = 1;
9365                 programStats.movelist[0] = '\0';
9366
9367                 SendProgramStatsToFrontend( cps, &programStats );
9368
9369                 return;
9370
9371             } else if (strncmp(message,"++",2) == 0) {
9372                 /* Crafty 9.29+ outputs this */
9373                 programStats.got_fail = 2;
9374                 return;
9375
9376             } else if (strncmp(message,"--",2) == 0) {
9377                 /* Crafty 9.29+ outputs this */
9378                 programStats.got_fail = 1;
9379                 return;
9380
9381             } else if (thinkOutput[0] != NULLCHAR &&
9382                        strncmp(message, "    ", 4) == 0) {
9383                 unsigned message_len;
9384
9385                 p = message;
9386                 while (*p && *p == ' ') p++;
9387
9388                 message_len = strlen( p );
9389
9390                 /* [AS] Avoid buffer overflow */
9391                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9392                     strcat(thinkOutput, " ");
9393                     strcat(thinkOutput, p);
9394                 }
9395
9396                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9397                     strcat(programStats.movelist, " ");
9398                     strcat(programStats.movelist, p);
9399                 }
9400
9401                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9402                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9403                     DisplayMove(currentMove - 1);
9404                 }
9405                 return;
9406             }
9407         }
9408         else {
9409             buf1[0] = NULLCHAR;
9410
9411             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9412                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9413             {
9414                 ChessProgramStats cpstats;
9415
9416                 if (plyext != ' ' && plyext != '\t') {
9417                     time *= 100;
9418                 }
9419
9420                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9421                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9422                     curscore = -curscore;
9423                 }
9424
9425                 cpstats.depth = plylev;
9426                 cpstats.nodes = nodes;
9427                 cpstats.time = time;
9428                 cpstats.score = curscore;
9429                 cpstats.got_only_move = 0;
9430                 cpstats.movelist[0] = '\0';
9431
9432                 if (buf1[0] != NULLCHAR) {
9433                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9434                 }
9435
9436                 cpstats.ok_to_send = 0;
9437                 cpstats.line_is_book = 0;
9438                 cpstats.nr_moves = 0;
9439                 cpstats.moves_left = 0;
9440
9441                 SendProgramStatsToFrontend( cps, &cpstats );
9442             }
9443         }
9444     }
9445 }
9446
9447
9448 /* Parse a game score from the character string "game", and
9449    record it as the history of the current game.  The game
9450    score is NOT assumed to start from the standard position.
9451    The display is not updated in any way.
9452    */
9453 void
9454 ParseGameHistory (char *game)
9455 {
9456     ChessMove moveType;
9457     int fromX, fromY, toX, toY, boardIndex;
9458     char promoChar;
9459     char *p, *q;
9460     char buf[MSG_SIZ];
9461
9462     if (appData.debugMode)
9463       fprintf(debugFP, "Parsing game history: %s\n", game);
9464
9465     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9466     gameInfo.site = StrSave(appData.icsHost);
9467     gameInfo.date = PGNDate();
9468     gameInfo.round = StrSave("-");
9469
9470     /* Parse out names of players */
9471     while (*game == ' ') game++;
9472     p = buf;
9473     while (*game != ' ') *p++ = *game++;
9474     *p = NULLCHAR;
9475     gameInfo.white = StrSave(buf);
9476     while (*game == ' ') game++;
9477     p = buf;
9478     while (*game != ' ' && *game != '\n') *p++ = *game++;
9479     *p = NULLCHAR;
9480     gameInfo.black = StrSave(buf);
9481
9482     /* Parse moves */
9483     boardIndex = blackPlaysFirst ? 1 : 0;
9484     yynewstr(game);
9485     for (;;) {
9486         yyboardindex = boardIndex;
9487         moveType = (ChessMove) Myylex();
9488         switch (moveType) {
9489           case IllegalMove:             /* maybe suicide chess, etc. */
9490   if (appData.debugMode) {
9491     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9492     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9493     setbuf(debugFP, NULL);
9494   }
9495           case WhitePromotion:
9496           case BlackPromotion:
9497           case WhiteNonPromotion:
9498           case BlackNonPromotion:
9499           case NormalMove:
9500           case WhiteCapturesEnPassant:
9501           case BlackCapturesEnPassant:
9502           case WhiteKingSideCastle:
9503           case WhiteQueenSideCastle:
9504           case BlackKingSideCastle:
9505           case BlackQueenSideCastle:
9506           case WhiteKingSideCastleWild:
9507           case WhiteQueenSideCastleWild:
9508           case BlackKingSideCastleWild:
9509           case BlackQueenSideCastleWild:
9510           /* PUSH Fabien */
9511           case WhiteHSideCastleFR:
9512           case WhiteASideCastleFR:
9513           case BlackHSideCastleFR:
9514           case BlackASideCastleFR:
9515           /* POP Fabien */
9516             fromX = currentMoveString[0] - AAA;
9517             fromY = currentMoveString[1] - ONE;
9518             toX = currentMoveString[2] - AAA;
9519             toY = currentMoveString[3] - ONE;
9520             promoChar = currentMoveString[4];
9521             break;
9522           case WhiteDrop:
9523           case BlackDrop:
9524             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9525             fromX = moveType == WhiteDrop ?
9526               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9527             (int) CharToPiece(ToLower(currentMoveString[0]));
9528             fromY = DROP_RANK;
9529             toX = currentMoveString[2] - AAA;
9530             toY = currentMoveString[3] - ONE;
9531             promoChar = NULLCHAR;
9532             break;
9533           case AmbiguousMove:
9534             /* bug? */
9535             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9536   if (appData.debugMode) {
9537     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9538     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9539     setbuf(debugFP, NULL);
9540   }
9541             DisplayError(buf, 0);
9542             return;
9543           case ImpossibleMove:
9544             /* bug? */
9545             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9546   if (appData.debugMode) {
9547     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9548     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9549     setbuf(debugFP, NULL);
9550   }
9551             DisplayError(buf, 0);
9552             return;
9553           case EndOfFile:
9554             if (boardIndex < backwardMostMove) {
9555                 /* Oops, gap.  How did that happen? */
9556                 DisplayError(_("Gap in move list"), 0);
9557                 return;
9558             }
9559             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9560             if (boardIndex > forwardMostMove) {
9561                 forwardMostMove = boardIndex;
9562             }
9563             return;
9564           case ElapsedTime:
9565             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9566                 strcat(parseList[boardIndex-1], " ");
9567                 strcat(parseList[boardIndex-1], yy_text);
9568             }
9569             continue;
9570           case Comment:
9571           case PGNTag:
9572           case NAG:
9573           default:
9574             /* ignore */
9575             continue;
9576           case WhiteWins:
9577           case BlackWins:
9578           case GameIsDrawn:
9579           case GameUnfinished:
9580             if (gameMode == IcsExamining) {
9581                 if (boardIndex < backwardMostMove) {
9582                     /* Oops, gap.  How did that happen? */
9583                     return;
9584                 }
9585                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9586                 return;
9587             }
9588             gameInfo.result = moveType;
9589             p = strchr(yy_text, '{');
9590             if (p == NULL) p = strchr(yy_text, '(');
9591             if (p == NULL) {
9592                 p = yy_text;
9593                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9594             } else {
9595                 q = strchr(p, *p == '{' ? '}' : ')');
9596                 if (q != NULL) *q = NULLCHAR;
9597                 p++;
9598             }
9599             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9600             gameInfo.resultDetails = StrSave(p);
9601             continue;
9602         }
9603         if (boardIndex >= forwardMostMove &&
9604             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9605             backwardMostMove = blackPlaysFirst ? 1 : 0;
9606             return;
9607         }
9608         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9609                                  fromY, fromX, toY, toX, promoChar,
9610                                  parseList[boardIndex]);
9611         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9612         /* currentMoveString is set as a side-effect of yylex */
9613         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9614         strcat(moveList[boardIndex], "\n");
9615         boardIndex++;
9616         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9617         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9618           case MT_NONE:
9619           case MT_STALEMATE:
9620           default:
9621             break;
9622           case MT_CHECK:
9623             if(gameInfo.variant != VariantShogi)
9624                 strcat(parseList[boardIndex - 1], "+");
9625             break;
9626           case MT_CHECKMATE:
9627           case MT_STAINMATE:
9628             strcat(parseList[boardIndex - 1], "#");
9629             break;
9630         }
9631     }
9632 }
9633
9634
9635 /* Apply a move to the given board  */
9636 void
9637 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9638 {
9639   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9640   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9641
9642     /* [HGM] compute & store e.p. status and castling rights for new position */
9643     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9644
9645       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9646       oldEP = (signed char)board[EP_STATUS];
9647       board[EP_STATUS] = EP_NONE;
9648
9649   if (fromY == DROP_RANK) {
9650         /* must be first */
9651         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9652             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9653             return;
9654         }
9655         piece = board[toY][toX] = (ChessSquare) fromX;
9656   } else {
9657       ChessSquare victim;
9658       int i;
9659
9660       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9661            victim = board[killY][killX],
9662            board[killY][killX] = EmptySquare,
9663            board[EP_STATUS] = EP_CAPTURE;
9664
9665       if( board[toY][toX] != EmptySquare ) {
9666            board[EP_STATUS] = EP_CAPTURE;
9667            if( (fromX != toX || fromY != toY) && // not igui!
9668                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9669                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9670                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9671            }
9672       }
9673
9674       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9675            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9676                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9677       } else
9678       if( board[fromY][fromX] == WhitePawn ) {
9679            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9680                board[EP_STATUS] = EP_PAWN_MOVE;
9681            if( toY-fromY==2) {
9682                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9683                         gameInfo.variant != VariantBerolina || toX < fromX)
9684                       board[EP_STATUS] = toX | berolina;
9685                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9686                         gameInfo.variant != VariantBerolina || toX > fromX)
9687                       board[EP_STATUS] = toX;
9688            }
9689       } else
9690       if( board[fromY][fromX] == BlackPawn ) {
9691            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9692                board[EP_STATUS] = EP_PAWN_MOVE;
9693            if( toY-fromY== -2) {
9694                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9695                         gameInfo.variant != VariantBerolina || toX < fromX)
9696                       board[EP_STATUS] = toX | berolina;
9697                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9698                         gameInfo.variant != VariantBerolina || toX > fromX)
9699                       board[EP_STATUS] = toX;
9700            }
9701        }
9702
9703        for(i=0; i<nrCastlingRights; i++) {
9704            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9705               board[CASTLING][i] == toX   && castlingRank[i] == toY
9706              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9707        }
9708
9709        if(gameInfo.variant == VariantSChess) { // update virginity
9710            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9711            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9712            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9713            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9714        }
9715
9716      if (fromX == toX && fromY == toY) return;
9717
9718      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9719      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9720      if(gameInfo.variant == VariantKnightmate)
9721          king += (int) WhiteUnicorn - (int) WhiteKing;
9722
9723     /* Code added by Tord: */
9724     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9725     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9726         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9727       board[fromY][fromX] = EmptySquare;
9728       board[toY][toX] = EmptySquare;
9729       if((toX > fromX) != (piece == WhiteRook)) {
9730         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9731       } else {
9732         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9733       }
9734     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9735                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9736       board[fromY][fromX] = EmptySquare;
9737       board[toY][toX] = EmptySquare;
9738       if((toX > fromX) != (piece == BlackRook)) {
9739         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9740       } else {
9741         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9742       }
9743     /* End of code added by Tord */
9744
9745     } else if (board[fromY][fromX] == king
9746         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9747         && toY == fromY && toX > fromX+1) {
9748         board[fromY][fromX] = EmptySquare;
9749         board[toY][toX] = king;
9750         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9751         board[fromY][BOARD_RGHT-1] = EmptySquare;
9752     } else if (board[fromY][fromX] == king
9753         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9754                && toY == fromY && toX < fromX-1) {
9755         board[fromY][fromX] = EmptySquare;
9756         board[toY][toX] = king;
9757         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9758         board[fromY][BOARD_LEFT] = EmptySquare;
9759     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9760                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9761                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9762                ) {
9763         /* white pawn promotion */
9764         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9765         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9766             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9767         board[fromY][fromX] = EmptySquare;
9768     } else if ((fromY >= BOARD_HEIGHT>>1)
9769                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9770                && (toX != fromX)
9771                && gameInfo.variant != VariantXiangqi
9772                && gameInfo.variant != VariantBerolina
9773                && (board[fromY][fromX] == WhitePawn)
9774                && (board[toY][toX] == EmptySquare)) {
9775         board[fromY][fromX] = EmptySquare;
9776         board[toY][toX] = WhitePawn;
9777         captured = board[toY - 1][toX];
9778         board[toY - 1][toX] = EmptySquare;
9779     } else if ((fromY == BOARD_HEIGHT-4)
9780                && (toX == fromX)
9781                && gameInfo.variant == VariantBerolina
9782                && (board[fromY][fromX] == WhitePawn)
9783                && (board[toY][toX] == EmptySquare)) {
9784         board[fromY][fromX] = EmptySquare;
9785         board[toY][toX] = WhitePawn;
9786         if(oldEP & EP_BEROLIN_A) {
9787                 captured = board[fromY][fromX-1];
9788                 board[fromY][fromX-1] = EmptySquare;
9789         }else{  captured = board[fromY][fromX+1];
9790                 board[fromY][fromX+1] = EmptySquare;
9791         }
9792     } else if (board[fromY][fromX] == king
9793         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9794                && toY == fromY && toX > fromX+1) {
9795         board[fromY][fromX] = EmptySquare;
9796         board[toY][toX] = king;
9797         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9798         board[fromY][BOARD_RGHT-1] = EmptySquare;
9799     } else if (board[fromY][fromX] == king
9800         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9801                && toY == fromY && toX < fromX-1) {
9802         board[fromY][fromX] = EmptySquare;
9803         board[toY][toX] = king;
9804         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9805         board[fromY][BOARD_LEFT] = EmptySquare;
9806     } else if (fromY == 7 && fromX == 3
9807                && board[fromY][fromX] == BlackKing
9808                && toY == 7 && toX == 5) {
9809         board[fromY][fromX] = EmptySquare;
9810         board[toY][toX] = BlackKing;
9811         board[fromY][7] = EmptySquare;
9812         board[toY][4] = BlackRook;
9813     } else if (fromY == 7 && fromX == 3
9814                && board[fromY][fromX] == BlackKing
9815                && toY == 7 && toX == 1) {
9816         board[fromY][fromX] = EmptySquare;
9817         board[toY][toX] = BlackKing;
9818         board[fromY][0] = EmptySquare;
9819         board[toY][2] = BlackRook;
9820     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9821                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9822                && toY < promoRank && promoChar
9823                ) {
9824         /* black pawn promotion */
9825         board[toY][toX] = CharToPiece(ToLower(promoChar));
9826         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9827             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9828         board[fromY][fromX] = EmptySquare;
9829     } else if ((fromY < BOARD_HEIGHT>>1)
9830                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9831                && (toX != fromX)
9832                && gameInfo.variant != VariantXiangqi
9833                && gameInfo.variant != VariantBerolina
9834                && (board[fromY][fromX] == BlackPawn)
9835                && (board[toY][toX] == EmptySquare)) {
9836         board[fromY][fromX] = EmptySquare;
9837         board[toY][toX] = BlackPawn;
9838         captured = board[toY + 1][toX];
9839         board[toY + 1][toX] = EmptySquare;
9840     } else if ((fromY == 3)
9841                && (toX == fromX)
9842                && gameInfo.variant == VariantBerolina
9843                && (board[fromY][fromX] == BlackPawn)
9844                && (board[toY][toX] == EmptySquare)) {
9845         board[fromY][fromX] = EmptySquare;
9846         board[toY][toX] = BlackPawn;
9847         if(oldEP & EP_BEROLIN_A) {
9848                 captured = board[fromY][fromX-1];
9849                 board[fromY][fromX-1] = EmptySquare;
9850         }else{  captured = board[fromY][fromX+1];
9851                 board[fromY][fromX+1] = EmptySquare;
9852         }
9853     } else {
9854         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9855         board[fromY][fromX] = EmptySquare;
9856         board[toY][toX] = piece;
9857     }
9858   }
9859
9860     if (gameInfo.holdingsWidth != 0) {
9861
9862       /* !!A lot more code needs to be written to support holdings  */
9863       /* [HGM] OK, so I have written it. Holdings are stored in the */
9864       /* penultimate board files, so they are automaticlly stored   */
9865       /* in the game history.                                       */
9866       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9867                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9868         /* Delete from holdings, by decreasing count */
9869         /* and erasing image if necessary            */
9870         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9871         if(p < (int) BlackPawn) { /* white drop */
9872              p -= (int)WhitePawn;
9873                  p = PieceToNumber((ChessSquare)p);
9874              if(p >= gameInfo.holdingsSize) p = 0;
9875              if(--board[p][BOARD_WIDTH-2] <= 0)
9876                   board[p][BOARD_WIDTH-1] = EmptySquare;
9877              if((int)board[p][BOARD_WIDTH-2] < 0)
9878                         board[p][BOARD_WIDTH-2] = 0;
9879         } else {                  /* black drop */
9880              p -= (int)BlackPawn;
9881                  p = PieceToNumber((ChessSquare)p);
9882              if(p >= gameInfo.holdingsSize) p = 0;
9883              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9884                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9885              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9886                         board[BOARD_HEIGHT-1-p][1] = 0;
9887         }
9888       }
9889       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9890           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9891         /* [HGM] holdings: Add to holdings, if holdings exist */
9892         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9893                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9894                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9895         }
9896         p = (int) captured;
9897         if (p >= (int) BlackPawn) {
9898           p -= (int)BlackPawn;
9899           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9900                   /* in Shogi restore piece to its original  first */
9901                   captured = (ChessSquare) (DEMOTED captured);
9902                   p = DEMOTED p;
9903           }
9904           p = PieceToNumber((ChessSquare)p);
9905           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9906           board[p][BOARD_WIDTH-2]++;
9907           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9908         } else {
9909           p -= (int)WhitePawn;
9910           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9911                   captured = (ChessSquare) (DEMOTED captured);
9912                   p = DEMOTED p;
9913           }
9914           p = PieceToNumber((ChessSquare)p);
9915           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9916           board[BOARD_HEIGHT-1-p][1]++;
9917           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9918         }
9919       }
9920     } else if (gameInfo.variant == VariantAtomic) {
9921       if (captured != EmptySquare) {
9922         int y, x;
9923         for (y = toY-1; y <= toY+1; y++) {
9924           for (x = toX-1; x <= toX+1; x++) {
9925             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9926                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9927               board[y][x] = EmptySquare;
9928             }
9929           }
9930         }
9931         board[toY][toX] = EmptySquare;
9932       }
9933     }
9934     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9935         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9936     } else
9937     if(promoChar == '+') {
9938         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9939         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9940     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9941         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9942         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9943            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9944         board[toY][toX] = newPiece;
9945     }
9946     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9947                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9948         // [HGM] superchess: take promotion piece out of holdings
9949         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9950         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9951             if(!--board[k][BOARD_WIDTH-2])
9952                 board[k][BOARD_WIDTH-1] = EmptySquare;
9953         } else {
9954             if(!--board[BOARD_HEIGHT-1-k][1])
9955                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9956         }
9957     }
9958 }
9959
9960 /* Updates forwardMostMove */
9961 void
9962 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9963 {
9964     int x = toX, y = toY;
9965     char *s = parseList[forwardMostMove];
9966     ChessSquare p = boards[forwardMostMove][toY][toX];
9967 //    forwardMostMove++; // [HGM] bare: moved downstream
9968
9969     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9970     (void) CoordsToAlgebraic(boards[forwardMostMove],
9971                              PosFlags(forwardMostMove),
9972                              fromY, fromX, y, x, promoChar,
9973                              s);
9974     if(killX >= 0 && killY >= 0)
9975         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9976
9977     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9978         int timeLeft; static int lastLoadFlag=0; int king, piece;
9979         piece = boards[forwardMostMove][fromY][fromX];
9980         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9981         if(gameInfo.variant == VariantKnightmate)
9982             king += (int) WhiteUnicorn - (int) WhiteKing;
9983         if(forwardMostMove == 0) {
9984             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9985                 fprintf(serverMoves, "%s;", UserName());
9986             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9987                 fprintf(serverMoves, "%s;", second.tidy);
9988             fprintf(serverMoves, "%s;", first.tidy);
9989             if(gameMode == MachinePlaysWhite)
9990                 fprintf(serverMoves, "%s;", UserName());
9991             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9992                 fprintf(serverMoves, "%s;", second.tidy);
9993         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9994         lastLoadFlag = loadFlag;
9995         // print base move
9996         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9997         // print castling suffix
9998         if( toY == fromY && piece == king ) {
9999             if(toX-fromX > 1)
10000                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10001             if(fromX-toX >1)
10002                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10003         }
10004         // e.p. suffix
10005         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10006              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10007              boards[forwardMostMove][toY][toX] == EmptySquare
10008              && fromX != toX && fromY != toY)
10009                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10010         // promotion suffix
10011         if(promoChar != NULLCHAR) {
10012             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10013                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10014                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10015             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10016         }
10017         if(!loadFlag) {
10018                 char buf[MOVE_LEN*2], *p; int len;
10019             fprintf(serverMoves, "/%d/%d",
10020                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10021             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10022             else                      timeLeft = blackTimeRemaining/1000;
10023             fprintf(serverMoves, "/%d", timeLeft);
10024                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10025                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10026                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10027                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10028             fprintf(serverMoves, "/%s", buf);
10029         }
10030         fflush(serverMoves);
10031     }
10032
10033     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10034         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10035       return;
10036     }
10037     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10038     if (commentList[forwardMostMove+1] != NULL) {
10039         free(commentList[forwardMostMove+1]);
10040         commentList[forwardMostMove+1] = NULL;
10041     }
10042     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10043     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10044     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10045     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10046     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10047     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10048     adjustedClock = FALSE;
10049     gameInfo.result = GameUnfinished;
10050     if (gameInfo.resultDetails != NULL) {
10051         free(gameInfo.resultDetails);
10052         gameInfo.resultDetails = NULL;
10053     }
10054     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10055                               moveList[forwardMostMove - 1]);
10056     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10057       case MT_NONE:
10058       case MT_STALEMATE:
10059       default:
10060         break;
10061       case MT_CHECK:
10062         if(gameInfo.variant != VariantShogi)
10063             strcat(parseList[forwardMostMove - 1], "+");
10064         break;
10065       case MT_CHECKMATE:
10066       case MT_STAINMATE:
10067         strcat(parseList[forwardMostMove - 1], "#");
10068         break;
10069     }
10070
10071     killX = killY = -1; // [HGM] lion: used up
10072 }
10073
10074 /* Updates currentMove if not pausing */
10075 void
10076 ShowMove (int fromX, int fromY, int toX, int toY)
10077 {
10078     int instant = (gameMode == PlayFromGameFile) ?
10079         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10080     if(appData.noGUI) return;
10081     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10082         if (!instant) {
10083             if (forwardMostMove == currentMove + 1) {
10084                 AnimateMove(boards[forwardMostMove - 1],
10085                             fromX, fromY, toX, toY);
10086             }
10087         }
10088         currentMove = forwardMostMove;
10089     }
10090
10091     if (instant) return;
10092
10093     DisplayMove(currentMove - 1);
10094     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10095             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10096                 SetHighlights(fromX, fromY, toX, toY);
10097             }
10098     }
10099     DrawPosition(FALSE, boards[currentMove]);
10100     DisplayBothClocks();
10101     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10102 }
10103
10104 void
10105 SendEgtPath (ChessProgramState *cps)
10106 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10107         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10108
10109         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10110
10111         while(*p) {
10112             char c, *q = name+1, *r, *s;
10113
10114             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10115             while(*p && *p != ',') *q++ = *p++;
10116             *q++ = ':'; *q = 0;
10117             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10118                 strcmp(name, ",nalimov:") == 0 ) {
10119                 // take nalimov path from the menu-changeable option first, if it is defined
10120               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10121                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10122             } else
10123             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10124                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10125                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10126                 s = r = StrStr(s, ":") + 1; // beginning of path info
10127                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10128                 c = *r; *r = 0;             // temporarily null-terminate path info
10129                     *--q = 0;               // strip of trailig ':' from name
10130                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10131                 *r = c;
10132                 SendToProgram(buf,cps);     // send egtbpath command for this format
10133             }
10134             if(*p == ',') p++; // read away comma to position for next format name
10135         }
10136 }
10137
10138 static int
10139 NonStandardBoardSize ()
10140 {
10141       /* [HGM] Awkward testing. Should really be a table */
10142       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10143       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10144       if( gameInfo.variant == VariantXiangqi )
10145            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10146       if( gameInfo.variant == VariantShogi )
10147            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10148       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10149            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10150       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10151           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10152            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10153       if( gameInfo.variant == VariantCourier )
10154            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10155       if( gameInfo.variant == VariantSuper )
10156            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10157       if( gameInfo.variant == VariantGreat )
10158            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10159       if( gameInfo.variant == VariantSChess )
10160            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10161       if( gameInfo.variant == VariantGrand )
10162            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10163       if( gameInfo.variant == VariantChu )
10164            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10165       return overruled;
10166 }
10167
10168 void
10169 InitChessProgram (ChessProgramState *cps, int setup)
10170 /* setup needed to setup FRC opening position */
10171 {
10172     char buf[MSG_SIZ], b[MSG_SIZ];
10173     if (appData.noChessProgram) return;
10174     hintRequested = FALSE;
10175     bookRequested = FALSE;
10176
10177     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10178     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10179     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10180     if(cps->memSize) { /* [HGM] memory */
10181       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10182         SendToProgram(buf, cps);
10183     }
10184     SendEgtPath(cps); /* [HGM] EGT */
10185     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10186       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10187         SendToProgram(buf, cps);
10188     }
10189
10190     SendToProgram(cps->initString, cps);
10191     if (gameInfo.variant != VariantNormal &&
10192         gameInfo.variant != VariantLoadable
10193         /* [HGM] also send variant if board size non-standard */
10194         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10195                                             ) {
10196       char *v = VariantName(gameInfo.variant);
10197       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10198         /* [HGM] in protocol 1 we have to assume all variants valid */
10199         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10200         DisplayFatalError(buf, 0, 1);
10201         return;
10202       }
10203
10204       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10205         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10206                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10207            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10208            if(StrStr(cps->variants, b) == NULL) {
10209                // specific sized variant not known, check if general sizing allowed
10210                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10211                    if(StrStr(cps->variants, "boardsize") == NULL) {
10212                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10213                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10214                        DisplayFatalError(buf, 0, 1);
10215                        return;
10216                    }
10217                    /* [HGM] here we really should compare with the maximum supported board size */
10218                }
10219            }
10220       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10221       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10222       SendToProgram(buf, cps);
10223     }
10224     currentlyInitializedVariant = gameInfo.variant;
10225
10226     /* [HGM] send opening position in FRC to first engine */
10227     if(setup) {
10228           SendToProgram("force\n", cps);
10229           SendBoard(cps, 0);
10230           /* engine is now in force mode! Set flag to wake it up after first move. */
10231           setboardSpoiledMachineBlack = 1;
10232     }
10233
10234     if (cps->sendICS) {
10235       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10236       SendToProgram(buf, cps);
10237     }
10238     cps->maybeThinking = FALSE;
10239     cps->offeredDraw = 0;
10240     if (!appData.icsActive) {
10241         SendTimeControl(cps, movesPerSession, timeControl,
10242                         timeIncrement, appData.searchDepth,
10243                         searchTime);
10244     }
10245     if (appData.showThinking
10246         // [HGM] thinking: four options require thinking output to be sent
10247         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10248                                 ) {
10249         SendToProgram("post\n", cps);
10250     }
10251     SendToProgram("hard\n", cps);
10252     if (!appData.ponderNextMove) {
10253         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10254            it without being sure what state we are in first.  "hard"
10255            is not a toggle, so that one is OK.
10256          */
10257         SendToProgram("easy\n", cps);
10258     }
10259     if (cps->usePing) {
10260       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10261       SendToProgram(buf, cps);
10262     }
10263     cps->initDone = TRUE;
10264     ClearEngineOutputPane(cps == &second);
10265 }
10266
10267
10268 void
10269 ResendOptions (ChessProgramState *cps)
10270 { // send the stored value of the options
10271   int i;
10272   char buf[MSG_SIZ];
10273   Option *opt = cps->option;
10274   for(i=0; i<cps->nrOptions; i++, opt++) {
10275       switch(opt->type) {
10276         case Spin:
10277         case Slider:
10278         case CheckBox:
10279             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10280           break;
10281         case ComboBox:
10282           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10283           break;
10284         default:
10285             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10286           break;
10287         case Button:
10288         case SaveButton:
10289           continue;
10290       }
10291       SendToProgram(buf, cps);
10292   }
10293 }
10294
10295 void
10296 StartChessProgram (ChessProgramState *cps)
10297 {
10298     char buf[MSG_SIZ];
10299     int err;
10300
10301     if (appData.noChessProgram) return;
10302     cps->initDone = FALSE;
10303
10304     if (strcmp(cps->host, "localhost") == 0) {
10305         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10306     } else if (*appData.remoteShell == NULLCHAR) {
10307         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10308     } else {
10309         if (*appData.remoteUser == NULLCHAR) {
10310           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10311                     cps->program);
10312         } else {
10313           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10314                     cps->host, appData.remoteUser, cps->program);
10315         }
10316         err = StartChildProcess(buf, "", &cps->pr);
10317     }
10318
10319     if (err != 0) {
10320       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10321         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10322         if(cps != &first) return;
10323         appData.noChessProgram = TRUE;
10324         ThawUI();
10325         SetNCPMode();
10326 //      DisplayFatalError(buf, err, 1);
10327 //      cps->pr = NoProc;
10328 //      cps->isr = NULL;
10329         return;
10330     }
10331
10332     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10333     if (cps->protocolVersion > 1) {
10334       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10335       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10336         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10337         cps->comboCnt = 0;  //                and values of combo boxes
10338       }
10339       SendToProgram(buf, cps);
10340       if(cps->reload) ResendOptions(cps);
10341     } else {
10342       SendToProgram("xboard\n", cps);
10343     }
10344 }
10345
10346 void
10347 TwoMachinesEventIfReady P((void))
10348 {
10349   static int curMess = 0;
10350   if (first.lastPing != first.lastPong) {
10351     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10352     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10353     return;
10354   }
10355   if (second.lastPing != second.lastPong) {
10356     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10357     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10358     return;
10359   }
10360   DisplayMessage("", ""); curMess = 0;
10361   TwoMachinesEvent();
10362 }
10363
10364 char *
10365 MakeName (char *template)
10366 {
10367     time_t clock;
10368     struct tm *tm;
10369     static char buf[MSG_SIZ];
10370     char *p = buf;
10371     int i;
10372
10373     clock = time((time_t *)NULL);
10374     tm = localtime(&clock);
10375
10376     while(*p++ = *template++) if(p[-1] == '%') {
10377         switch(*template++) {
10378           case 0:   *p = 0; return buf;
10379           case 'Y': i = tm->tm_year+1900; break;
10380           case 'y': i = tm->tm_year-100; break;
10381           case 'M': i = tm->tm_mon+1; break;
10382           case 'd': i = tm->tm_mday; break;
10383           case 'h': i = tm->tm_hour; break;
10384           case 'm': i = tm->tm_min; break;
10385           case 's': i = tm->tm_sec; break;
10386           default:  i = 0;
10387         }
10388         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10389     }
10390     return buf;
10391 }
10392
10393 int
10394 CountPlayers (char *p)
10395 {
10396     int n = 0;
10397     while(p = strchr(p, '\n')) p++, n++; // count participants
10398     return n;
10399 }
10400
10401 FILE *
10402 WriteTourneyFile (char *results, FILE *f)
10403 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10404     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10405     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10406         // create a file with tournament description
10407         fprintf(f, "-participants {%s}\n", appData.participants);
10408         fprintf(f, "-seedBase %d\n", appData.seedBase);
10409         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10410         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10411         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10412         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10413         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10414         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10415         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10416         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10417         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10418         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10419         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10420         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10421         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10422         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10423         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10424         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10425         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10426         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10427         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10428         fprintf(f, "-smpCores %d\n", appData.smpCores);
10429         if(searchTime > 0)
10430                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10431         else {
10432                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10433                 fprintf(f, "-tc %s\n", appData.timeControl);
10434                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10435         }
10436         fprintf(f, "-results \"%s\"\n", results);
10437     }
10438     return f;
10439 }
10440
10441 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10442
10443 void
10444 Substitute (char *participants, int expunge)
10445 {
10446     int i, changed, changes=0, nPlayers=0;
10447     char *p, *q, *r, buf[MSG_SIZ];
10448     if(participants == NULL) return;
10449     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10450     r = p = participants; q = appData.participants;
10451     while(*p && *p == *q) {
10452         if(*p == '\n') r = p+1, nPlayers++;
10453         p++; q++;
10454     }
10455     if(*p) { // difference
10456         while(*p && *p++ != '\n');
10457         while(*q && *q++ != '\n');
10458       changed = nPlayers;
10459         changes = 1 + (strcmp(p, q) != 0);
10460     }
10461     if(changes == 1) { // a single engine mnemonic was changed
10462         q = r; while(*q) nPlayers += (*q++ == '\n');
10463         p = buf; while(*r && (*p = *r++) != '\n') p++;
10464         *p = NULLCHAR;
10465         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10466         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10467         if(mnemonic[i]) { // The substitute is valid
10468             FILE *f;
10469             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10470                 flock(fileno(f), LOCK_EX);
10471                 ParseArgsFromFile(f);
10472                 fseek(f, 0, SEEK_SET);
10473                 FREE(appData.participants); appData.participants = participants;
10474                 if(expunge) { // erase results of replaced engine
10475                     int len = strlen(appData.results), w, b, dummy;
10476                     for(i=0; i<len; i++) {
10477                         Pairing(i, nPlayers, &w, &b, &dummy);
10478                         if((w == changed || b == changed) && appData.results[i] == '*') {
10479                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10480                             fclose(f);
10481                             return;
10482                         }
10483                     }
10484                     for(i=0; i<len; i++) {
10485                         Pairing(i, nPlayers, &w, &b, &dummy);
10486                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10487                     }
10488                 }
10489                 WriteTourneyFile(appData.results, f);
10490                 fclose(f); // release lock
10491                 return;
10492             }
10493         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10494     }
10495     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10496     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10497     free(participants);
10498     return;
10499 }
10500
10501 int
10502 CheckPlayers (char *participants)
10503 {
10504         int i;
10505         char buf[MSG_SIZ], *p;
10506         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10507         while(p = strchr(participants, '\n')) {
10508             *p = NULLCHAR;
10509             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10510             if(!mnemonic[i]) {
10511                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10512                 *p = '\n';
10513                 DisplayError(buf, 0);
10514                 return 1;
10515             }
10516             *p = '\n';
10517             participants = p + 1;
10518         }
10519         return 0;
10520 }
10521
10522 int
10523 CreateTourney (char *name)
10524 {
10525         FILE *f;
10526         if(matchMode && strcmp(name, appData.tourneyFile)) {
10527              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10528         }
10529         if(name[0] == NULLCHAR) {
10530             if(appData.participants[0])
10531                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10532             return 0;
10533         }
10534         f = fopen(name, "r");
10535         if(f) { // file exists
10536             ASSIGN(appData.tourneyFile, name);
10537             ParseArgsFromFile(f); // parse it
10538         } else {
10539             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10540             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10541                 DisplayError(_("Not enough participants"), 0);
10542                 return 0;
10543             }
10544             if(CheckPlayers(appData.participants)) return 0;
10545             ASSIGN(appData.tourneyFile, name);
10546             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10547             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10548         }
10549         fclose(f);
10550         appData.noChessProgram = FALSE;
10551         appData.clockMode = TRUE;
10552         SetGNUMode();
10553         return 1;
10554 }
10555
10556 int
10557 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10558 {
10559     char buf[MSG_SIZ], *p, *q;
10560     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10561     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10562     skip = !all && group[0]; // if group requested, we start in skip mode
10563     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10564         p = names; q = buf; header = 0;
10565         while(*p && *p != '\n') *q++ = *p++;
10566         *q = 0;
10567         if(*p == '\n') p++;
10568         if(buf[0] == '#') {
10569             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10570             depth++; // we must be entering a new group
10571             if(all) continue; // suppress printing group headers when complete list requested
10572             header = 1;
10573             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10574         }
10575         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10576         if(engineList[i]) free(engineList[i]);
10577         engineList[i] = strdup(buf);
10578         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10579         if(engineMnemonic[i]) free(engineMnemonic[i]);
10580         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10581             strcat(buf, " (");
10582             sscanf(q + 8, "%s", buf + strlen(buf));
10583             strcat(buf, ")");
10584         }
10585         engineMnemonic[i] = strdup(buf);
10586         i++;
10587     }
10588     engineList[i] = engineMnemonic[i] = NULL;
10589     return i;
10590 }
10591
10592 // following implemented as macro to avoid type limitations
10593 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10594
10595 void
10596 SwapEngines (int n)
10597 {   // swap settings for first engine and other engine (so far only some selected options)
10598     int h;
10599     char *p;
10600     if(n == 0) return;
10601     SWAP(directory, p)
10602     SWAP(chessProgram, p)
10603     SWAP(isUCI, h)
10604     SWAP(hasOwnBookUCI, h)
10605     SWAP(protocolVersion, h)
10606     SWAP(reuse, h)
10607     SWAP(scoreIsAbsolute, h)
10608     SWAP(timeOdds, h)
10609     SWAP(logo, p)
10610     SWAP(pgnName, p)
10611     SWAP(pvSAN, h)
10612     SWAP(engOptions, p)
10613     SWAP(engInitString, p)
10614     SWAP(computerString, p)
10615     SWAP(features, p)
10616     SWAP(fenOverride, p)
10617     SWAP(NPS, h)
10618     SWAP(accumulateTC, h)
10619     SWAP(host, p)
10620 }
10621
10622 int
10623 GetEngineLine (char *s, int n)
10624 {
10625     int i;
10626     char buf[MSG_SIZ];
10627     extern char *icsNames;
10628     if(!s || !*s) return 0;
10629     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10630     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10631     if(!mnemonic[i]) return 0;
10632     if(n == 11) return 1; // just testing if there was a match
10633     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10634     if(n == 1) SwapEngines(n);
10635     ParseArgsFromString(buf);
10636     if(n == 1) SwapEngines(n);
10637     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10638         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10639         ParseArgsFromString(buf);
10640     }
10641     return 1;
10642 }
10643
10644 int
10645 SetPlayer (int player, char *p)
10646 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10647     int i;
10648     char buf[MSG_SIZ], *engineName;
10649     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10650     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10651     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10652     if(mnemonic[i]) {
10653         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10654         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10655         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10656         ParseArgsFromString(buf);
10657     } else { // no engine with this nickname is installed!
10658         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10659         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10660         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10661         ModeHighlight();
10662         DisplayError(buf, 0);
10663         return 0;
10664     }
10665     free(engineName);
10666     return i;
10667 }
10668
10669 char *recentEngines;
10670
10671 void
10672 RecentEngineEvent (int nr)
10673 {
10674     int n;
10675 //    SwapEngines(1); // bump first to second
10676 //    ReplaceEngine(&second, 1); // and load it there
10677     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10678     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10679     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10680         ReplaceEngine(&first, 0);
10681         FloatToFront(&appData.recentEngineList, command[n]);
10682     }
10683 }
10684
10685 int
10686 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10687 {   // determine players from game number
10688     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10689
10690     if(appData.tourneyType == 0) {
10691         roundsPerCycle = (nPlayers - 1) | 1;
10692         pairingsPerRound = nPlayers / 2;
10693     } else if(appData.tourneyType > 0) {
10694         roundsPerCycle = nPlayers - appData.tourneyType;
10695         pairingsPerRound = appData.tourneyType;
10696     }
10697     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10698     gamesPerCycle = gamesPerRound * roundsPerCycle;
10699     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10700     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10701     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10702     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10703     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10704     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10705
10706     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10707     if(appData.roundSync) *syncInterval = gamesPerRound;
10708
10709     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10710
10711     if(appData.tourneyType == 0) {
10712         if(curPairing == (nPlayers-1)/2 ) {
10713             *whitePlayer = curRound;
10714             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10715         } else {
10716             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10717             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10718             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10719             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10720         }
10721     } else if(appData.tourneyType > 1) {
10722         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10723         *whitePlayer = curRound + appData.tourneyType;
10724     } else if(appData.tourneyType > 0) {
10725         *whitePlayer = curPairing;
10726         *blackPlayer = curRound + appData.tourneyType;
10727     }
10728
10729     // take care of white/black alternation per round.
10730     // For cycles and games this is already taken care of by default, derived from matchGame!
10731     return curRound & 1;
10732 }
10733
10734 int
10735 NextTourneyGame (int nr, int *swapColors)
10736 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10737     char *p, *q;
10738     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10739     FILE *tf;
10740     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10741     tf = fopen(appData.tourneyFile, "r");
10742     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10743     ParseArgsFromFile(tf); fclose(tf);
10744     InitTimeControls(); // TC might be altered from tourney file
10745
10746     nPlayers = CountPlayers(appData.participants); // count participants
10747     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10748     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10749
10750     if(syncInterval) {
10751         p = q = appData.results;
10752         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10753         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10754             DisplayMessage(_("Waiting for other game(s)"),"");
10755             waitingForGame = TRUE;
10756             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10757             return 0;
10758         }
10759         waitingForGame = FALSE;
10760     }
10761
10762     if(appData.tourneyType < 0) {
10763         if(nr>=0 && !pairingReceived) {
10764             char buf[1<<16];
10765             if(pairing.pr == NoProc) {
10766                 if(!appData.pairingEngine[0]) {
10767                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10768                     return 0;
10769                 }
10770                 StartChessProgram(&pairing); // starts the pairing engine
10771             }
10772             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10773             SendToProgram(buf, &pairing);
10774             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10775             SendToProgram(buf, &pairing);
10776             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10777         }
10778         pairingReceived = 0;                              // ... so we continue here
10779         *swapColors = 0;
10780         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10781         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10782         matchGame = 1; roundNr = nr / syncInterval + 1;
10783     }
10784
10785     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10786
10787     // redefine engines, engine dir, etc.
10788     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10789     if(first.pr == NoProc) {
10790       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10791       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10792     }
10793     if(second.pr == NoProc) {
10794       SwapEngines(1);
10795       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10796       SwapEngines(1);         // and make that valid for second engine by swapping
10797       InitEngine(&second, 1);
10798     }
10799     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10800     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10801     return OK;
10802 }
10803
10804 void
10805 NextMatchGame ()
10806 {   // performs game initialization that does not invoke engines, and then tries to start the game
10807     int res, firstWhite, swapColors = 0;
10808     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10809     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
10810         char buf[MSG_SIZ];
10811         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10812         if(strcmp(buf, currentDebugFile)) { // name has changed
10813             FILE *f = fopen(buf, "w");
10814             if(f) { // if opening the new file failed, just keep using the old one
10815                 ASSIGN(currentDebugFile, buf);
10816                 fclose(debugFP);
10817                 debugFP = f;
10818             }
10819             if(appData.serverFileName) {
10820                 if(serverFP) fclose(serverFP);
10821                 serverFP = fopen(appData.serverFileName, "w");
10822                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10823                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10824             }
10825         }
10826     }
10827     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10828     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10829     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10830     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10831     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10832     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10833     Reset(FALSE, first.pr != NoProc);
10834     res = LoadGameOrPosition(matchGame); // setup game
10835     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10836     if(!res) return; // abort when bad game/pos file
10837     TwoMachinesEvent();
10838 }
10839
10840 void
10841 UserAdjudicationEvent (int result)
10842 {
10843     ChessMove gameResult = GameIsDrawn;
10844
10845     if( result > 0 ) {
10846         gameResult = WhiteWins;
10847     }
10848     else if( result < 0 ) {
10849         gameResult = BlackWins;
10850     }
10851
10852     if( gameMode == TwoMachinesPlay ) {
10853         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10854     }
10855 }
10856
10857
10858 // [HGM] save: calculate checksum of game to make games easily identifiable
10859 int
10860 StringCheckSum (char *s)
10861 {
10862         int i = 0;
10863         if(s==NULL) return 0;
10864         while(*s) i = i*259 + *s++;
10865         return i;
10866 }
10867
10868 int
10869 GameCheckSum ()
10870 {
10871         int i, sum=0;
10872         for(i=backwardMostMove; i<forwardMostMove; i++) {
10873                 sum += pvInfoList[i].depth;
10874                 sum += StringCheckSum(parseList[i]);
10875                 sum += StringCheckSum(commentList[i]);
10876                 sum *= 261;
10877         }
10878         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10879         return sum + StringCheckSum(commentList[i]);
10880 } // end of save patch
10881
10882 void
10883 GameEnds (ChessMove result, char *resultDetails, int whosays)
10884 {
10885     GameMode nextGameMode;
10886     int isIcsGame;
10887     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10888
10889     if(endingGame) return; /* [HGM] crash: forbid recursion */
10890     endingGame = 1;
10891     if(twoBoards) { // [HGM] dual: switch back to one board
10892         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10893         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10894     }
10895     if (appData.debugMode) {
10896       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10897               result, resultDetails ? resultDetails : "(null)", whosays);
10898     }
10899
10900     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10901
10902     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10903
10904     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10905         /* If we are playing on ICS, the server decides when the
10906            game is over, but the engine can offer to draw, claim
10907            a draw, or resign.
10908          */
10909 #if ZIPPY
10910         if (appData.zippyPlay && first.initDone) {
10911             if (result == GameIsDrawn) {
10912                 /* In case draw still needs to be claimed */
10913                 SendToICS(ics_prefix);
10914                 SendToICS("draw\n");
10915             } else if (StrCaseStr(resultDetails, "resign")) {
10916                 SendToICS(ics_prefix);
10917                 SendToICS("resign\n");
10918             }
10919         }
10920 #endif
10921         endingGame = 0; /* [HGM] crash */
10922         return;
10923     }
10924
10925     /* If we're loading the game from a file, stop */
10926     if (whosays == GE_FILE) {
10927       (void) StopLoadGameTimer();
10928       gameFileFP = NULL;
10929     }
10930
10931     /* Cancel draw offers */
10932     first.offeredDraw = second.offeredDraw = 0;
10933
10934     /* If this is an ICS game, only ICS can really say it's done;
10935        if not, anyone can. */
10936     isIcsGame = (gameMode == IcsPlayingWhite ||
10937                  gameMode == IcsPlayingBlack ||
10938                  gameMode == IcsObserving    ||
10939                  gameMode == IcsExamining);
10940
10941     if (!isIcsGame || whosays == GE_ICS) {
10942         /* OK -- not an ICS game, or ICS said it was done */
10943         StopClocks();
10944         if (!isIcsGame && !appData.noChessProgram)
10945           SetUserThinkingEnables();
10946
10947         /* [HGM] if a machine claims the game end we verify this claim */
10948         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10949             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10950                 char claimer;
10951                 ChessMove trueResult = (ChessMove) -1;
10952
10953                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10954                                             first.twoMachinesColor[0] :
10955                                             second.twoMachinesColor[0] ;
10956
10957                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10958                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10959                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10960                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10961                 } else
10962                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10963                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10964                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10965                 } else
10966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10967                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10968                 }
10969
10970                 // now verify win claims, but not in drop games, as we don't understand those yet
10971                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10972                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10973                     (result == WhiteWins && claimer == 'w' ||
10974                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10975                       if (appData.debugMode) {
10976                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10977                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10978                       }
10979                       if(result != trueResult) {
10980                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10981                               result = claimer == 'w' ? BlackWins : WhiteWins;
10982                               resultDetails = buf;
10983                       }
10984                 } else
10985                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10986                     && (forwardMostMove <= backwardMostMove ||
10987                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10988                         (claimer=='b')==(forwardMostMove&1))
10989                                                                                   ) {
10990                       /* [HGM] verify: draws that were not flagged are false claims */
10991                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10992                       result = claimer == 'w' ? BlackWins : WhiteWins;
10993                       resultDetails = buf;
10994                 }
10995                 /* (Claiming a loss is accepted no questions asked!) */
10996             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10997                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10998                 result = GameUnfinished;
10999                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11000             }
11001             /* [HGM] bare: don't allow bare King to win */
11002             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11003                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11004                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11005                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11006                && result != GameIsDrawn)
11007             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11008                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11009                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11010                         if(p >= 0 && p <= (int)WhiteKing) k++;
11011                 }
11012                 if (appData.debugMode) {
11013                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11014                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11015                 }
11016                 if(k <= 1) {
11017                         result = GameIsDrawn;
11018                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11019                         resultDetails = buf;
11020                 }
11021             }
11022         }
11023
11024
11025         if(serverMoves != NULL && !loadFlag) { char c = '=';
11026             if(result==WhiteWins) c = '+';
11027             if(result==BlackWins) c = '-';
11028             if(resultDetails != NULL)
11029                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11030         }
11031         if (resultDetails != NULL) {
11032             gameInfo.result = result;
11033             gameInfo.resultDetails = StrSave(resultDetails);
11034
11035             /* display last move only if game was not loaded from file */
11036             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11037                 DisplayMove(currentMove - 1);
11038
11039             if (forwardMostMove != 0) {
11040                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11041                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11042                                                                 ) {
11043                     if (*appData.saveGameFile != NULLCHAR) {
11044                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11045                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11046                         else
11047                         SaveGameToFile(appData.saveGameFile, TRUE);
11048                     } else if (appData.autoSaveGames) {
11049                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11050                     }
11051                     if (*appData.savePositionFile != NULLCHAR) {
11052                         SavePositionToFile(appData.savePositionFile);
11053                     }
11054                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11055                 }
11056             }
11057
11058             /* Tell program how game ended in case it is learning */
11059             /* [HGM] Moved this to after saving the PGN, just in case */
11060             /* engine died and we got here through time loss. In that */
11061             /* case we will get a fatal error writing the pipe, which */
11062             /* would otherwise lose us the PGN.                       */
11063             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11064             /* output during GameEnds should never be fatal anymore   */
11065             if (gameMode == MachinePlaysWhite ||
11066                 gameMode == MachinePlaysBlack ||
11067                 gameMode == TwoMachinesPlay ||
11068                 gameMode == IcsPlayingWhite ||
11069                 gameMode == IcsPlayingBlack ||
11070                 gameMode == BeginningOfGame) {
11071                 char buf[MSG_SIZ];
11072                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11073                         resultDetails);
11074                 if (first.pr != NoProc) {
11075                     SendToProgram(buf, &first);
11076                 }
11077                 if (second.pr != NoProc &&
11078                     gameMode == TwoMachinesPlay) {
11079                     SendToProgram(buf, &second);
11080                 }
11081             }
11082         }
11083
11084         if (appData.icsActive) {
11085             if (appData.quietPlay &&
11086                 (gameMode == IcsPlayingWhite ||
11087                  gameMode == IcsPlayingBlack)) {
11088                 SendToICS(ics_prefix);
11089                 SendToICS("set shout 1\n");
11090             }
11091             nextGameMode = IcsIdle;
11092             ics_user_moved = FALSE;
11093             /* clean up premove.  It's ugly when the game has ended and the
11094              * premove highlights are still on the board.
11095              */
11096             if (gotPremove) {
11097               gotPremove = FALSE;
11098               ClearPremoveHighlights();
11099               DrawPosition(FALSE, boards[currentMove]);
11100             }
11101             if (whosays == GE_ICS) {
11102                 switch (result) {
11103                 case WhiteWins:
11104                     if (gameMode == IcsPlayingWhite)
11105                         PlayIcsWinSound();
11106                     else if(gameMode == IcsPlayingBlack)
11107                         PlayIcsLossSound();
11108                     break;
11109                 case BlackWins:
11110                     if (gameMode == IcsPlayingBlack)
11111                         PlayIcsWinSound();
11112                     else if(gameMode == IcsPlayingWhite)
11113                         PlayIcsLossSound();
11114                     break;
11115                 case GameIsDrawn:
11116                     PlayIcsDrawSound();
11117                     break;
11118                 default:
11119                     PlayIcsUnfinishedSound();
11120                 }
11121             }
11122             if(appData.quitNext) { ExitEvent(0); return; }
11123         } else if (gameMode == EditGame ||
11124                    gameMode == PlayFromGameFile ||
11125                    gameMode == AnalyzeMode ||
11126                    gameMode == AnalyzeFile) {
11127             nextGameMode = gameMode;
11128         } else {
11129             nextGameMode = EndOfGame;
11130         }
11131         pausing = FALSE;
11132         ModeHighlight();
11133     } else {
11134         nextGameMode = gameMode;
11135     }
11136
11137     if (appData.noChessProgram) {
11138         gameMode = nextGameMode;
11139         ModeHighlight();
11140         endingGame = 0; /* [HGM] crash */
11141         return;
11142     }
11143
11144     if (first.reuse) {
11145         /* Put first chess program into idle state */
11146         if (first.pr != NoProc &&
11147             (gameMode == MachinePlaysWhite ||
11148              gameMode == MachinePlaysBlack ||
11149              gameMode == TwoMachinesPlay ||
11150              gameMode == IcsPlayingWhite ||
11151              gameMode == IcsPlayingBlack ||
11152              gameMode == BeginningOfGame)) {
11153             SendToProgram("force\n", &first);
11154             if (first.usePing) {
11155               char buf[MSG_SIZ];
11156               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11157               SendToProgram(buf, &first);
11158             }
11159         }
11160     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11161         /* Kill off first chess program */
11162         if (first.isr != NULL)
11163           RemoveInputSource(first.isr);
11164         first.isr = NULL;
11165
11166         if (first.pr != NoProc) {
11167             ExitAnalyzeMode();
11168             DoSleep( appData.delayBeforeQuit );
11169             SendToProgram("quit\n", &first);
11170             DoSleep( appData.delayAfterQuit );
11171             DestroyChildProcess(first.pr, first.useSigterm);
11172             first.reload = TRUE;
11173         }
11174         first.pr = NoProc;
11175     }
11176     if (second.reuse) {
11177         /* Put second chess program into idle state */
11178         if (second.pr != NoProc &&
11179             gameMode == TwoMachinesPlay) {
11180             SendToProgram("force\n", &second);
11181             if (second.usePing) {
11182               char buf[MSG_SIZ];
11183               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11184               SendToProgram(buf, &second);
11185             }
11186         }
11187     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11188         /* Kill off second chess program */
11189         if (second.isr != NULL)
11190           RemoveInputSource(second.isr);
11191         second.isr = NULL;
11192
11193         if (second.pr != NoProc) {
11194             DoSleep( appData.delayBeforeQuit );
11195             SendToProgram("quit\n", &second);
11196             DoSleep( appData.delayAfterQuit );
11197             DestroyChildProcess(second.pr, second.useSigterm);
11198             second.reload = TRUE;
11199         }
11200         second.pr = NoProc;
11201     }
11202
11203     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11204         char resChar = '=';
11205         switch (result) {
11206         case WhiteWins:
11207           resChar = '+';
11208           if (first.twoMachinesColor[0] == 'w') {
11209             first.matchWins++;
11210           } else {
11211             second.matchWins++;
11212           }
11213           break;
11214         case BlackWins:
11215           resChar = '-';
11216           if (first.twoMachinesColor[0] == 'b') {
11217             first.matchWins++;
11218           } else {
11219             second.matchWins++;
11220           }
11221           break;
11222         case GameUnfinished:
11223           resChar = ' ';
11224         default:
11225           break;
11226         }
11227
11228         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11229         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11230             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11231             ReserveGame(nextGame, resChar); // sets nextGame
11232             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11233             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11234         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11235
11236         if (nextGame <= appData.matchGames && !abortMatch) {
11237             gameMode = nextGameMode;
11238             matchGame = nextGame; // this will be overruled in tourney mode!
11239             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11240             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11241             endingGame = 0; /* [HGM] crash */
11242             return;
11243         } else {
11244             gameMode = nextGameMode;
11245             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11246                      first.tidy, second.tidy,
11247                      first.matchWins, second.matchWins,
11248                      appData.matchGames - (first.matchWins + second.matchWins));
11249             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11250             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11251             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11252             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11253                 first.twoMachinesColor = "black\n";
11254                 second.twoMachinesColor = "white\n";
11255             } else {
11256                 first.twoMachinesColor = "white\n";
11257                 second.twoMachinesColor = "black\n";
11258             }
11259         }
11260     }
11261     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11262         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11263       ExitAnalyzeMode();
11264     gameMode = nextGameMode;
11265     ModeHighlight();
11266     endingGame = 0;  /* [HGM] crash */
11267     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11268         if(matchMode == TRUE) { // match through command line: exit with or without popup
11269             if(ranking) {
11270                 ToNrEvent(forwardMostMove);
11271                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11272                 else ExitEvent(0);
11273             } else DisplayFatalError(buf, 0, 0);
11274         } else { // match through menu; just stop, with or without popup
11275             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11276             ModeHighlight();
11277             if(ranking){
11278                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11279             } else DisplayNote(buf);
11280       }
11281       if(ranking) free(ranking);
11282     }
11283 }
11284
11285 /* Assumes program was just initialized (initString sent).
11286    Leaves program in force mode. */
11287 void
11288 FeedMovesToProgram (ChessProgramState *cps, int upto)
11289 {
11290     int i;
11291
11292     if (appData.debugMode)
11293       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11294               startedFromSetupPosition ? "position and " : "",
11295               backwardMostMove, upto, cps->which);
11296     if(currentlyInitializedVariant != gameInfo.variant) {
11297       char buf[MSG_SIZ];
11298         // [HGM] variantswitch: make engine aware of new variant
11299         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11300                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11301         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11302         SendToProgram(buf, cps);
11303         currentlyInitializedVariant = gameInfo.variant;
11304     }
11305     SendToProgram("force\n", cps);
11306     if (startedFromSetupPosition) {
11307         SendBoard(cps, backwardMostMove);
11308     if (appData.debugMode) {
11309         fprintf(debugFP, "feedMoves\n");
11310     }
11311     }
11312     for (i = backwardMostMove; i < upto; i++) {
11313         SendMoveToProgram(i, cps);
11314     }
11315 }
11316
11317
11318 int
11319 ResurrectChessProgram ()
11320 {
11321      /* The chess program may have exited.
11322         If so, restart it and feed it all the moves made so far. */
11323     static int doInit = 0;
11324
11325     if (appData.noChessProgram) return 1;
11326
11327     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11328         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11329         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11330         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11331     } else {
11332         if (first.pr != NoProc) return 1;
11333         StartChessProgram(&first);
11334     }
11335     InitChessProgram(&first, FALSE);
11336     FeedMovesToProgram(&first, currentMove);
11337
11338     if (!first.sendTime) {
11339         /* can't tell gnuchess what its clock should read,
11340            so we bow to its notion. */
11341         ResetClocks();
11342         timeRemaining[0][currentMove] = whiteTimeRemaining;
11343         timeRemaining[1][currentMove] = blackTimeRemaining;
11344     }
11345
11346     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11347                 appData.icsEngineAnalyze) && first.analysisSupport) {
11348       SendToProgram("analyze\n", &first);
11349       first.analyzing = TRUE;
11350     }
11351     return 1;
11352 }
11353
11354 /*
11355  * Button procedures
11356  */
11357 void
11358 Reset (int redraw, int init)
11359 {
11360     int i;
11361
11362     if (appData.debugMode) {
11363         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11364                 redraw, init, gameMode);
11365     }
11366     CleanupTail(); // [HGM] vari: delete any stored variations
11367     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11368     pausing = pauseExamInvalid = FALSE;
11369     startedFromSetupPosition = blackPlaysFirst = FALSE;
11370     firstMove = TRUE;
11371     whiteFlag = blackFlag = FALSE;
11372     userOfferedDraw = FALSE;
11373     hintRequested = bookRequested = FALSE;
11374     first.maybeThinking = FALSE;
11375     second.maybeThinking = FALSE;
11376     first.bookSuspend = FALSE; // [HGM] book
11377     second.bookSuspend = FALSE;
11378     thinkOutput[0] = NULLCHAR;
11379     lastHint[0] = NULLCHAR;
11380     ClearGameInfo(&gameInfo);
11381     gameInfo.variant = StringToVariant(appData.variant);
11382     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11383     ics_user_moved = ics_clock_paused = FALSE;
11384     ics_getting_history = H_FALSE;
11385     ics_gamenum = -1;
11386     white_holding[0] = black_holding[0] = NULLCHAR;
11387     ClearProgramStats();
11388     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11389
11390     ResetFrontEnd();
11391     ClearHighlights();
11392     flipView = appData.flipView;
11393     ClearPremoveHighlights();
11394     gotPremove = FALSE;
11395     alarmSounded = FALSE;
11396
11397     GameEnds(EndOfFile, NULL, GE_PLAYER);
11398     if(appData.serverMovesName != NULL) {
11399         /* [HGM] prepare to make moves file for broadcasting */
11400         clock_t t = clock();
11401         if(serverMoves != NULL) fclose(serverMoves);
11402         serverMoves = fopen(appData.serverMovesName, "r");
11403         if(serverMoves != NULL) {
11404             fclose(serverMoves);
11405             /* delay 15 sec before overwriting, so all clients can see end */
11406             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11407         }
11408         serverMoves = fopen(appData.serverMovesName, "w");
11409     }
11410
11411     ExitAnalyzeMode();
11412     gameMode = BeginningOfGame;
11413     ModeHighlight();
11414     if(appData.icsActive) gameInfo.variant = VariantNormal;
11415     currentMove = forwardMostMove = backwardMostMove = 0;
11416     MarkTargetSquares(1);
11417     InitPosition(redraw);
11418     for (i = 0; i < MAX_MOVES; i++) {
11419         if (commentList[i] != NULL) {
11420             free(commentList[i]);
11421             commentList[i] = NULL;
11422         }
11423     }
11424     ResetClocks();
11425     timeRemaining[0][0] = whiteTimeRemaining;
11426     timeRemaining[1][0] = blackTimeRemaining;
11427
11428     if (first.pr == NoProc) {
11429         StartChessProgram(&first);
11430     }
11431     if (init) {
11432             InitChessProgram(&first, startedFromSetupPosition);
11433     }
11434     DisplayTitle("");
11435     DisplayMessage("", "");
11436     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11437     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11438     ClearMap();        // [HGM] exclude: invalidate map
11439 }
11440
11441 void
11442 AutoPlayGameLoop ()
11443 {
11444     for (;;) {
11445         if (!AutoPlayOneMove())
11446           return;
11447         if (matchMode || appData.timeDelay == 0)
11448           continue;
11449         if (appData.timeDelay < 0)
11450           return;
11451         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11452         break;
11453     }
11454 }
11455
11456 void
11457 AnalyzeNextGame()
11458 {
11459     ReloadGame(1); // next game
11460 }
11461
11462 int
11463 AutoPlayOneMove ()
11464 {
11465     int fromX, fromY, toX, toY;
11466
11467     if (appData.debugMode) {
11468       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11469     }
11470
11471     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11472       return FALSE;
11473
11474     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11475       pvInfoList[currentMove].depth = programStats.depth;
11476       pvInfoList[currentMove].score = programStats.score;
11477       pvInfoList[currentMove].time  = 0;
11478       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11479       else { // append analysis of final position as comment
11480         char buf[MSG_SIZ];
11481         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11482         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11483       }
11484       programStats.depth = 0;
11485     }
11486
11487     if (currentMove >= forwardMostMove) {
11488       if(gameMode == AnalyzeFile) {
11489           if(appData.loadGameIndex == -1) {
11490             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11491           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11492           } else {
11493           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11494         }
11495       }
11496 //      gameMode = EndOfGame;
11497 //      ModeHighlight();
11498
11499       /* [AS] Clear current move marker at the end of a game */
11500       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11501
11502       return FALSE;
11503     }
11504
11505     toX = moveList[currentMove][2] - AAA;
11506     toY = moveList[currentMove][3] - ONE;
11507
11508     if (moveList[currentMove][1] == '@') {
11509         if (appData.highlightLastMove) {
11510             SetHighlights(-1, -1, toX, toY);
11511         }
11512     } else {
11513         fromX = moveList[currentMove][0] - AAA;
11514         fromY = moveList[currentMove][1] - ONE;
11515
11516         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11517
11518         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11519
11520         if (appData.highlightLastMove) {
11521             SetHighlights(fromX, fromY, toX, toY);
11522         }
11523     }
11524     DisplayMove(currentMove);
11525     SendMoveToProgram(currentMove++, &first);
11526     DisplayBothClocks();
11527     DrawPosition(FALSE, boards[currentMove]);
11528     // [HGM] PV info: always display, routine tests if empty
11529     DisplayComment(currentMove - 1, commentList[currentMove]);
11530     return TRUE;
11531 }
11532
11533
11534 int
11535 LoadGameOneMove (ChessMove readAhead)
11536 {
11537     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11538     char promoChar = NULLCHAR;
11539     ChessMove moveType;
11540     char move[MSG_SIZ];
11541     char *p, *q;
11542
11543     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11544         gameMode != AnalyzeMode && gameMode != Training) {
11545         gameFileFP = NULL;
11546         return FALSE;
11547     }
11548
11549     yyboardindex = forwardMostMove;
11550     if (readAhead != EndOfFile) {
11551       moveType = readAhead;
11552     } else {
11553       if (gameFileFP == NULL)
11554           return FALSE;
11555       moveType = (ChessMove) Myylex();
11556     }
11557
11558     done = FALSE;
11559     switch (moveType) {
11560       case Comment:
11561         if (appData.debugMode)
11562           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11563         p = yy_text;
11564
11565         /* append the comment but don't display it */
11566         AppendComment(currentMove, p, FALSE);
11567         return TRUE;
11568
11569       case WhiteCapturesEnPassant:
11570       case BlackCapturesEnPassant:
11571       case WhitePromotion:
11572       case BlackPromotion:
11573       case WhiteNonPromotion:
11574       case BlackNonPromotion:
11575       case NormalMove:
11576       case WhiteKingSideCastle:
11577       case WhiteQueenSideCastle:
11578       case BlackKingSideCastle:
11579       case BlackQueenSideCastle:
11580       case WhiteKingSideCastleWild:
11581       case WhiteQueenSideCastleWild:
11582       case BlackKingSideCastleWild:
11583       case BlackQueenSideCastleWild:
11584       /* PUSH Fabien */
11585       case WhiteHSideCastleFR:
11586       case WhiteASideCastleFR:
11587       case BlackHSideCastleFR:
11588       case BlackASideCastleFR:
11589       /* POP Fabien */
11590         if (appData.debugMode)
11591           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11592         fromX = currentMoveString[0] - AAA;
11593         fromY = currentMoveString[1] - ONE;
11594         toX = currentMoveString[2] - AAA;
11595         toY = currentMoveString[3] - ONE;
11596         promoChar = currentMoveString[4];
11597         break;
11598
11599       case WhiteDrop:
11600       case BlackDrop:
11601         if (appData.debugMode)
11602           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11603         fromX = moveType == WhiteDrop ?
11604           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11605         (int) CharToPiece(ToLower(currentMoveString[0]));
11606         fromY = DROP_RANK;
11607         toX = currentMoveString[2] - AAA;
11608         toY = currentMoveString[3] - ONE;
11609         break;
11610
11611       case WhiteWins:
11612       case BlackWins:
11613       case GameIsDrawn:
11614       case GameUnfinished:
11615         if (appData.debugMode)
11616           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11617         p = strchr(yy_text, '{');
11618         if (p == NULL) p = strchr(yy_text, '(');
11619         if (p == NULL) {
11620             p = yy_text;
11621             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11622         } else {
11623             q = strchr(p, *p == '{' ? '}' : ')');
11624             if (q != NULL) *q = NULLCHAR;
11625             p++;
11626         }
11627         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11628         GameEnds(moveType, p, GE_FILE);
11629         done = TRUE;
11630         if (cmailMsgLoaded) {
11631             ClearHighlights();
11632             flipView = WhiteOnMove(currentMove);
11633             if (moveType == GameUnfinished) flipView = !flipView;
11634             if (appData.debugMode)
11635               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11636         }
11637         break;
11638
11639       case EndOfFile:
11640         if (appData.debugMode)
11641           fprintf(debugFP, "Parser hit end of file\n");
11642         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11643           case MT_NONE:
11644           case MT_CHECK:
11645             break;
11646           case MT_CHECKMATE:
11647           case MT_STAINMATE:
11648             if (WhiteOnMove(currentMove)) {
11649                 GameEnds(BlackWins, "Black mates", GE_FILE);
11650             } else {
11651                 GameEnds(WhiteWins, "White mates", GE_FILE);
11652             }
11653             break;
11654           case MT_STALEMATE:
11655             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11656             break;
11657         }
11658         done = TRUE;
11659         break;
11660
11661       case MoveNumberOne:
11662         if (lastLoadGameStart == GNUChessGame) {
11663             /* GNUChessGames have numbers, but they aren't move numbers */
11664             if (appData.debugMode)
11665               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11666                       yy_text, (int) moveType);
11667             return LoadGameOneMove(EndOfFile); /* tail recursion */
11668         }
11669         /* else fall thru */
11670
11671       case XBoardGame:
11672       case GNUChessGame:
11673       case PGNTag:
11674         /* Reached start of next game in file */
11675         if (appData.debugMode)
11676           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11677         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11678           case MT_NONE:
11679           case MT_CHECK:
11680             break;
11681           case MT_CHECKMATE:
11682           case MT_STAINMATE:
11683             if (WhiteOnMove(currentMove)) {
11684                 GameEnds(BlackWins, "Black mates", GE_FILE);
11685             } else {
11686                 GameEnds(WhiteWins, "White mates", GE_FILE);
11687             }
11688             break;
11689           case MT_STALEMATE:
11690             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11691             break;
11692         }
11693         done = TRUE;
11694         break;
11695
11696       case PositionDiagram:     /* should not happen; ignore */
11697       case ElapsedTime:         /* ignore */
11698       case NAG:                 /* ignore */
11699         if (appData.debugMode)
11700           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11701                   yy_text, (int) moveType);
11702         return LoadGameOneMove(EndOfFile); /* tail recursion */
11703
11704       case IllegalMove:
11705         if (appData.testLegality) {
11706             if (appData.debugMode)
11707               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11708             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11709                     (forwardMostMove / 2) + 1,
11710                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11711             DisplayError(move, 0);
11712             done = TRUE;
11713         } else {
11714             if (appData.debugMode)
11715               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11716                       yy_text, currentMoveString);
11717             fromX = currentMoveString[0] - AAA;
11718             fromY = currentMoveString[1] - ONE;
11719             toX = currentMoveString[2] - AAA;
11720             toY = currentMoveString[3] - ONE;
11721             promoChar = currentMoveString[4];
11722         }
11723         break;
11724
11725       case AmbiguousMove:
11726         if (appData.debugMode)
11727           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11728         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11729                 (forwardMostMove / 2) + 1,
11730                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11731         DisplayError(move, 0);
11732         done = TRUE;
11733         break;
11734
11735       default:
11736       case ImpossibleMove:
11737         if (appData.debugMode)
11738           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11739         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11740                 (forwardMostMove / 2) + 1,
11741                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11742         DisplayError(move, 0);
11743         done = TRUE;
11744         break;
11745     }
11746
11747     if (done) {
11748         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11749             DrawPosition(FALSE, boards[currentMove]);
11750             DisplayBothClocks();
11751             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11752               DisplayComment(currentMove - 1, commentList[currentMove]);
11753         }
11754         (void) StopLoadGameTimer();
11755         gameFileFP = NULL;
11756         cmailOldMove = forwardMostMove;
11757         return FALSE;
11758     } else {
11759         /* currentMoveString is set as a side-effect of yylex */
11760
11761         thinkOutput[0] = NULLCHAR;
11762         MakeMove(fromX, fromY, toX, toY, promoChar);
11763         currentMove = forwardMostMove;
11764         return TRUE;
11765     }
11766 }
11767
11768 /* Load the nth game from the given file */
11769 int
11770 LoadGameFromFile (char *filename, int n, char *title, int useList)
11771 {
11772     FILE *f;
11773     char buf[MSG_SIZ];
11774
11775     if (strcmp(filename, "-") == 0) {
11776         f = stdin;
11777         title = "stdin";
11778     } else {
11779         f = fopen(filename, "rb");
11780         if (f == NULL) {
11781           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11782             DisplayError(buf, errno);
11783             return FALSE;
11784         }
11785     }
11786     if (fseek(f, 0, 0) == -1) {
11787         /* f is not seekable; probably a pipe */
11788         useList = FALSE;
11789     }
11790     if (useList && n == 0) {
11791         int error = GameListBuild(f);
11792         if (error) {
11793             DisplayError(_("Cannot build game list"), error);
11794         } else if (!ListEmpty(&gameList) &&
11795                    ((ListGame *) gameList.tailPred)->number > 1) {
11796             GameListPopUp(f, title);
11797             return TRUE;
11798         }
11799         GameListDestroy();
11800         n = 1;
11801     }
11802     if (n == 0) n = 1;
11803     return LoadGame(f, n, title, FALSE);
11804 }
11805
11806
11807 void
11808 MakeRegisteredMove ()
11809 {
11810     int fromX, fromY, toX, toY;
11811     char promoChar;
11812     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11813         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11814           case CMAIL_MOVE:
11815           case CMAIL_DRAW:
11816             if (appData.debugMode)
11817               fprintf(debugFP, "Restoring %s for game %d\n",
11818                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11819
11820             thinkOutput[0] = NULLCHAR;
11821             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11822             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11823             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11824             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11825             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11826             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11827             MakeMove(fromX, fromY, toX, toY, promoChar);
11828             ShowMove(fromX, fromY, toX, toY);
11829
11830             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11831               case MT_NONE:
11832               case MT_CHECK:
11833                 break;
11834
11835               case MT_CHECKMATE:
11836               case MT_STAINMATE:
11837                 if (WhiteOnMove(currentMove)) {
11838                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11839                 } else {
11840                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11841                 }
11842                 break;
11843
11844               case MT_STALEMATE:
11845                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11846                 break;
11847             }
11848
11849             break;
11850
11851           case CMAIL_RESIGN:
11852             if (WhiteOnMove(currentMove)) {
11853                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11854             } else {
11855                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11856             }
11857             break;
11858
11859           case CMAIL_ACCEPT:
11860             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11861             break;
11862
11863           default:
11864             break;
11865         }
11866     }
11867
11868     return;
11869 }
11870
11871 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11872 int
11873 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11874 {
11875     int retVal;
11876
11877     if (gameNumber > nCmailGames) {
11878         DisplayError(_("No more games in this message"), 0);
11879         return FALSE;
11880     }
11881     if (f == lastLoadGameFP) {
11882         int offset = gameNumber - lastLoadGameNumber;
11883         if (offset == 0) {
11884             cmailMsg[0] = NULLCHAR;
11885             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11886                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11887                 nCmailMovesRegistered--;
11888             }
11889             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11890             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11891                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11892             }
11893         } else {
11894             if (! RegisterMove()) return FALSE;
11895         }
11896     }
11897
11898     retVal = LoadGame(f, gameNumber, title, useList);
11899
11900     /* Make move registered during previous look at this game, if any */
11901     MakeRegisteredMove();
11902
11903     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11904         commentList[currentMove]
11905           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11906         DisplayComment(currentMove - 1, commentList[currentMove]);
11907     }
11908
11909     return retVal;
11910 }
11911
11912 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11913 int
11914 ReloadGame (int offset)
11915 {
11916     int gameNumber = lastLoadGameNumber + offset;
11917     if (lastLoadGameFP == NULL) {
11918         DisplayError(_("No game has been loaded yet"), 0);
11919         return FALSE;
11920     }
11921     if (gameNumber <= 0) {
11922         DisplayError(_("Can't back up any further"), 0);
11923         return FALSE;
11924     }
11925     if (cmailMsgLoaded) {
11926         return CmailLoadGame(lastLoadGameFP, gameNumber,
11927                              lastLoadGameTitle, lastLoadGameUseList);
11928     } else {
11929         return LoadGame(lastLoadGameFP, gameNumber,
11930                         lastLoadGameTitle, lastLoadGameUseList);
11931     }
11932 }
11933
11934 int keys[EmptySquare+1];
11935
11936 int
11937 PositionMatches (Board b1, Board b2)
11938 {
11939     int r, f, sum=0;
11940     switch(appData.searchMode) {
11941         case 1: return CompareWithRights(b1, b2);
11942         case 2:
11943             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11944                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11945             }
11946             return TRUE;
11947         case 3:
11948             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11949               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11950                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11951             }
11952             return sum==0;
11953         case 4:
11954             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11955                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11956             }
11957             return sum==0;
11958     }
11959     return TRUE;
11960 }
11961
11962 #define Q_PROMO  4
11963 #define Q_EP     3
11964 #define Q_BCASTL 2
11965 #define Q_WCASTL 1
11966
11967 int pieceList[256], quickBoard[256];
11968 ChessSquare pieceType[256] = { EmptySquare };
11969 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11970 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11971 int soughtTotal, turn;
11972 Boolean epOK, flipSearch;
11973
11974 typedef struct {
11975     unsigned char piece, to;
11976 } Move;
11977
11978 #define DSIZE (250000)
11979
11980 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11981 Move *moveDatabase = initialSpace;
11982 unsigned int movePtr, dataSize = DSIZE;
11983
11984 int
11985 MakePieceList (Board board, int *counts)
11986 {
11987     int r, f, n=Q_PROMO, total=0;
11988     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11989     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11990         int sq = f + (r<<4);
11991         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11992             quickBoard[sq] = ++n;
11993             pieceList[n] = sq;
11994             pieceType[n] = board[r][f];
11995             counts[board[r][f]]++;
11996             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11997             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11998             total++;
11999         }
12000     }
12001     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12002     return total;
12003 }
12004
12005 void
12006 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12007 {
12008     int sq = fromX + (fromY<<4);
12009     int piece = quickBoard[sq];
12010     quickBoard[sq] = 0;
12011     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12012     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12013         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12014         moveDatabase[movePtr++].piece = Q_WCASTL;
12015         quickBoard[sq] = piece;
12016         piece = quickBoard[from]; quickBoard[from] = 0;
12017         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12018     } else
12019     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12020         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12021         moveDatabase[movePtr++].piece = Q_BCASTL;
12022         quickBoard[sq] = piece;
12023         piece = quickBoard[from]; quickBoard[from] = 0;
12024         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12025     } else
12026     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12027         quickBoard[(fromY<<4)+toX] = 0;
12028         moveDatabase[movePtr].piece = Q_EP;
12029         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12030         moveDatabase[movePtr].to = sq;
12031     } else
12032     if(promoPiece != pieceType[piece]) {
12033         moveDatabase[movePtr++].piece = Q_PROMO;
12034         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12035     }
12036     moveDatabase[movePtr].piece = piece;
12037     quickBoard[sq] = piece;
12038     movePtr++;
12039 }
12040
12041 int
12042 PackGame (Board board)
12043 {
12044     Move *newSpace = NULL;
12045     moveDatabase[movePtr].piece = 0; // terminate previous game
12046     if(movePtr > dataSize) {
12047         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12048         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12049         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12050         if(newSpace) {
12051             int i;
12052             Move *p = moveDatabase, *q = newSpace;
12053             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12054             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12055             moveDatabase = newSpace;
12056         } else { // calloc failed, we must be out of memory. Too bad...
12057             dataSize = 0; // prevent calloc events for all subsequent games
12058             return 0;     // and signal this one isn't cached
12059         }
12060     }
12061     movePtr++;
12062     MakePieceList(board, counts);
12063     return movePtr;
12064 }
12065
12066 int
12067 QuickCompare (Board board, int *minCounts, int *maxCounts)
12068 {   // compare according to search mode
12069     int r, f;
12070     switch(appData.searchMode)
12071     {
12072       case 1: // exact position match
12073         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12074         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12075             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12076         }
12077         break;
12078       case 2: // can have extra material on empty squares
12079         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12080             if(board[r][f] == EmptySquare) continue;
12081             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12082         }
12083         break;
12084       case 3: // material with exact Pawn structure
12085         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12086             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12087             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12088         } // fall through to material comparison
12089       case 4: // exact material
12090         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12091         break;
12092       case 6: // material range with given imbalance
12093         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12094         // fall through to range comparison
12095       case 5: // material range
12096         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12097     }
12098     return TRUE;
12099 }
12100
12101 int
12102 QuickScan (Board board, Move *move)
12103 {   // reconstruct game,and compare all positions in it
12104     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12105     do {
12106         int piece = move->piece;
12107         int to = move->to, from = pieceList[piece];
12108         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12109           if(!piece) return -1;
12110           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12111             piece = (++move)->piece;
12112             from = pieceList[piece];
12113             counts[pieceType[piece]]--;
12114             pieceType[piece] = (ChessSquare) move->to;
12115             counts[move->to]++;
12116           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12117             counts[pieceType[quickBoard[to]]]--;
12118             quickBoard[to] = 0; total--;
12119             move++;
12120             continue;
12121           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12122             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12123             from  = pieceList[piece]; // so this must be King
12124             quickBoard[from] = 0;
12125             pieceList[piece] = to;
12126             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12127             quickBoard[from] = 0; // rook
12128             quickBoard[to] = piece;
12129             to = move->to; piece = move->piece;
12130             goto aftercastle;
12131           }
12132         }
12133         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12134         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12135         quickBoard[from] = 0;
12136       aftercastle:
12137         quickBoard[to] = piece;
12138         pieceList[piece] = to;
12139         cnt++; turn ^= 3;
12140         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12141            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12142            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12143                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12144           ) {
12145             static int lastCounts[EmptySquare+1];
12146             int i;
12147             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12148             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12149         } else stretch = 0;
12150         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12151         move++;
12152     } while(1);
12153 }
12154
12155 void
12156 InitSearch ()
12157 {
12158     int r, f;
12159     flipSearch = FALSE;
12160     CopyBoard(soughtBoard, boards[currentMove]);
12161     soughtTotal = MakePieceList(soughtBoard, maxSought);
12162     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12163     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12164     CopyBoard(reverseBoard, boards[currentMove]);
12165     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12166         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12167         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12168         reverseBoard[r][f] = piece;
12169     }
12170     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12171     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12172     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12173                  || (boards[currentMove][CASTLING][2] == NoRights ||
12174                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12175                  && (boards[currentMove][CASTLING][5] == NoRights ||
12176                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12177       ) {
12178         flipSearch = TRUE;
12179         CopyBoard(flipBoard, soughtBoard);
12180         CopyBoard(rotateBoard, reverseBoard);
12181         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12183             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12184         }
12185     }
12186     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12187     if(appData.searchMode >= 5) {
12188         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12189         MakePieceList(soughtBoard, minSought);
12190         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12191     }
12192     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12193         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12194 }
12195
12196 GameInfo dummyInfo;
12197 static int creatingBook;
12198
12199 int
12200 GameContainsPosition (FILE *f, ListGame *lg)
12201 {
12202     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12203     int fromX, fromY, toX, toY;
12204     char promoChar;
12205     static int initDone=FALSE;
12206
12207     // weed out games based on numerical tag comparison
12208     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12209     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12210     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12211     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12212     if(!initDone) {
12213         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12214         initDone = TRUE;
12215     }
12216     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12217     else CopyBoard(boards[scratch], initialPosition); // default start position
12218     if(lg->moves) {
12219         turn = btm + 1;
12220         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12221         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12222     }
12223     if(btm) plyNr++;
12224     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12225     fseek(f, lg->offset, 0);
12226     yynewfile(f);
12227     while(1) {
12228         yyboardindex = scratch;
12229         quickFlag = plyNr+1;
12230         next = Myylex();
12231         quickFlag = 0;
12232         switch(next) {
12233             case PGNTag:
12234                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12235             default:
12236                 continue;
12237
12238             case XBoardGame:
12239             case GNUChessGame:
12240                 if(plyNr) return -1; // after we have seen moves, this is for new game
12241               continue;
12242
12243             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12244             case ImpossibleMove:
12245             case WhiteWins: // game ends here with these four
12246             case BlackWins:
12247             case GameIsDrawn:
12248             case GameUnfinished:
12249                 return -1;
12250
12251             case IllegalMove:
12252                 if(appData.testLegality) return -1;
12253             case WhiteCapturesEnPassant:
12254             case BlackCapturesEnPassant:
12255             case WhitePromotion:
12256             case BlackPromotion:
12257             case WhiteNonPromotion:
12258             case BlackNonPromotion:
12259             case NormalMove:
12260             case WhiteKingSideCastle:
12261             case WhiteQueenSideCastle:
12262             case BlackKingSideCastle:
12263             case BlackQueenSideCastle:
12264             case WhiteKingSideCastleWild:
12265             case WhiteQueenSideCastleWild:
12266             case BlackKingSideCastleWild:
12267             case BlackQueenSideCastleWild:
12268             case WhiteHSideCastleFR:
12269             case WhiteASideCastleFR:
12270             case BlackHSideCastleFR:
12271             case BlackASideCastleFR:
12272                 fromX = currentMoveString[0] - AAA;
12273                 fromY = currentMoveString[1] - ONE;
12274                 toX = currentMoveString[2] - AAA;
12275                 toY = currentMoveString[3] - ONE;
12276                 promoChar = currentMoveString[4];
12277                 break;
12278             case WhiteDrop:
12279             case BlackDrop:
12280                 fromX = next == WhiteDrop ?
12281                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12282                   (int) CharToPiece(ToLower(currentMoveString[0]));
12283                 fromY = DROP_RANK;
12284                 toX = currentMoveString[2] - AAA;
12285                 toY = currentMoveString[3] - ONE;
12286                 promoChar = 0;
12287                 break;
12288         }
12289         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12290         plyNr++;
12291         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12292         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12293         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12294         if(appData.findMirror) {
12295             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12296             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12297         }
12298     }
12299 }
12300
12301 /* Load the nth game from open file f */
12302 int
12303 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12304 {
12305     ChessMove cm;
12306     char buf[MSG_SIZ];
12307     int gn = gameNumber;
12308     ListGame *lg = NULL;
12309     int numPGNTags = 0;
12310     int err, pos = -1;
12311     GameMode oldGameMode;
12312     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12313
12314     if (appData.debugMode)
12315         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12316
12317     if (gameMode == Training )
12318         SetTrainingModeOff();
12319
12320     oldGameMode = gameMode;
12321     if (gameMode != BeginningOfGame) {
12322       Reset(FALSE, TRUE);
12323     }
12324
12325     gameFileFP = f;
12326     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12327         fclose(lastLoadGameFP);
12328     }
12329
12330     if (useList) {
12331         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12332
12333         if (lg) {
12334             fseek(f, lg->offset, 0);
12335             GameListHighlight(gameNumber);
12336             pos = lg->position;
12337             gn = 1;
12338         }
12339         else {
12340             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12341               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12342             else
12343             DisplayError(_("Game number out of range"), 0);
12344             return FALSE;
12345         }
12346     } else {
12347         GameListDestroy();
12348         if (fseek(f, 0, 0) == -1) {
12349             if (f == lastLoadGameFP ?
12350                 gameNumber == lastLoadGameNumber + 1 :
12351                 gameNumber == 1) {
12352                 gn = 1;
12353             } else {
12354                 DisplayError(_("Can't seek on game file"), 0);
12355                 return FALSE;
12356             }
12357         }
12358     }
12359     lastLoadGameFP = f;
12360     lastLoadGameNumber = gameNumber;
12361     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12362     lastLoadGameUseList = useList;
12363
12364     yynewfile(f);
12365
12366     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12367       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12368                 lg->gameInfo.black);
12369             DisplayTitle(buf);
12370     } else if (*title != NULLCHAR) {
12371         if (gameNumber > 1) {
12372           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12373             DisplayTitle(buf);
12374         } else {
12375             DisplayTitle(title);
12376         }
12377     }
12378
12379     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12380         gameMode = PlayFromGameFile;
12381         ModeHighlight();
12382     }
12383
12384     currentMove = forwardMostMove = backwardMostMove = 0;
12385     CopyBoard(boards[0], initialPosition);
12386     StopClocks();
12387
12388     /*
12389      * Skip the first gn-1 games in the file.
12390      * Also skip over anything that precedes an identifiable
12391      * start of game marker, to avoid being confused by
12392      * garbage at the start of the file.  Currently
12393      * recognized start of game markers are the move number "1",
12394      * the pattern "gnuchess .* game", the pattern
12395      * "^[#;%] [^ ]* game file", and a PGN tag block.
12396      * A game that starts with one of the latter two patterns
12397      * will also have a move number 1, possibly
12398      * following a position diagram.
12399      * 5-4-02: Let's try being more lenient and allowing a game to
12400      * start with an unnumbered move.  Does that break anything?
12401      */
12402     cm = lastLoadGameStart = EndOfFile;
12403     while (gn > 0) {
12404         yyboardindex = forwardMostMove;
12405         cm = (ChessMove) Myylex();
12406         switch (cm) {
12407           case EndOfFile:
12408             if (cmailMsgLoaded) {
12409                 nCmailGames = CMAIL_MAX_GAMES - gn;
12410             } else {
12411                 Reset(TRUE, TRUE);
12412                 DisplayError(_("Game not found in file"), 0);
12413             }
12414             return FALSE;
12415
12416           case GNUChessGame:
12417           case XBoardGame:
12418             gn--;
12419             lastLoadGameStart = cm;
12420             break;
12421
12422           case MoveNumberOne:
12423             switch (lastLoadGameStart) {
12424               case GNUChessGame:
12425               case XBoardGame:
12426               case PGNTag:
12427                 break;
12428               case MoveNumberOne:
12429               case EndOfFile:
12430                 gn--;           /* count this game */
12431                 lastLoadGameStart = cm;
12432                 break;
12433               default:
12434                 /* impossible */
12435                 break;
12436             }
12437             break;
12438
12439           case PGNTag:
12440             switch (lastLoadGameStart) {
12441               case GNUChessGame:
12442               case PGNTag:
12443               case MoveNumberOne:
12444               case EndOfFile:
12445                 gn--;           /* count this game */
12446                 lastLoadGameStart = cm;
12447                 break;
12448               case XBoardGame:
12449                 lastLoadGameStart = cm; /* game counted already */
12450                 break;
12451               default:
12452                 /* impossible */
12453                 break;
12454             }
12455             if (gn > 0) {
12456                 do {
12457                     yyboardindex = forwardMostMove;
12458                     cm = (ChessMove) Myylex();
12459                 } while (cm == PGNTag || cm == Comment);
12460             }
12461             break;
12462
12463           case WhiteWins:
12464           case BlackWins:
12465           case GameIsDrawn:
12466             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12467                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12468                     != CMAIL_OLD_RESULT) {
12469                     nCmailResults ++ ;
12470                     cmailResult[  CMAIL_MAX_GAMES
12471                                 - gn - 1] = CMAIL_OLD_RESULT;
12472                 }
12473             }
12474             break;
12475
12476           case NormalMove:
12477             /* Only a NormalMove can be at the start of a game
12478              * without a position diagram. */
12479             if (lastLoadGameStart == EndOfFile ) {
12480               gn--;
12481               lastLoadGameStart = MoveNumberOne;
12482             }
12483             break;
12484
12485           default:
12486             break;
12487         }
12488     }
12489
12490     if (appData.debugMode)
12491       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12492
12493     if (cm == XBoardGame) {
12494         /* Skip any header junk before position diagram and/or move 1 */
12495         for (;;) {
12496             yyboardindex = forwardMostMove;
12497             cm = (ChessMove) Myylex();
12498
12499             if (cm == EndOfFile ||
12500                 cm == GNUChessGame || cm == XBoardGame) {
12501                 /* Empty game; pretend end-of-file and handle later */
12502                 cm = EndOfFile;
12503                 break;
12504             }
12505
12506             if (cm == MoveNumberOne || cm == PositionDiagram ||
12507                 cm == PGNTag || cm == Comment)
12508               break;
12509         }
12510     } else if (cm == GNUChessGame) {
12511         if (gameInfo.event != NULL) {
12512             free(gameInfo.event);
12513         }
12514         gameInfo.event = StrSave(yy_text);
12515     }
12516
12517     startedFromSetupPosition = FALSE;
12518     while (cm == PGNTag) {
12519         if (appData.debugMode)
12520           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12521         err = ParsePGNTag(yy_text, &gameInfo);
12522         if (!err) numPGNTags++;
12523
12524         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12525         if(gameInfo.variant != oldVariant) {
12526             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12527             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12528             InitPosition(TRUE);
12529             oldVariant = gameInfo.variant;
12530             if (appData.debugMode)
12531               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12532         }
12533
12534
12535         if (gameInfo.fen != NULL) {
12536           Board initial_position;
12537           startedFromSetupPosition = TRUE;
12538           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12539             Reset(TRUE, TRUE);
12540             DisplayError(_("Bad FEN position in file"), 0);
12541             return FALSE;
12542           }
12543           CopyBoard(boards[0], initial_position);
12544           if (blackPlaysFirst) {
12545             currentMove = forwardMostMove = backwardMostMove = 1;
12546             CopyBoard(boards[1], initial_position);
12547             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12548             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12549             timeRemaining[0][1] = whiteTimeRemaining;
12550             timeRemaining[1][1] = blackTimeRemaining;
12551             if (commentList[0] != NULL) {
12552               commentList[1] = commentList[0];
12553               commentList[0] = NULL;
12554             }
12555           } else {
12556             currentMove = forwardMostMove = backwardMostMove = 0;
12557           }
12558           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12559           {   int i;
12560               initialRulePlies = FENrulePlies;
12561               for( i=0; i< nrCastlingRights; i++ )
12562                   initialRights[i] = initial_position[CASTLING][i];
12563           }
12564           yyboardindex = forwardMostMove;
12565           free(gameInfo.fen);
12566           gameInfo.fen = NULL;
12567         }
12568
12569         yyboardindex = forwardMostMove;
12570         cm = (ChessMove) Myylex();
12571
12572         /* Handle comments interspersed among the tags */
12573         while (cm == Comment) {
12574             char *p;
12575             if (appData.debugMode)
12576               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12577             p = yy_text;
12578             AppendComment(currentMove, p, FALSE);
12579             yyboardindex = forwardMostMove;
12580             cm = (ChessMove) Myylex();
12581         }
12582     }
12583
12584     /* don't rely on existence of Event tag since if game was
12585      * pasted from clipboard the Event tag may not exist
12586      */
12587     if (numPGNTags > 0){
12588         char *tags;
12589         if (gameInfo.variant == VariantNormal) {
12590           VariantClass v = StringToVariant(gameInfo.event);
12591           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12592           if(v < VariantShogi) gameInfo.variant = v;
12593         }
12594         if (!matchMode) {
12595           if( appData.autoDisplayTags ) {
12596             tags = PGNTags(&gameInfo);
12597             TagsPopUp(tags, CmailMsg());
12598             free(tags);
12599           }
12600         }
12601     } else {
12602         /* Make something up, but don't display it now */
12603         SetGameInfo();
12604         TagsPopDown();
12605     }
12606
12607     if (cm == PositionDiagram) {
12608         int i, j;
12609         char *p;
12610         Board initial_position;
12611
12612         if (appData.debugMode)
12613           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12614
12615         if (!startedFromSetupPosition) {
12616             p = yy_text;
12617             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12618               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12619                 switch (*p) {
12620                   case '{':
12621                   case '[':
12622                   case '-':
12623                   case ' ':
12624                   case '\t':
12625                   case '\n':
12626                   case '\r':
12627                     break;
12628                   default:
12629                     initial_position[i][j++] = CharToPiece(*p);
12630                     break;
12631                 }
12632             while (*p == ' ' || *p == '\t' ||
12633                    *p == '\n' || *p == '\r') p++;
12634
12635             if (strncmp(p, "black", strlen("black"))==0)
12636               blackPlaysFirst = TRUE;
12637             else
12638               blackPlaysFirst = FALSE;
12639             startedFromSetupPosition = TRUE;
12640
12641             CopyBoard(boards[0], initial_position);
12642             if (blackPlaysFirst) {
12643                 currentMove = forwardMostMove = backwardMostMove = 1;
12644                 CopyBoard(boards[1], initial_position);
12645                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12646                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12647                 timeRemaining[0][1] = whiteTimeRemaining;
12648                 timeRemaining[1][1] = blackTimeRemaining;
12649                 if (commentList[0] != NULL) {
12650                     commentList[1] = commentList[0];
12651                     commentList[0] = NULL;
12652                 }
12653             } else {
12654                 currentMove = forwardMostMove = backwardMostMove = 0;
12655             }
12656         }
12657         yyboardindex = forwardMostMove;
12658         cm = (ChessMove) Myylex();
12659     }
12660
12661   if(!creatingBook) {
12662     if (first.pr == NoProc) {
12663         StartChessProgram(&first);
12664     }
12665     InitChessProgram(&first, FALSE);
12666     SendToProgram("force\n", &first);
12667     if (startedFromSetupPosition) {
12668         SendBoard(&first, forwardMostMove);
12669     if (appData.debugMode) {
12670         fprintf(debugFP, "Load Game\n");
12671     }
12672         DisplayBothClocks();
12673     }
12674   }
12675
12676     /* [HGM] server: flag to write setup moves in broadcast file as one */
12677     loadFlag = appData.suppressLoadMoves;
12678
12679     while (cm == Comment) {
12680         char *p;
12681         if (appData.debugMode)
12682           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12683         p = yy_text;
12684         AppendComment(currentMove, p, FALSE);
12685         yyboardindex = forwardMostMove;
12686         cm = (ChessMove) Myylex();
12687     }
12688
12689     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12690         cm == WhiteWins || cm == BlackWins ||
12691         cm == GameIsDrawn || cm == GameUnfinished) {
12692         DisplayMessage("", _("No moves in game"));
12693         if (cmailMsgLoaded) {
12694             if (appData.debugMode)
12695               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12696             ClearHighlights();
12697             flipView = FALSE;
12698         }
12699         DrawPosition(FALSE, boards[currentMove]);
12700         DisplayBothClocks();
12701         gameMode = EditGame;
12702         ModeHighlight();
12703         gameFileFP = NULL;
12704         cmailOldMove = 0;
12705         return TRUE;
12706     }
12707
12708     // [HGM] PV info: routine tests if comment empty
12709     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12710         DisplayComment(currentMove - 1, commentList[currentMove]);
12711     }
12712     if (!matchMode && appData.timeDelay != 0)
12713       DrawPosition(FALSE, boards[currentMove]);
12714
12715     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12716       programStats.ok_to_send = 1;
12717     }
12718
12719     /* if the first token after the PGN tags is a move
12720      * and not move number 1, retrieve it from the parser
12721      */
12722     if (cm != MoveNumberOne)
12723         LoadGameOneMove(cm);
12724
12725     /* load the remaining moves from the file */
12726     while (LoadGameOneMove(EndOfFile)) {
12727       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12728       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12729     }
12730
12731     /* rewind to the start of the game */
12732     currentMove = backwardMostMove;
12733
12734     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12735
12736     if (oldGameMode == AnalyzeFile) {
12737       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12738       AnalyzeFileEvent();
12739     } else
12740     if (oldGameMode == AnalyzeMode) {
12741       AnalyzeFileEvent();
12742     }
12743
12744     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12745         long int w, b; // [HGM] adjourn: restore saved clock times
12746         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12747         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12748             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12749             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12750         }
12751     }
12752
12753     if(creatingBook) return TRUE;
12754     if (!matchMode && pos > 0) {
12755         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12756     } else
12757     if (matchMode || appData.timeDelay == 0) {
12758       ToEndEvent();
12759     } else if (appData.timeDelay > 0) {
12760       AutoPlayGameLoop();
12761     }
12762
12763     if (appData.debugMode)
12764         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12765
12766     loadFlag = 0; /* [HGM] true game starts */
12767     return TRUE;
12768 }
12769
12770 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12771 int
12772 ReloadPosition (int offset)
12773 {
12774     int positionNumber = lastLoadPositionNumber + offset;
12775     if (lastLoadPositionFP == NULL) {
12776         DisplayError(_("No position has been loaded yet"), 0);
12777         return FALSE;
12778     }
12779     if (positionNumber <= 0) {
12780         DisplayError(_("Can't back up any further"), 0);
12781         return FALSE;
12782     }
12783     return LoadPosition(lastLoadPositionFP, positionNumber,
12784                         lastLoadPositionTitle);
12785 }
12786
12787 /* Load the nth position from the given file */
12788 int
12789 LoadPositionFromFile (char *filename, int n, char *title)
12790 {
12791     FILE *f;
12792     char buf[MSG_SIZ];
12793
12794     if (strcmp(filename, "-") == 0) {
12795         return LoadPosition(stdin, n, "stdin");
12796     } else {
12797         f = fopen(filename, "rb");
12798         if (f == NULL) {
12799             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12800             DisplayError(buf, errno);
12801             return FALSE;
12802         } else {
12803             return LoadPosition(f, n, title);
12804         }
12805     }
12806 }
12807
12808 /* Load the nth position from the given open file, and close it */
12809 int
12810 LoadPosition (FILE *f, int positionNumber, char *title)
12811 {
12812     char *p, line[MSG_SIZ];
12813     Board initial_position;
12814     int i, j, fenMode, pn;
12815
12816     if (gameMode == Training )
12817         SetTrainingModeOff();
12818
12819     if (gameMode != BeginningOfGame) {
12820         Reset(FALSE, TRUE);
12821     }
12822     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12823         fclose(lastLoadPositionFP);
12824     }
12825     if (positionNumber == 0) positionNumber = 1;
12826     lastLoadPositionFP = f;
12827     lastLoadPositionNumber = positionNumber;
12828     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12829     if (first.pr == NoProc && !appData.noChessProgram) {
12830       StartChessProgram(&first);
12831       InitChessProgram(&first, FALSE);
12832     }
12833     pn = positionNumber;
12834     if (positionNumber < 0) {
12835         /* Negative position number means to seek to that byte offset */
12836         if (fseek(f, -positionNumber, 0) == -1) {
12837             DisplayError(_("Can't seek on position file"), 0);
12838             return FALSE;
12839         };
12840         pn = 1;
12841     } else {
12842         if (fseek(f, 0, 0) == -1) {
12843             if (f == lastLoadPositionFP ?
12844                 positionNumber == lastLoadPositionNumber + 1 :
12845                 positionNumber == 1) {
12846                 pn = 1;
12847             } else {
12848                 DisplayError(_("Can't seek on position file"), 0);
12849                 return FALSE;
12850             }
12851         }
12852     }
12853     /* See if this file is FEN or old-style xboard */
12854     if (fgets(line, MSG_SIZ, f) == NULL) {
12855         DisplayError(_("Position not found in file"), 0);
12856         return FALSE;
12857     }
12858     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12859     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12860
12861     if (pn >= 2) {
12862         if (fenMode || line[0] == '#') pn--;
12863         while (pn > 0) {
12864             /* skip positions before number pn */
12865             if (fgets(line, MSG_SIZ, f) == NULL) {
12866                 Reset(TRUE, TRUE);
12867                 DisplayError(_("Position not found in file"), 0);
12868                 return FALSE;
12869             }
12870             if (fenMode || line[0] == '#') pn--;
12871         }
12872     }
12873
12874     if (fenMode) {
12875         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12876             DisplayError(_("Bad FEN position in file"), 0);
12877             return FALSE;
12878         }
12879     } else {
12880         (void) fgets(line, MSG_SIZ, f);
12881         (void) fgets(line, MSG_SIZ, f);
12882
12883         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12884             (void) fgets(line, MSG_SIZ, f);
12885             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12886                 if (*p == ' ')
12887                   continue;
12888                 initial_position[i][j++] = CharToPiece(*p);
12889             }
12890         }
12891
12892         blackPlaysFirst = FALSE;
12893         if (!feof(f)) {
12894             (void) fgets(line, MSG_SIZ, f);
12895             if (strncmp(line, "black", strlen("black"))==0)
12896               blackPlaysFirst = TRUE;
12897         }
12898     }
12899     startedFromSetupPosition = TRUE;
12900
12901     CopyBoard(boards[0], initial_position);
12902     if (blackPlaysFirst) {
12903         currentMove = forwardMostMove = backwardMostMove = 1;
12904         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12905         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12906         CopyBoard(boards[1], initial_position);
12907         DisplayMessage("", _("Black to play"));
12908     } else {
12909         currentMove = forwardMostMove = backwardMostMove = 0;
12910         DisplayMessage("", _("White to play"));
12911     }
12912     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12913     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12914         SendToProgram("force\n", &first);
12915         SendBoard(&first, forwardMostMove);
12916     }
12917     if (appData.debugMode) {
12918 int i, j;
12919   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12920   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12921         fprintf(debugFP, "Load Position\n");
12922     }
12923
12924     if (positionNumber > 1) {
12925       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12926         DisplayTitle(line);
12927     } else {
12928         DisplayTitle(title);
12929     }
12930     gameMode = EditGame;
12931     ModeHighlight();
12932     ResetClocks();
12933     timeRemaining[0][1] = whiteTimeRemaining;
12934     timeRemaining[1][1] = blackTimeRemaining;
12935     DrawPosition(FALSE, boards[currentMove]);
12936
12937     return TRUE;
12938 }
12939
12940
12941 void
12942 CopyPlayerNameIntoFileName (char **dest, char *src)
12943 {
12944     while (*src != NULLCHAR && *src != ',') {
12945         if (*src == ' ') {
12946             *(*dest)++ = '_';
12947             src++;
12948         } else {
12949             *(*dest)++ = *src++;
12950         }
12951     }
12952 }
12953
12954 char *
12955 DefaultFileName (char *ext)
12956 {
12957     static char def[MSG_SIZ];
12958     char *p;
12959
12960     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12961         p = def;
12962         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12963         *p++ = '-';
12964         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12965         *p++ = '.';
12966         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12967     } else {
12968         def[0] = NULLCHAR;
12969     }
12970     return def;
12971 }
12972
12973 /* Save the current game to the given file */
12974 int
12975 SaveGameToFile (char *filename, int append)
12976 {
12977     FILE *f;
12978     char buf[MSG_SIZ];
12979     int result, i, t,tot=0;
12980
12981     if (strcmp(filename, "-") == 0) {
12982         return SaveGame(stdout, 0, NULL);
12983     } else {
12984         for(i=0; i<10; i++) { // upto 10 tries
12985              f = fopen(filename, append ? "a" : "w");
12986              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12987              if(f || errno != 13) break;
12988              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12989              tot += t;
12990         }
12991         if (f == NULL) {
12992             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12993             DisplayError(buf, errno);
12994             return FALSE;
12995         } else {
12996             safeStrCpy(buf, lastMsg, MSG_SIZ);
12997             DisplayMessage(_("Waiting for access to save file"), "");
12998             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12999             DisplayMessage(_("Saving game"), "");
13000             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13001             result = SaveGame(f, 0, NULL);
13002             DisplayMessage(buf, "");
13003             return result;
13004         }
13005     }
13006 }
13007
13008 char *
13009 SavePart (char *str)
13010 {
13011     static char buf[MSG_SIZ];
13012     char *p;
13013
13014     p = strchr(str, ' ');
13015     if (p == NULL) return str;
13016     strncpy(buf, str, p - str);
13017     buf[p - str] = NULLCHAR;
13018     return buf;
13019 }
13020
13021 #define PGN_MAX_LINE 75
13022
13023 #define PGN_SIDE_WHITE  0
13024 #define PGN_SIDE_BLACK  1
13025
13026 static int
13027 FindFirstMoveOutOfBook (int side)
13028 {
13029     int result = -1;
13030
13031     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13032         int index = backwardMostMove;
13033         int has_book_hit = 0;
13034
13035         if( (index % 2) != side ) {
13036             index++;
13037         }
13038
13039         while( index < forwardMostMove ) {
13040             /* Check to see if engine is in book */
13041             int depth = pvInfoList[index].depth;
13042             int score = pvInfoList[index].score;
13043             int in_book = 0;
13044
13045             if( depth <= 2 ) {
13046                 in_book = 1;
13047             }
13048             else if( score == 0 && depth == 63 ) {
13049                 in_book = 1; /* Zappa */
13050             }
13051             else if( score == 2 && depth == 99 ) {
13052                 in_book = 1; /* Abrok */
13053             }
13054
13055             has_book_hit += in_book;
13056
13057             if( ! in_book ) {
13058                 result = index;
13059
13060                 break;
13061             }
13062
13063             index += 2;
13064         }
13065     }
13066
13067     return result;
13068 }
13069
13070 void
13071 GetOutOfBookInfo (char * buf)
13072 {
13073     int oob[2];
13074     int i;
13075     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13076
13077     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13078     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13079
13080     *buf = '\0';
13081
13082     if( oob[0] >= 0 || oob[1] >= 0 ) {
13083         for( i=0; i<2; i++ ) {
13084             int idx = oob[i];
13085
13086             if( idx >= 0 ) {
13087                 if( i > 0 && oob[0] >= 0 ) {
13088                     strcat( buf, "   " );
13089                 }
13090
13091                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13092                 sprintf( buf+strlen(buf), "%s%.2f",
13093                     pvInfoList[idx].score >= 0 ? "+" : "",
13094                     pvInfoList[idx].score / 100.0 );
13095             }
13096         }
13097     }
13098 }
13099
13100 /* Save game in PGN style and close the file */
13101 int
13102 SaveGamePGN (FILE *f)
13103 {
13104     int i, offset, linelen, newblock;
13105 //    char *movetext;
13106     char numtext[32];
13107     int movelen, numlen, blank;
13108     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13109
13110     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13111
13112     PrintPGNTags(f, &gameInfo);
13113
13114     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13115
13116     if (backwardMostMove > 0 || startedFromSetupPosition) {
13117         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13118         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13119         fprintf(f, "\n{--------------\n");
13120         PrintPosition(f, backwardMostMove);
13121         fprintf(f, "--------------}\n");
13122         free(fen);
13123     }
13124     else {
13125         /* [AS] Out of book annotation */
13126         if( appData.saveOutOfBookInfo ) {
13127             char buf[64];
13128
13129             GetOutOfBookInfo( buf );
13130
13131             if( buf[0] != '\0' ) {
13132                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13133             }
13134         }
13135
13136         fprintf(f, "\n");
13137     }
13138
13139     i = backwardMostMove;
13140     linelen = 0;
13141     newblock = TRUE;
13142
13143     while (i < forwardMostMove) {
13144         /* Print comments preceding this move */
13145         if (commentList[i] != NULL) {
13146             if (linelen > 0) fprintf(f, "\n");
13147             fprintf(f, "%s", commentList[i]);
13148             linelen = 0;
13149             newblock = TRUE;
13150         }
13151
13152         /* Format move number */
13153         if ((i % 2) == 0)
13154           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13155         else
13156           if (newblock)
13157             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13158           else
13159             numtext[0] = NULLCHAR;
13160
13161         numlen = strlen(numtext);
13162         newblock = FALSE;
13163
13164         /* Print move number */
13165         blank = linelen > 0 && numlen > 0;
13166         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13167             fprintf(f, "\n");
13168             linelen = 0;
13169             blank = 0;
13170         }
13171         if (blank) {
13172             fprintf(f, " ");
13173             linelen++;
13174         }
13175         fprintf(f, "%s", numtext);
13176         linelen += numlen;
13177
13178         /* Get move */
13179         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13180         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13181
13182         /* Print move */
13183         blank = linelen > 0 && movelen > 0;
13184         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13185             fprintf(f, "\n");
13186             linelen = 0;
13187             blank = 0;
13188         }
13189         if (blank) {
13190             fprintf(f, " ");
13191             linelen++;
13192         }
13193         fprintf(f, "%s", move_buffer);
13194         linelen += movelen;
13195
13196         /* [AS] Add PV info if present */
13197         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13198             /* [HGM] add time */
13199             char buf[MSG_SIZ]; int seconds;
13200
13201             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13202
13203             if( seconds <= 0)
13204               buf[0] = 0;
13205             else
13206               if( seconds < 30 )
13207                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13208               else
13209                 {
13210                   seconds = (seconds + 4)/10; // round to full seconds
13211                   if( seconds < 60 )
13212                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13213                   else
13214                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13215                 }
13216
13217             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13218                       pvInfoList[i].score >= 0 ? "+" : "",
13219                       pvInfoList[i].score / 100.0,
13220                       pvInfoList[i].depth,
13221                       buf );
13222
13223             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13224
13225             /* Print score/depth */
13226             blank = linelen > 0 && movelen > 0;
13227             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13228                 fprintf(f, "\n");
13229                 linelen = 0;
13230                 blank = 0;
13231             }
13232             if (blank) {
13233                 fprintf(f, " ");
13234                 linelen++;
13235             }
13236             fprintf(f, "%s", move_buffer);
13237             linelen += movelen;
13238         }
13239
13240         i++;
13241     }
13242
13243     /* Start a new line */
13244     if (linelen > 0) fprintf(f, "\n");
13245
13246     /* Print comments after last move */
13247     if (commentList[i] != NULL) {
13248         fprintf(f, "%s\n", commentList[i]);
13249     }
13250
13251     /* Print result */
13252     if (gameInfo.resultDetails != NULL &&
13253         gameInfo.resultDetails[0] != NULLCHAR) {
13254         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13255         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13256            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13257             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13258         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13259     } else {
13260         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13261     }
13262
13263     fclose(f);
13264     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13265     return TRUE;
13266 }
13267
13268 /* Save game in old style and close the file */
13269 int
13270 SaveGameOldStyle (FILE *f)
13271 {
13272     int i, offset;
13273     time_t tm;
13274
13275     tm = time((time_t *) NULL);
13276
13277     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13278     PrintOpponents(f);
13279
13280     if (backwardMostMove > 0 || startedFromSetupPosition) {
13281         fprintf(f, "\n[--------------\n");
13282         PrintPosition(f, backwardMostMove);
13283         fprintf(f, "--------------]\n");
13284     } else {
13285         fprintf(f, "\n");
13286     }
13287
13288     i = backwardMostMove;
13289     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13290
13291     while (i < forwardMostMove) {
13292         if (commentList[i] != NULL) {
13293             fprintf(f, "[%s]\n", commentList[i]);
13294         }
13295
13296         if ((i % 2) == 1) {
13297             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13298             i++;
13299         } else {
13300             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13301             i++;
13302             if (commentList[i] != NULL) {
13303                 fprintf(f, "\n");
13304                 continue;
13305             }
13306             if (i >= forwardMostMove) {
13307                 fprintf(f, "\n");
13308                 break;
13309             }
13310             fprintf(f, "%s\n", parseList[i]);
13311             i++;
13312         }
13313     }
13314
13315     if (commentList[i] != NULL) {
13316         fprintf(f, "[%s]\n", commentList[i]);
13317     }
13318
13319     /* This isn't really the old style, but it's close enough */
13320     if (gameInfo.resultDetails != NULL &&
13321         gameInfo.resultDetails[0] != NULLCHAR) {
13322         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13323                 gameInfo.resultDetails);
13324     } else {
13325         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13326     }
13327
13328     fclose(f);
13329     return TRUE;
13330 }
13331
13332 /* Save the current game to open file f and close the file */
13333 int
13334 SaveGame (FILE *f, int dummy, char *dummy2)
13335 {
13336     if (gameMode == EditPosition) EditPositionDone(TRUE);
13337     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13338     if (appData.oldSaveStyle)
13339       return SaveGameOldStyle(f);
13340     else
13341       return SaveGamePGN(f);
13342 }
13343
13344 /* Save the current position to the given file */
13345 int
13346 SavePositionToFile (char *filename)
13347 {
13348     FILE *f;
13349     char buf[MSG_SIZ];
13350
13351     if (strcmp(filename, "-") == 0) {
13352         return SavePosition(stdout, 0, NULL);
13353     } else {
13354         f = fopen(filename, "a");
13355         if (f == NULL) {
13356             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13357             DisplayError(buf, errno);
13358             return FALSE;
13359         } else {
13360             safeStrCpy(buf, lastMsg, MSG_SIZ);
13361             DisplayMessage(_("Waiting for access to save file"), "");
13362             flock(fileno(f), LOCK_EX); // [HGM] lock
13363             DisplayMessage(_("Saving position"), "");
13364             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13365             SavePosition(f, 0, NULL);
13366             DisplayMessage(buf, "");
13367             return TRUE;
13368         }
13369     }
13370 }
13371
13372 /* Save the current position to the given open file and close the file */
13373 int
13374 SavePosition (FILE *f, int dummy, char *dummy2)
13375 {
13376     time_t tm;
13377     char *fen;
13378
13379     if (gameMode == EditPosition) EditPositionDone(TRUE);
13380     if (appData.oldSaveStyle) {
13381         tm = time((time_t *) NULL);
13382
13383         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13384         PrintOpponents(f);
13385         fprintf(f, "[--------------\n");
13386         PrintPosition(f, currentMove);
13387         fprintf(f, "--------------]\n");
13388     } else {
13389         fen = PositionToFEN(currentMove, NULL, 1);
13390         fprintf(f, "%s\n", fen);
13391         free(fen);
13392     }
13393     fclose(f);
13394     return TRUE;
13395 }
13396
13397 void
13398 ReloadCmailMsgEvent (int unregister)
13399 {
13400 #if !WIN32
13401     static char *inFilename = NULL;
13402     static char *outFilename;
13403     int i;
13404     struct stat inbuf, outbuf;
13405     int status;
13406
13407     /* Any registered moves are unregistered if unregister is set, */
13408     /* i.e. invoked by the signal handler */
13409     if (unregister) {
13410         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13411             cmailMoveRegistered[i] = FALSE;
13412             if (cmailCommentList[i] != NULL) {
13413                 free(cmailCommentList[i]);
13414                 cmailCommentList[i] = NULL;
13415             }
13416         }
13417         nCmailMovesRegistered = 0;
13418     }
13419
13420     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13421         cmailResult[i] = CMAIL_NOT_RESULT;
13422     }
13423     nCmailResults = 0;
13424
13425     if (inFilename == NULL) {
13426         /* Because the filenames are static they only get malloced once  */
13427         /* and they never get freed                                      */
13428         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13429         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13430
13431         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13432         sprintf(outFilename, "%s.out", appData.cmailGameName);
13433     }
13434
13435     status = stat(outFilename, &outbuf);
13436     if (status < 0) {
13437         cmailMailedMove = FALSE;
13438     } else {
13439         status = stat(inFilename, &inbuf);
13440         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13441     }
13442
13443     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13444        counts the games, notes how each one terminated, etc.
13445
13446        It would be nice to remove this kludge and instead gather all
13447        the information while building the game list.  (And to keep it
13448        in the game list nodes instead of having a bunch of fixed-size
13449        parallel arrays.)  Note this will require getting each game's
13450        termination from the PGN tags, as the game list builder does
13451        not process the game moves.  --mann
13452        */
13453     cmailMsgLoaded = TRUE;
13454     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13455
13456     /* Load first game in the file or popup game menu */
13457     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13458
13459 #endif /* !WIN32 */
13460     return;
13461 }
13462
13463 int
13464 RegisterMove ()
13465 {
13466     FILE *f;
13467     char string[MSG_SIZ];
13468
13469     if (   cmailMailedMove
13470         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13471         return TRUE;            /* Allow free viewing  */
13472     }
13473
13474     /* Unregister move to ensure that we don't leave RegisterMove        */
13475     /* with the move registered when the conditions for registering no   */
13476     /* longer hold                                                       */
13477     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13478         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13479         nCmailMovesRegistered --;
13480
13481         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13482           {
13483               free(cmailCommentList[lastLoadGameNumber - 1]);
13484               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13485           }
13486     }
13487
13488     if (cmailOldMove == -1) {
13489         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13490         return FALSE;
13491     }
13492
13493     if (currentMove > cmailOldMove + 1) {
13494         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13495         return FALSE;
13496     }
13497
13498     if (currentMove < cmailOldMove) {
13499         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13500         return FALSE;
13501     }
13502
13503     if (forwardMostMove > currentMove) {
13504         /* Silently truncate extra moves */
13505         TruncateGame();
13506     }
13507
13508     if (   (currentMove == cmailOldMove + 1)
13509         || (   (currentMove == cmailOldMove)
13510             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13511                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13512         if (gameInfo.result != GameUnfinished) {
13513             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13514         }
13515
13516         if (commentList[currentMove] != NULL) {
13517             cmailCommentList[lastLoadGameNumber - 1]
13518               = StrSave(commentList[currentMove]);
13519         }
13520         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13521
13522         if (appData.debugMode)
13523           fprintf(debugFP, "Saving %s for game %d\n",
13524                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13525
13526         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13527
13528         f = fopen(string, "w");
13529         if (appData.oldSaveStyle) {
13530             SaveGameOldStyle(f); /* also closes the file */
13531
13532             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13533             f = fopen(string, "w");
13534             SavePosition(f, 0, NULL); /* also closes the file */
13535         } else {
13536             fprintf(f, "{--------------\n");
13537             PrintPosition(f, currentMove);
13538             fprintf(f, "--------------}\n\n");
13539
13540             SaveGame(f, 0, NULL); /* also closes the file*/
13541         }
13542
13543         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13544         nCmailMovesRegistered ++;
13545     } else if (nCmailGames == 1) {
13546         DisplayError(_("You have not made a move yet"), 0);
13547         return FALSE;
13548     }
13549
13550     return TRUE;
13551 }
13552
13553 void
13554 MailMoveEvent ()
13555 {
13556 #if !WIN32
13557     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13558     FILE *commandOutput;
13559     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13560     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13561     int nBuffers;
13562     int i;
13563     int archived;
13564     char *arcDir;
13565
13566     if (! cmailMsgLoaded) {
13567         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13568         return;
13569     }
13570
13571     if (nCmailGames == nCmailResults) {
13572         DisplayError(_("No unfinished games"), 0);
13573         return;
13574     }
13575
13576 #if CMAIL_PROHIBIT_REMAIL
13577     if (cmailMailedMove) {
13578       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);
13579         DisplayError(msg, 0);
13580         return;
13581     }
13582 #endif
13583
13584     if (! (cmailMailedMove || RegisterMove())) return;
13585
13586     if (   cmailMailedMove
13587         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13588       snprintf(string, MSG_SIZ, partCommandString,
13589                appData.debugMode ? " -v" : "", appData.cmailGameName);
13590         commandOutput = popen(string, "r");
13591
13592         if (commandOutput == NULL) {
13593             DisplayError(_("Failed to invoke cmail"), 0);
13594         } else {
13595             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13596                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13597             }
13598             if (nBuffers > 1) {
13599                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13600                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13601                 nBytes = MSG_SIZ - 1;
13602             } else {
13603                 (void) memcpy(msg, buffer, nBytes);
13604             }
13605             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13606
13607             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13608                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13609
13610                 archived = TRUE;
13611                 for (i = 0; i < nCmailGames; i ++) {
13612                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13613                         archived = FALSE;
13614                     }
13615                 }
13616                 if (   archived
13617                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13618                         != NULL)) {
13619                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13620                            arcDir,
13621                            appData.cmailGameName,
13622                            gameInfo.date);
13623                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13624                     cmailMsgLoaded = FALSE;
13625                 }
13626             }
13627
13628             DisplayInformation(msg);
13629             pclose(commandOutput);
13630         }
13631     } else {
13632         if ((*cmailMsg) != '\0') {
13633             DisplayInformation(cmailMsg);
13634         }
13635     }
13636
13637     return;
13638 #endif /* !WIN32 */
13639 }
13640
13641 char *
13642 CmailMsg ()
13643 {
13644 #if WIN32
13645     return NULL;
13646 #else
13647     int  prependComma = 0;
13648     char number[5];
13649     char string[MSG_SIZ];       /* Space for game-list */
13650     int  i;
13651
13652     if (!cmailMsgLoaded) return "";
13653
13654     if (cmailMailedMove) {
13655       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13656     } else {
13657         /* Create a list of games left */
13658       snprintf(string, MSG_SIZ, "[");
13659         for (i = 0; i < nCmailGames; i ++) {
13660             if (! (   cmailMoveRegistered[i]
13661                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13662                 if (prependComma) {
13663                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13664                 } else {
13665                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13666                     prependComma = 1;
13667                 }
13668
13669                 strcat(string, number);
13670             }
13671         }
13672         strcat(string, "]");
13673
13674         if (nCmailMovesRegistered + nCmailResults == 0) {
13675             switch (nCmailGames) {
13676               case 1:
13677                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13678                 break;
13679
13680               case 2:
13681                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13682                 break;
13683
13684               default:
13685                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13686                          nCmailGames);
13687                 break;
13688             }
13689         } else {
13690             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13691               case 1:
13692                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13693                          string);
13694                 break;
13695
13696               case 0:
13697                 if (nCmailResults == nCmailGames) {
13698                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13699                 } else {
13700                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13701                 }
13702                 break;
13703
13704               default:
13705                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13706                          string);
13707             }
13708         }
13709     }
13710     return cmailMsg;
13711 #endif /* WIN32 */
13712 }
13713
13714 void
13715 ResetGameEvent ()
13716 {
13717     if (gameMode == Training)
13718       SetTrainingModeOff();
13719
13720     Reset(TRUE, TRUE);
13721     cmailMsgLoaded = FALSE;
13722     if (appData.icsActive) {
13723       SendToICS(ics_prefix);
13724       SendToICS("refresh\n");
13725     }
13726 }
13727
13728 void
13729 ExitEvent (int status)
13730 {
13731     exiting++;
13732     if (exiting > 2) {
13733       /* Give up on clean exit */
13734       exit(status);
13735     }
13736     if (exiting > 1) {
13737       /* Keep trying for clean exit */
13738       return;
13739     }
13740
13741     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13742
13743     if (telnetISR != NULL) {
13744       RemoveInputSource(telnetISR);
13745     }
13746     if (icsPR != NoProc) {
13747       DestroyChildProcess(icsPR, TRUE);
13748     }
13749
13750     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13751     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13752
13753     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13754     /* make sure this other one finishes before killing it!                  */
13755     if(endingGame) { int count = 0;
13756         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13757         while(endingGame && count++ < 10) DoSleep(1);
13758         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13759     }
13760
13761     /* Kill off chess programs */
13762     if (first.pr != NoProc) {
13763         ExitAnalyzeMode();
13764
13765         DoSleep( appData.delayBeforeQuit );
13766         SendToProgram("quit\n", &first);
13767         DoSleep( appData.delayAfterQuit );
13768         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13769     }
13770     if (second.pr != NoProc) {
13771         DoSleep( appData.delayBeforeQuit );
13772         SendToProgram("quit\n", &second);
13773         DoSleep( appData.delayAfterQuit );
13774         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13775     }
13776     if (first.isr != NULL) {
13777         RemoveInputSource(first.isr);
13778     }
13779     if (second.isr != NULL) {
13780         RemoveInputSource(second.isr);
13781     }
13782
13783     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13784     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13785
13786     ShutDownFrontEnd();
13787     exit(status);
13788 }
13789
13790 void
13791 PauseEngine (ChessProgramState *cps)
13792 {
13793     SendToProgram("pause\n", cps);
13794     cps->pause = 2;
13795 }
13796
13797 void
13798 UnPauseEngine (ChessProgramState *cps)
13799 {
13800     SendToProgram("resume\n", cps);
13801     cps->pause = 1;
13802 }
13803
13804 void
13805 PauseEvent ()
13806 {
13807     if (appData.debugMode)
13808         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13809     if (pausing) {
13810         pausing = FALSE;
13811         ModeHighlight();
13812         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13813             StartClocks();
13814             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13815                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13816                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13817             }
13818             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13819             HandleMachineMove(stashedInputMove, stalledEngine);
13820             stalledEngine = NULL;
13821             return;
13822         }
13823         if (gameMode == MachinePlaysWhite ||
13824             gameMode == TwoMachinesPlay   ||
13825             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13826             if(first.pause)  UnPauseEngine(&first);
13827             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13828             if(second.pause) UnPauseEngine(&second);
13829             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13830             StartClocks();
13831         } else {
13832             DisplayBothClocks();
13833         }
13834         if (gameMode == PlayFromGameFile) {
13835             if (appData.timeDelay >= 0)
13836                 AutoPlayGameLoop();
13837         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13838             Reset(FALSE, TRUE);
13839             SendToICS(ics_prefix);
13840             SendToICS("refresh\n");
13841         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13842             ForwardInner(forwardMostMove);
13843         }
13844         pauseExamInvalid = FALSE;
13845     } else {
13846         switch (gameMode) {
13847           default:
13848             return;
13849           case IcsExamining:
13850             pauseExamForwardMostMove = forwardMostMove;
13851             pauseExamInvalid = FALSE;
13852             /* fall through */
13853           case IcsObserving:
13854           case IcsPlayingWhite:
13855           case IcsPlayingBlack:
13856             pausing = TRUE;
13857             ModeHighlight();
13858             return;
13859           case PlayFromGameFile:
13860             (void) StopLoadGameTimer();
13861             pausing = TRUE;
13862             ModeHighlight();
13863             break;
13864           case BeginningOfGame:
13865             if (appData.icsActive) return;
13866             /* else fall through */
13867           case MachinePlaysWhite:
13868           case MachinePlaysBlack:
13869           case TwoMachinesPlay:
13870             if (forwardMostMove == 0)
13871               return;           /* don't pause if no one has moved */
13872             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13873                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13874                 if(onMove->pause) {           // thinking engine can be paused
13875                     PauseEngine(onMove);      // do it
13876                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13877                         PauseEngine(onMove->other);
13878                     else
13879                         SendToProgram("easy\n", onMove->other);
13880                     StopClocks();
13881                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13882             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13883                 if(first.pause) {
13884                     PauseEngine(&first);
13885                     StopClocks();
13886                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13887             } else { // human on move, pause pondering by either method
13888                 if(first.pause)
13889                     PauseEngine(&first);
13890                 else if(appData.ponderNextMove)
13891                     SendToProgram("easy\n", &first);
13892                 StopClocks();
13893             }
13894             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13895           case AnalyzeMode:
13896             pausing = TRUE;
13897             ModeHighlight();
13898             break;
13899         }
13900     }
13901 }
13902
13903 void
13904 EditCommentEvent ()
13905 {
13906     char title[MSG_SIZ];
13907
13908     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13909       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13910     } else {
13911       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13912                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13913                parseList[currentMove - 1]);
13914     }
13915
13916     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13917 }
13918
13919
13920 void
13921 EditTagsEvent ()
13922 {
13923     char *tags = PGNTags(&gameInfo);
13924     bookUp = FALSE;
13925     EditTagsPopUp(tags, NULL);
13926     free(tags);
13927 }
13928
13929 void
13930 ToggleSecond ()
13931 {
13932   if(second.analyzing) {
13933     SendToProgram("exit\n", &second);
13934     second.analyzing = FALSE;
13935   } else {
13936     if (second.pr == NoProc) StartChessProgram(&second);
13937     InitChessProgram(&second, FALSE);
13938     FeedMovesToProgram(&second, currentMove);
13939
13940     SendToProgram("analyze\n", &second);
13941     second.analyzing = TRUE;
13942   }
13943 }
13944
13945 /* Toggle ShowThinking */
13946 void
13947 ToggleShowThinking()
13948 {
13949   appData.showThinking = !appData.showThinking;
13950   ShowThinkingEvent();
13951 }
13952
13953 int
13954 AnalyzeModeEvent ()
13955 {
13956     char buf[MSG_SIZ];
13957
13958     if (!first.analysisSupport) {
13959       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13960       DisplayError(buf, 0);
13961       return 0;
13962     }
13963     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13964     if (appData.icsActive) {
13965         if (gameMode != IcsObserving) {
13966           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13967             DisplayError(buf, 0);
13968             /* secure check */
13969             if (appData.icsEngineAnalyze) {
13970                 if (appData.debugMode)
13971                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13972                 ExitAnalyzeMode();
13973                 ModeHighlight();
13974             }
13975             return 0;
13976         }
13977         /* if enable, user wants to disable icsEngineAnalyze */
13978         if (appData.icsEngineAnalyze) {
13979                 ExitAnalyzeMode();
13980                 ModeHighlight();
13981                 return 0;
13982         }
13983         appData.icsEngineAnalyze = TRUE;
13984         if (appData.debugMode)
13985             fprintf(debugFP, "ICS engine analyze starting... \n");
13986     }
13987
13988     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13989     if (appData.noChessProgram || gameMode == AnalyzeMode)
13990       return 0;
13991
13992     if (gameMode != AnalyzeFile) {
13993         if (!appData.icsEngineAnalyze) {
13994                EditGameEvent();
13995                if (gameMode != EditGame) return 0;
13996         }
13997         if (!appData.showThinking) ToggleShowThinking();
13998         ResurrectChessProgram();
13999         SendToProgram("analyze\n", &first);
14000         first.analyzing = TRUE;
14001         /*first.maybeThinking = TRUE;*/
14002         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14003         EngineOutputPopUp();
14004     }
14005     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14006     pausing = FALSE;
14007     ModeHighlight();
14008     SetGameInfo();
14009
14010     StartAnalysisClock();
14011     GetTimeMark(&lastNodeCountTime);
14012     lastNodeCount = 0;
14013     return 1;
14014 }
14015
14016 void
14017 AnalyzeFileEvent ()
14018 {
14019     if (appData.noChessProgram || gameMode == AnalyzeFile)
14020       return;
14021
14022     if (!first.analysisSupport) {
14023       char buf[MSG_SIZ];
14024       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14025       DisplayError(buf, 0);
14026       return;
14027     }
14028
14029     if (gameMode != AnalyzeMode) {
14030         keepInfo = 1; // mere annotating should not alter PGN tags
14031         EditGameEvent();
14032         keepInfo = 0;
14033         if (gameMode != EditGame) return;
14034         if (!appData.showThinking) ToggleShowThinking();
14035         ResurrectChessProgram();
14036         SendToProgram("analyze\n", &first);
14037         first.analyzing = TRUE;
14038         /*first.maybeThinking = TRUE;*/
14039         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14040         EngineOutputPopUp();
14041     }
14042     gameMode = AnalyzeFile;
14043     pausing = FALSE;
14044     ModeHighlight();
14045
14046     StartAnalysisClock();
14047     GetTimeMark(&lastNodeCountTime);
14048     lastNodeCount = 0;
14049     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14050     AnalysisPeriodicEvent(1);
14051 }
14052
14053 void
14054 MachineWhiteEvent ()
14055 {
14056     char buf[MSG_SIZ];
14057     char *bookHit = NULL;
14058
14059     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14060       return;
14061
14062
14063     if (gameMode == PlayFromGameFile ||
14064         gameMode == TwoMachinesPlay  ||
14065         gameMode == Training         ||
14066         gameMode == AnalyzeMode      ||
14067         gameMode == EndOfGame)
14068         EditGameEvent();
14069
14070     if (gameMode == EditPosition)
14071         EditPositionDone(TRUE);
14072
14073     if (!WhiteOnMove(currentMove)) {
14074         DisplayError(_("It is not White's turn"), 0);
14075         return;
14076     }
14077
14078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14079       ExitAnalyzeMode();
14080
14081     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14082         gameMode == AnalyzeFile)
14083         TruncateGame();
14084
14085     ResurrectChessProgram();    /* in case it isn't running */
14086     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14087         gameMode = MachinePlaysWhite;
14088         ResetClocks();
14089     } else
14090     gameMode = MachinePlaysWhite;
14091     pausing = FALSE;
14092     ModeHighlight();
14093     SetGameInfo();
14094     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14095     DisplayTitle(buf);
14096     if (first.sendName) {
14097       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14098       SendToProgram(buf, &first);
14099     }
14100     if (first.sendTime) {
14101       if (first.useColors) {
14102         SendToProgram("black\n", &first); /*gnu kludge*/
14103       }
14104       SendTimeRemaining(&first, TRUE);
14105     }
14106     if (first.useColors) {
14107       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14108     }
14109     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14110     SetMachineThinkingEnables();
14111     first.maybeThinking = TRUE;
14112     StartClocks();
14113     firstMove = FALSE;
14114
14115     if (appData.autoFlipView && !flipView) {
14116       flipView = !flipView;
14117       DrawPosition(FALSE, NULL);
14118       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14119     }
14120
14121     if(bookHit) { // [HGM] book: simulate book reply
14122         static char bookMove[MSG_SIZ]; // a bit generous?
14123
14124         programStats.nodes = programStats.depth = programStats.time =
14125         programStats.score = programStats.got_only_move = 0;
14126         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14127
14128         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14129         strcat(bookMove, bookHit);
14130         HandleMachineMove(bookMove, &first);
14131     }
14132 }
14133
14134 void
14135 MachineBlackEvent ()
14136 {
14137   char buf[MSG_SIZ];
14138   char *bookHit = NULL;
14139
14140     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14141         return;
14142
14143
14144     if (gameMode == PlayFromGameFile ||
14145         gameMode == TwoMachinesPlay  ||
14146         gameMode == Training         ||
14147         gameMode == AnalyzeMode      ||
14148         gameMode == EndOfGame)
14149         EditGameEvent();
14150
14151     if (gameMode == EditPosition)
14152         EditPositionDone(TRUE);
14153
14154     if (WhiteOnMove(currentMove)) {
14155         DisplayError(_("It is not Black's turn"), 0);
14156         return;
14157     }
14158
14159     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14160       ExitAnalyzeMode();
14161
14162     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14163         gameMode == AnalyzeFile)
14164         TruncateGame();
14165
14166     ResurrectChessProgram();    /* in case it isn't running */
14167     gameMode = MachinePlaysBlack;
14168     pausing = FALSE;
14169     ModeHighlight();
14170     SetGameInfo();
14171     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14172     DisplayTitle(buf);
14173     if (first.sendName) {
14174       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14175       SendToProgram(buf, &first);
14176     }
14177     if (first.sendTime) {
14178       if (first.useColors) {
14179         SendToProgram("white\n", &first); /*gnu kludge*/
14180       }
14181       SendTimeRemaining(&first, FALSE);
14182     }
14183     if (first.useColors) {
14184       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14185     }
14186     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14187     SetMachineThinkingEnables();
14188     first.maybeThinking = TRUE;
14189     StartClocks();
14190
14191     if (appData.autoFlipView && flipView) {
14192       flipView = !flipView;
14193       DrawPosition(FALSE, NULL);
14194       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14195     }
14196     if(bookHit) { // [HGM] book: simulate book reply
14197         static char bookMove[MSG_SIZ]; // a bit generous?
14198
14199         programStats.nodes = programStats.depth = programStats.time =
14200         programStats.score = programStats.got_only_move = 0;
14201         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14202
14203         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14204         strcat(bookMove, bookHit);
14205         HandleMachineMove(bookMove, &first);
14206     }
14207 }
14208
14209
14210 void
14211 DisplayTwoMachinesTitle ()
14212 {
14213     char buf[MSG_SIZ];
14214     if (appData.matchGames > 0) {
14215         if(appData.tourneyFile[0]) {
14216           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14217                    gameInfo.white, _("vs."), gameInfo.black,
14218                    nextGame+1, appData.matchGames+1,
14219                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14220         } else
14221         if (first.twoMachinesColor[0] == 'w') {
14222           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14223                    gameInfo.white, _("vs."),  gameInfo.black,
14224                    first.matchWins, second.matchWins,
14225                    matchGame - 1 - (first.matchWins + second.matchWins));
14226         } else {
14227           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14228                    gameInfo.white, _("vs."), gameInfo.black,
14229                    second.matchWins, first.matchWins,
14230                    matchGame - 1 - (first.matchWins + second.matchWins));
14231         }
14232     } else {
14233       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14234     }
14235     DisplayTitle(buf);
14236 }
14237
14238 void
14239 SettingsMenuIfReady ()
14240 {
14241   if (second.lastPing != second.lastPong) {
14242     DisplayMessage("", _("Waiting for second chess program"));
14243     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14244     return;
14245   }
14246   ThawUI();
14247   DisplayMessage("", "");
14248   SettingsPopUp(&second);
14249 }
14250
14251 int
14252 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14253 {
14254     char buf[MSG_SIZ];
14255     if (cps->pr == NoProc) {
14256         StartChessProgram(cps);
14257         if (cps->protocolVersion == 1) {
14258           retry();
14259           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14260         } else {
14261           /* kludge: allow timeout for initial "feature" command */
14262           if(retry != TwoMachinesEventIfReady) FreezeUI();
14263           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14264           DisplayMessage("", buf);
14265           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14266         }
14267         return 1;
14268     }
14269     return 0;
14270 }
14271
14272 void
14273 TwoMachinesEvent P((void))
14274 {
14275     int i;
14276     char buf[MSG_SIZ];
14277     ChessProgramState *onmove;
14278     char *bookHit = NULL;
14279     static int stalling = 0;
14280     TimeMark now;
14281     long wait;
14282
14283     if (appData.noChessProgram) return;
14284
14285     switch (gameMode) {
14286       case TwoMachinesPlay:
14287         return;
14288       case MachinePlaysWhite:
14289       case MachinePlaysBlack:
14290         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14291             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14292             return;
14293         }
14294         /* fall through */
14295       case BeginningOfGame:
14296       case PlayFromGameFile:
14297       case EndOfGame:
14298         EditGameEvent();
14299         if (gameMode != EditGame) return;
14300         break;
14301       case EditPosition:
14302         EditPositionDone(TRUE);
14303         break;
14304       case AnalyzeMode:
14305       case AnalyzeFile:
14306         ExitAnalyzeMode();
14307         break;
14308       case EditGame:
14309       default:
14310         break;
14311     }
14312
14313 //    forwardMostMove = currentMove;
14314     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14315     startingEngine = TRUE;
14316
14317     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14318
14319     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14320     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14321       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14322       return;
14323     }
14324     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14325
14326     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14327         startingEngine = FALSE;
14328         DisplayError("second engine does not play this", 0);
14329         return;
14330     }
14331
14332     if(!stalling) {
14333       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14334       SendToProgram("force\n", &second);
14335       stalling = 1;
14336       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14337       return;
14338     }
14339     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14340     if(appData.matchPause>10000 || appData.matchPause<10)
14341                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14342     wait = SubtractTimeMarks(&now, &pauseStart);
14343     if(wait < appData.matchPause) {
14344         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14345         return;
14346     }
14347     // we are now committed to starting the game
14348     stalling = 0;
14349     DisplayMessage("", "");
14350     if (startedFromSetupPosition) {
14351         SendBoard(&second, backwardMostMove);
14352     if (appData.debugMode) {
14353         fprintf(debugFP, "Two Machines\n");
14354     }
14355     }
14356     for (i = backwardMostMove; i < forwardMostMove; i++) {
14357         SendMoveToProgram(i, &second);
14358     }
14359
14360     gameMode = TwoMachinesPlay;
14361     pausing = startingEngine = FALSE;
14362     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14363     SetGameInfo();
14364     DisplayTwoMachinesTitle();
14365     firstMove = TRUE;
14366     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14367         onmove = &first;
14368     } else {
14369         onmove = &second;
14370     }
14371     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14372     SendToProgram(first.computerString, &first);
14373     if (first.sendName) {
14374       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14375       SendToProgram(buf, &first);
14376     }
14377     SendToProgram(second.computerString, &second);
14378     if (second.sendName) {
14379       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14380       SendToProgram(buf, &second);
14381     }
14382
14383     ResetClocks();
14384     if (!first.sendTime || !second.sendTime) {
14385         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14386         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14387     }
14388     if (onmove->sendTime) {
14389       if (onmove->useColors) {
14390         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14391       }
14392       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14393     }
14394     if (onmove->useColors) {
14395       SendToProgram(onmove->twoMachinesColor, onmove);
14396     }
14397     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14398 //    SendToProgram("go\n", onmove);
14399     onmove->maybeThinking = TRUE;
14400     SetMachineThinkingEnables();
14401
14402     StartClocks();
14403
14404     if(bookHit) { // [HGM] book: simulate book reply
14405         static char bookMove[MSG_SIZ]; // a bit generous?
14406
14407         programStats.nodes = programStats.depth = programStats.time =
14408         programStats.score = programStats.got_only_move = 0;
14409         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14410
14411         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14412         strcat(bookMove, bookHit);
14413         savedMessage = bookMove; // args for deferred call
14414         savedState = onmove;
14415         ScheduleDelayedEvent(DeferredBookMove, 1);
14416     }
14417 }
14418
14419 void
14420 TrainingEvent ()
14421 {
14422     if (gameMode == Training) {
14423       SetTrainingModeOff();
14424       gameMode = PlayFromGameFile;
14425       DisplayMessage("", _("Training mode off"));
14426     } else {
14427       gameMode = Training;
14428       animateTraining = appData.animate;
14429
14430       /* make sure we are not already at the end of the game */
14431       if (currentMove < forwardMostMove) {
14432         SetTrainingModeOn();
14433         DisplayMessage("", _("Training mode on"));
14434       } else {
14435         gameMode = PlayFromGameFile;
14436         DisplayError(_("Already at end of game"), 0);
14437       }
14438     }
14439     ModeHighlight();
14440 }
14441
14442 void
14443 IcsClientEvent ()
14444 {
14445     if (!appData.icsActive) return;
14446     switch (gameMode) {
14447       case IcsPlayingWhite:
14448       case IcsPlayingBlack:
14449       case IcsObserving:
14450       case IcsIdle:
14451       case BeginningOfGame:
14452       case IcsExamining:
14453         return;
14454
14455       case EditGame:
14456         break;
14457
14458       case EditPosition:
14459         EditPositionDone(TRUE);
14460         break;
14461
14462       case AnalyzeMode:
14463       case AnalyzeFile:
14464         ExitAnalyzeMode();
14465         break;
14466
14467       default:
14468         EditGameEvent();
14469         break;
14470     }
14471
14472     gameMode = IcsIdle;
14473     ModeHighlight();
14474     return;
14475 }
14476
14477 void
14478 EditGameEvent ()
14479 {
14480     int i;
14481
14482     switch (gameMode) {
14483       case Training:
14484         SetTrainingModeOff();
14485         break;
14486       case MachinePlaysWhite:
14487       case MachinePlaysBlack:
14488       case BeginningOfGame:
14489         SendToProgram("force\n", &first);
14490         SetUserThinkingEnables();
14491         break;
14492       case PlayFromGameFile:
14493         (void) StopLoadGameTimer();
14494         if (gameFileFP != NULL) {
14495             gameFileFP = NULL;
14496         }
14497         break;
14498       case EditPosition:
14499         EditPositionDone(TRUE);
14500         break;
14501       case AnalyzeMode:
14502       case AnalyzeFile:
14503         ExitAnalyzeMode();
14504         SendToProgram("force\n", &first);
14505         break;
14506       case TwoMachinesPlay:
14507         GameEnds(EndOfFile, NULL, GE_PLAYER);
14508         ResurrectChessProgram();
14509         SetUserThinkingEnables();
14510         break;
14511       case EndOfGame:
14512         ResurrectChessProgram();
14513         break;
14514       case IcsPlayingBlack:
14515       case IcsPlayingWhite:
14516         DisplayError(_("Warning: You are still playing a game"), 0);
14517         break;
14518       case IcsObserving:
14519         DisplayError(_("Warning: You are still observing a game"), 0);
14520         break;
14521       case IcsExamining:
14522         DisplayError(_("Warning: You are still examining a game"), 0);
14523         break;
14524       case IcsIdle:
14525         break;
14526       case EditGame:
14527       default:
14528         return;
14529     }
14530
14531     pausing = FALSE;
14532     StopClocks();
14533     first.offeredDraw = second.offeredDraw = 0;
14534
14535     if (gameMode == PlayFromGameFile) {
14536         whiteTimeRemaining = timeRemaining[0][currentMove];
14537         blackTimeRemaining = timeRemaining[1][currentMove];
14538         DisplayTitle("");
14539     }
14540
14541     if (gameMode == MachinePlaysWhite ||
14542         gameMode == MachinePlaysBlack ||
14543         gameMode == TwoMachinesPlay ||
14544         gameMode == EndOfGame) {
14545         i = forwardMostMove;
14546         while (i > currentMove) {
14547             SendToProgram("undo\n", &first);
14548             i--;
14549         }
14550         if(!adjustedClock) {
14551         whiteTimeRemaining = timeRemaining[0][currentMove];
14552         blackTimeRemaining = timeRemaining[1][currentMove];
14553         DisplayBothClocks();
14554         }
14555         if (whiteFlag || blackFlag) {
14556             whiteFlag = blackFlag = 0;
14557         }
14558         DisplayTitle("");
14559     }
14560
14561     gameMode = EditGame;
14562     ModeHighlight();
14563     SetGameInfo();
14564 }
14565
14566
14567 void
14568 EditPositionEvent ()
14569 {
14570     if (gameMode == EditPosition) {
14571         EditGameEvent();
14572         return;
14573     }
14574
14575     EditGameEvent();
14576     if (gameMode != EditGame) return;
14577
14578     gameMode = EditPosition;
14579     ModeHighlight();
14580     SetGameInfo();
14581     if (currentMove > 0)
14582       CopyBoard(boards[0], boards[currentMove]);
14583
14584     blackPlaysFirst = !WhiteOnMove(currentMove);
14585     ResetClocks();
14586     currentMove = forwardMostMove = backwardMostMove = 0;
14587     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14588     DisplayMove(-1);
14589     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14590 }
14591
14592 void
14593 ExitAnalyzeMode ()
14594 {
14595     /* [DM] icsEngineAnalyze - possible call from other functions */
14596     if (appData.icsEngineAnalyze) {
14597         appData.icsEngineAnalyze = FALSE;
14598
14599         DisplayMessage("",_("Close ICS engine analyze..."));
14600     }
14601     if (first.analysisSupport && first.analyzing) {
14602       SendToBoth("exit\n");
14603       first.analyzing = second.analyzing = FALSE;
14604     }
14605     thinkOutput[0] = NULLCHAR;
14606 }
14607
14608 void
14609 EditPositionDone (Boolean fakeRights)
14610 {
14611     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14612
14613     startedFromSetupPosition = TRUE;
14614     InitChessProgram(&first, FALSE);
14615     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14616       boards[0][EP_STATUS] = EP_NONE;
14617       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14618       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14619         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14620         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14621       } else boards[0][CASTLING][2] = NoRights;
14622       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14623         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14624         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14625       } else boards[0][CASTLING][5] = NoRights;
14626       if(gameInfo.variant == VariantSChess) {
14627         int i;
14628         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14629           boards[0][VIRGIN][i] = 0;
14630           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14631           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14632         }
14633       }
14634     }
14635     SendToProgram("force\n", &first);
14636     if (blackPlaysFirst) {
14637         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14638         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14639         currentMove = forwardMostMove = backwardMostMove = 1;
14640         CopyBoard(boards[1], boards[0]);
14641     } else {
14642         currentMove = forwardMostMove = backwardMostMove = 0;
14643     }
14644     SendBoard(&first, forwardMostMove);
14645     if (appData.debugMode) {
14646         fprintf(debugFP, "EditPosDone\n");
14647     }
14648     DisplayTitle("");
14649     DisplayMessage("", "");
14650     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14651     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14652     gameMode = EditGame;
14653     ModeHighlight();
14654     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14655     ClearHighlights(); /* [AS] */
14656 }
14657
14658 /* Pause for `ms' milliseconds */
14659 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14660 void
14661 TimeDelay (long ms)
14662 {
14663     TimeMark m1, m2;
14664
14665     GetTimeMark(&m1);
14666     do {
14667         GetTimeMark(&m2);
14668     } while (SubtractTimeMarks(&m2, &m1) < ms);
14669 }
14670
14671 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14672 void
14673 SendMultiLineToICS (char *buf)
14674 {
14675     char temp[MSG_SIZ+1], *p;
14676     int len;
14677
14678     len = strlen(buf);
14679     if (len > MSG_SIZ)
14680       len = MSG_SIZ;
14681
14682     strncpy(temp, buf, len);
14683     temp[len] = 0;
14684
14685     p = temp;
14686     while (*p) {
14687         if (*p == '\n' || *p == '\r')
14688           *p = ' ';
14689         ++p;
14690     }
14691
14692     strcat(temp, "\n");
14693     SendToICS(temp);
14694     SendToPlayer(temp, strlen(temp));
14695 }
14696
14697 void
14698 SetWhiteToPlayEvent ()
14699 {
14700     if (gameMode == EditPosition) {
14701         blackPlaysFirst = FALSE;
14702         DisplayBothClocks();    /* works because currentMove is 0 */
14703     } else if (gameMode == IcsExamining) {
14704         SendToICS(ics_prefix);
14705         SendToICS("tomove white\n");
14706     }
14707 }
14708
14709 void
14710 SetBlackToPlayEvent ()
14711 {
14712     if (gameMode == EditPosition) {
14713         blackPlaysFirst = TRUE;
14714         currentMove = 1;        /* kludge */
14715         DisplayBothClocks();
14716         currentMove = 0;
14717     } else if (gameMode == IcsExamining) {
14718         SendToICS(ics_prefix);
14719         SendToICS("tomove black\n");
14720     }
14721 }
14722
14723 void
14724 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14725 {
14726     char buf[MSG_SIZ];
14727     ChessSquare piece = boards[0][y][x];
14728     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14729     static int lastVariant;
14730
14731     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14732
14733     switch (selection) {
14734       case ClearBoard:
14735         CopyBoard(currentBoard, boards[0]);
14736         CopyBoard(menuBoard, initialPosition);
14737         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14738             SendToICS(ics_prefix);
14739             SendToICS("bsetup clear\n");
14740         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14741             SendToICS(ics_prefix);
14742             SendToICS("clearboard\n");
14743         } else {
14744             int nonEmpty = 0;
14745             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14746                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14747                 for (y = 0; y < BOARD_HEIGHT; y++) {
14748                     if (gameMode == IcsExamining) {
14749                         if (boards[currentMove][y][x] != EmptySquare) {
14750                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14751                                     AAA + x, ONE + y);
14752                             SendToICS(buf);
14753                         }
14754                     } else {
14755                         if(boards[0][y][x] != p) nonEmpty++;
14756                         boards[0][y][x] = p;
14757                     }
14758                 }
14759                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14760             }
14761             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14762                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14763                     ChessSquare p = menuBoard[0][x];
14764                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14765                     p = menuBoard[BOARD_HEIGHT-1][x];
14766                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14767                 }
14768                 DisplayMessage("Clicking clock again restores position", "");
14769                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14770                 if(!nonEmpty) { // asked to clear an empty board
14771                     CopyBoard(boards[0], menuBoard);
14772                 } else
14773                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14774                     CopyBoard(boards[0], initialPosition);
14775                 } else
14776                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14777                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14778                     CopyBoard(boards[0], erasedBoard);
14779                 } else
14780                     CopyBoard(erasedBoard, currentBoard);
14781
14782             }
14783         }
14784         if (gameMode == EditPosition) {
14785             DrawPosition(FALSE, boards[0]);
14786         }
14787         break;
14788
14789       case WhitePlay:
14790         SetWhiteToPlayEvent();
14791         break;
14792
14793       case BlackPlay:
14794         SetBlackToPlayEvent();
14795         break;
14796
14797       case EmptySquare:
14798         if (gameMode == IcsExamining) {
14799             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14800             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14801             SendToICS(buf);
14802         } else {
14803             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14804                 if(x == BOARD_LEFT-2) {
14805                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14806                     boards[0][y][1] = 0;
14807                 } else
14808                 if(x == BOARD_RGHT+1) {
14809                     if(y >= gameInfo.holdingsSize) break;
14810                     boards[0][y][BOARD_WIDTH-2] = 0;
14811                 } else break;
14812             }
14813             boards[0][y][x] = EmptySquare;
14814             DrawPosition(FALSE, boards[0]);
14815         }
14816         break;
14817
14818       case PromotePiece:
14819         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14820            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14821             selection = (ChessSquare) (PROMOTED piece);
14822         } else if(piece == EmptySquare) selection = WhiteSilver;
14823         else selection = (ChessSquare)((int)piece - 1);
14824         goto defaultlabel;
14825
14826       case DemotePiece:
14827         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14828            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14829             selection = (ChessSquare) (DEMOTED piece);
14830         } else if(piece == EmptySquare) selection = BlackSilver;
14831         else selection = (ChessSquare)((int)piece + 1);
14832         goto defaultlabel;
14833
14834       case WhiteQueen:
14835       case BlackQueen:
14836         if(gameInfo.variant == VariantShatranj ||
14837            gameInfo.variant == VariantXiangqi  ||
14838            gameInfo.variant == VariantCourier  ||
14839            gameInfo.variant == VariantASEAN    ||
14840            gameInfo.variant == VariantMakruk     )
14841             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14842         goto defaultlabel;
14843
14844       case WhiteKing:
14845       case BlackKing:
14846         if(gameInfo.variant == VariantXiangqi)
14847             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14848         if(gameInfo.variant == VariantKnightmate)
14849             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14850       default:
14851         defaultlabel:
14852         if (gameMode == IcsExamining) {
14853             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14854             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14855                      PieceToChar(selection), AAA + x, ONE + y);
14856             SendToICS(buf);
14857         } else {
14858             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14859                 int n;
14860                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14861                     n = PieceToNumber(selection - BlackPawn);
14862                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14863                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14864                     boards[0][BOARD_HEIGHT-1-n][1]++;
14865                 } else
14866                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14867                     n = PieceToNumber(selection);
14868                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14869                     boards[0][n][BOARD_WIDTH-1] = selection;
14870                     boards[0][n][BOARD_WIDTH-2]++;
14871                 }
14872             } else
14873             boards[0][y][x] = selection;
14874             DrawPosition(TRUE, boards[0]);
14875             ClearHighlights();
14876             fromX = fromY = -1;
14877         }
14878         break;
14879     }
14880 }
14881
14882
14883 void
14884 DropMenuEvent (ChessSquare selection, int x, int y)
14885 {
14886     ChessMove moveType;
14887
14888     switch (gameMode) {
14889       case IcsPlayingWhite:
14890       case MachinePlaysBlack:
14891         if (!WhiteOnMove(currentMove)) {
14892             DisplayMoveError(_("It is Black's turn"));
14893             return;
14894         }
14895         moveType = WhiteDrop;
14896         break;
14897       case IcsPlayingBlack:
14898       case MachinePlaysWhite:
14899         if (WhiteOnMove(currentMove)) {
14900             DisplayMoveError(_("It is White's turn"));
14901             return;
14902         }
14903         moveType = BlackDrop;
14904         break;
14905       case EditGame:
14906         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14907         break;
14908       default:
14909         return;
14910     }
14911
14912     if (moveType == BlackDrop && selection < BlackPawn) {
14913       selection = (ChessSquare) ((int) selection
14914                                  + (int) BlackPawn - (int) WhitePawn);
14915     }
14916     if (boards[currentMove][y][x] != EmptySquare) {
14917         DisplayMoveError(_("That square is occupied"));
14918         return;
14919     }
14920
14921     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14922 }
14923
14924 void
14925 AcceptEvent ()
14926 {
14927     /* Accept a pending offer of any kind from opponent */
14928
14929     if (appData.icsActive) {
14930         SendToICS(ics_prefix);
14931         SendToICS("accept\n");
14932     } else if (cmailMsgLoaded) {
14933         if (currentMove == cmailOldMove &&
14934             commentList[cmailOldMove] != NULL &&
14935             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14936                    "Black offers a draw" : "White offers a draw")) {
14937             TruncateGame();
14938             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14939             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14940         } else {
14941             DisplayError(_("There is no pending offer on this move"), 0);
14942             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14943         }
14944     } else {
14945         /* Not used for offers from chess program */
14946     }
14947 }
14948
14949 void
14950 DeclineEvent ()
14951 {
14952     /* Decline a pending offer of any kind from opponent */
14953
14954     if (appData.icsActive) {
14955         SendToICS(ics_prefix);
14956         SendToICS("decline\n");
14957     } else if (cmailMsgLoaded) {
14958         if (currentMove == cmailOldMove &&
14959             commentList[cmailOldMove] != NULL &&
14960             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14961                    "Black offers a draw" : "White offers a draw")) {
14962 #ifdef NOTDEF
14963             AppendComment(cmailOldMove, "Draw declined", TRUE);
14964             DisplayComment(cmailOldMove - 1, "Draw declined");
14965 #endif /*NOTDEF*/
14966         } else {
14967             DisplayError(_("There is no pending offer on this move"), 0);
14968         }
14969     } else {
14970         /* Not used for offers from chess program */
14971     }
14972 }
14973
14974 void
14975 RematchEvent ()
14976 {
14977     /* Issue ICS rematch command */
14978     if (appData.icsActive) {
14979         SendToICS(ics_prefix);
14980         SendToICS("rematch\n");
14981     }
14982 }
14983
14984 void
14985 CallFlagEvent ()
14986 {
14987     /* Call your opponent's flag (claim a win on time) */
14988     if (appData.icsActive) {
14989         SendToICS(ics_prefix);
14990         SendToICS("flag\n");
14991     } else {
14992         switch (gameMode) {
14993           default:
14994             return;
14995           case MachinePlaysWhite:
14996             if (whiteFlag) {
14997                 if (blackFlag)
14998                   GameEnds(GameIsDrawn, "Both players ran out of time",
14999                            GE_PLAYER);
15000                 else
15001                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15002             } else {
15003                 DisplayError(_("Your opponent is not out of time"), 0);
15004             }
15005             break;
15006           case MachinePlaysBlack:
15007             if (blackFlag) {
15008                 if (whiteFlag)
15009                   GameEnds(GameIsDrawn, "Both players ran out of time",
15010                            GE_PLAYER);
15011                 else
15012                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15013             } else {
15014                 DisplayError(_("Your opponent is not out of time"), 0);
15015             }
15016             break;
15017         }
15018     }
15019 }
15020
15021 void
15022 ClockClick (int which)
15023 {       // [HGM] code moved to back-end from winboard.c
15024         if(which) { // black clock
15025           if (gameMode == EditPosition || gameMode == IcsExamining) {
15026             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15027             SetBlackToPlayEvent();
15028           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15029           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15030           } else if (shiftKey) {
15031             AdjustClock(which, -1);
15032           } else if (gameMode == IcsPlayingWhite ||
15033                      gameMode == MachinePlaysBlack) {
15034             CallFlagEvent();
15035           }
15036         } else { // white clock
15037           if (gameMode == EditPosition || gameMode == IcsExamining) {
15038             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15039             SetWhiteToPlayEvent();
15040           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15041           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15042           } else if (shiftKey) {
15043             AdjustClock(which, -1);
15044           } else if (gameMode == IcsPlayingBlack ||
15045                    gameMode == MachinePlaysWhite) {
15046             CallFlagEvent();
15047           }
15048         }
15049 }
15050
15051 void
15052 DrawEvent ()
15053 {
15054     /* Offer draw or accept pending draw offer from opponent */
15055
15056     if (appData.icsActive) {
15057         /* Note: tournament rules require draw offers to be
15058            made after you make your move but before you punch
15059            your clock.  Currently ICS doesn't let you do that;
15060            instead, you immediately punch your clock after making
15061            a move, but you can offer a draw at any time. */
15062
15063         SendToICS(ics_prefix);
15064         SendToICS("draw\n");
15065         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15066     } else if (cmailMsgLoaded) {
15067         if (currentMove == cmailOldMove &&
15068             commentList[cmailOldMove] != NULL &&
15069             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15070                    "Black offers a draw" : "White offers a draw")) {
15071             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15072             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15073         } else if (currentMove == cmailOldMove + 1) {
15074             char *offer = WhiteOnMove(cmailOldMove) ?
15075               "White offers a draw" : "Black offers a draw";
15076             AppendComment(currentMove, offer, TRUE);
15077             DisplayComment(currentMove - 1, offer);
15078             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15079         } else {
15080             DisplayError(_("You must make your move before offering a draw"), 0);
15081             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15082         }
15083     } else if (first.offeredDraw) {
15084         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15085     } else {
15086         if (first.sendDrawOffers) {
15087             SendToProgram("draw\n", &first);
15088             userOfferedDraw = TRUE;
15089         }
15090     }
15091 }
15092
15093 void
15094 AdjournEvent ()
15095 {
15096     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15097
15098     if (appData.icsActive) {
15099         SendToICS(ics_prefix);
15100         SendToICS("adjourn\n");
15101     } else {
15102         /* Currently GNU Chess doesn't offer or accept Adjourns */
15103     }
15104 }
15105
15106
15107 void
15108 AbortEvent ()
15109 {
15110     /* Offer Abort or accept pending Abort offer from opponent */
15111
15112     if (appData.icsActive) {
15113         SendToICS(ics_prefix);
15114         SendToICS("abort\n");
15115     } else {
15116         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15117     }
15118 }
15119
15120 void
15121 ResignEvent ()
15122 {
15123     /* Resign.  You can do this even if it's not your turn. */
15124
15125     if (appData.icsActive) {
15126         SendToICS(ics_prefix);
15127         SendToICS("resign\n");
15128     } else {
15129         switch (gameMode) {
15130           case MachinePlaysWhite:
15131             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15132             break;
15133           case MachinePlaysBlack:
15134             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15135             break;
15136           case EditGame:
15137             if (cmailMsgLoaded) {
15138                 TruncateGame();
15139                 if (WhiteOnMove(cmailOldMove)) {
15140                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15141                 } else {
15142                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15143                 }
15144                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15145             }
15146             break;
15147           default:
15148             break;
15149         }
15150     }
15151 }
15152
15153
15154 void
15155 StopObservingEvent ()
15156 {
15157     /* Stop observing current games */
15158     SendToICS(ics_prefix);
15159     SendToICS("unobserve\n");
15160 }
15161
15162 void
15163 StopExaminingEvent ()
15164 {
15165     /* Stop observing current game */
15166     SendToICS(ics_prefix);
15167     SendToICS("unexamine\n");
15168 }
15169
15170 void
15171 ForwardInner (int target)
15172 {
15173     int limit; int oldSeekGraphUp = seekGraphUp;
15174
15175     if (appData.debugMode)
15176         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15177                 target, currentMove, forwardMostMove);
15178
15179     if (gameMode == EditPosition)
15180       return;
15181
15182     seekGraphUp = FALSE;
15183     MarkTargetSquares(1);
15184
15185     if (gameMode == PlayFromGameFile && !pausing)
15186       PauseEvent();
15187
15188     if (gameMode == IcsExamining && pausing)
15189       limit = pauseExamForwardMostMove;
15190     else
15191       limit = forwardMostMove;
15192
15193     if (target > limit) target = limit;
15194
15195     if (target > 0 && moveList[target - 1][0]) {
15196         int fromX, fromY, toX, toY;
15197         toX = moveList[target - 1][2] - AAA;
15198         toY = moveList[target - 1][3] - ONE;
15199         if (moveList[target - 1][1] == '@') {
15200             if (appData.highlightLastMove) {
15201                 SetHighlights(-1, -1, toX, toY);
15202             }
15203         } else {
15204             fromX = moveList[target - 1][0] - AAA;
15205             fromY = moveList[target - 1][1] - ONE;
15206             if (target == currentMove + 1) {
15207                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15208             }
15209             if (appData.highlightLastMove) {
15210                 SetHighlights(fromX, fromY, toX, toY);
15211             }
15212         }
15213     }
15214     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15215         gameMode == Training || gameMode == PlayFromGameFile ||
15216         gameMode == AnalyzeFile) {
15217         while (currentMove < target) {
15218             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15219             SendMoveToProgram(currentMove++, &first);
15220         }
15221     } else {
15222         currentMove = target;
15223     }
15224
15225     if (gameMode == EditGame || gameMode == EndOfGame) {
15226         whiteTimeRemaining = timeRemaining[0][currentMove];
15227         blackTimeRemaining = timeRemaining[1][currentMove];
15228     }
15229     DisplayBothClocks();
15230     DisplayMove(currentMove - 1);
15231     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15232     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15233     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15234         DisplayComment(currentMove - 1, commentList[currentMove]);
15235     }
15236     ClearMap(); // [HGM] exclude: invalidate map
15237 }
15238
15239
15240 void
15241 ForwardEvent ()
15242 {
15243     if (gameMode == IcsExamining && !pausing) {
15244         SendToICS(ics_prefix);
15245         SendToICS("forward\n");
15246     } else {
15247         ForwardInner(currentMove + 1);
15248     }
15249 }
15250
15251 void
15252 ToEndEvent ()
15253 {
15254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15255         /* to optimze, we temporarily turn off analysis mode while we feed
15256          * the remaining moves to the engine. Otherwise we get analysis output
15257          * after each move.
15258          */
15259         if (first.analysisSupport) {
15260           SendToProgram("exit\nforce\n", &first);
15261           first.analyzing = FALSE;
15262         }
15263     }
15264
15265     if (gameMode == IcsExamining && !pausing) {
15266         SendToICS(ics_prefix);
15267         SendToICS("forward 999999\n");
15268     } else {
15269         ForwardInner(forwardMostMove);
15270     }
15271
15272     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15273         /* we have fed all the moves, so reactivate analysis mode */
15274         SendToProgram("analyze\n", &first);
15275         first.analyzing = TRUE;
15276         /*first.maybeThinking = TRUE;*/
15277         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15278     }
15279 }
15280
15281 void
15282 BackwardInner (int target)
15283 {
15284     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15285
15286     if (appData.debugMode)
15287         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15288                 target, currentMove, forwardMostMove);
15289
15290     if (gameMode == EditPosition) return;
15291     seekGraphUp = FALSE;
15292     MarkTargetSquares(1);
15293     if (currentMove <= backwardMostMove) {
15294         ClearHighlights();
15295         DrawPosition(full_redraw, boards[currentMove]);
15296         return;
15297     }
15298     if (gameMode == PlayFromGameFile && !pausing)
15299       PauseEvent();
15300
15301     if (moveList[target][0]) {
15302         int fromX, fromY, toX, toY;
15303         toX = moveList[target][2] - AAA;
15304         toY = moveList[target][3] - ONE;
15305         if (moveList[target][1] == '@') {
15306             if (appData.highlightLastMove) {
15307                 SetHighlights(-1, -1, toX, toY);
15308             }
15309         } else {
15310             fromX = moveList[target][0] - AAA;
15311             fromY = moveList[target][1] - ONE;
15312             if (target == currentMove - 1) {
15313                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15314             }
15315             if (appData.highlightLastMove) {
15316                 SetHighlights(fromX, fromY, toX, toY);
15317             }
15318         }
15319     }
15320     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15321         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15322         while (currentMove > target) {
15323             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15324                 // null move cannot be undone. Reload program with move history before it.
15325                 int i;
15326                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15327                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15328                 }
15329                 SendBoard(&first, i);
15330               if(second.analyzing) SendBoard(&second, i);
15331                 for(currentMove=i; currentMove<target; currentMove++) {
15332                     SendMoveToProgram(currentMove, &first);
15333                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15334                 }
15335                 break;
15336             }
15337             SendToBoth("undo\n");
15338             currentMove--;
15339         }
15340     } else {
15341         currentMove = target;
15342     }
15343
15344     if (gameMode == EditGame || gameMode == EndOfGame) {
15345         whiteTimeRemaining = timeRemaining[0][currentMove];
15346         blackTimeRemaining = timeRemaining[1][currentMove];
15347     }
15348     DisplayBothClocks();
15349     DisplayMove(currentMove - 1);
15350     DrawPosition(full_redraw, boards[currentMove]);
15351     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15352     // [HGM] PV info: routine tests if comment empty
15353     DisplayComment(currentMove - 1, commentList[currentMove]);
15354     ClearMap(); // [HGM] exclude: invalidate map
15355 }
15356
15357 void
15358 BackwardEvent ()
15359 {
15360     if (gameMode == IcsExamining && !pausing) {
15361         SendToICS(ics_prefix);
15362         SendToICS("backward\n");
15363     } else {
15364         BackwardInner(currentMove - 1);
15365     }
15366 }
15367
15368 void
15369 ToStartEvent ()
15370 {
15371     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15372         /* to optimize, we temporarily turn off analysis mode while we undo
15373          * all the moves. Otherwise we get analysis output after each undo.
15374          */
15375         if (first.analysisSupport) {
15376           SendToProgram("exit\nforce\n", &first);
15377           first.analyzing = FALSE;
15378         }
15379     }
15380
15381     if (gameMode == IcsExamining && !pausing) {
15382         SendToICS(ics_prefix);
15383         SendToICS("backward 999999\n");
15384     } else {
15385         BackwardInner(backwardMostMove);
15386     }
15387
15388     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15389         /* we have fed all the moves, so reactivate analysis mode */
15390         SendToProgram("analyze\n", &first);
15391         first.analyzing = TRUE;
15392         /*first.maybeThinking = TRUE;*/
15393         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15394     }
15395 }
15396
15397 void
15398 ToNrEvent (int to)
15399 {
15400   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15401   if (to >= forwardMostMove) to = forwardMostMove;
15402   if (to <= backwardMostMove) to = backwardMostMove;
15403   if (to < currentMove) {
15404     BackwardInner(to);
15405   } else {
15406     ForwardInner(to);
15407   }
15408 }
15409
15410 void
15411 RevertEvent (Boolean annotate)
15412 {
15413     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15414         return;
15415     }
15416     if (gameMode != IcsExamining) {
15417         DisplayError(_("You are not examining a game"), 0);
15418         return;
15419     }
15420     if (pausing) {
15421         DisplayError(_("You can't revert while pausing"), 0);
15422         return;
15423     }
15424     SendToICS(ics_prefix);
15425     SendToICS("revert\n");
15426 }
15427
15428 void
15429 RetractMoveEvent ()
15430 {
15431     switch (gameMode) {
15432       case MachinePlaysWhite:
15433       case MachinePlaysBlack:
15434         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15435             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15436             return;
15437         }
15438         if (forwardMostMove < 2) return;
15439         currentMove = forwardMostMove = forwardMostMove - 2;
15440         whiteTimeRemaining = timeRemaining[0][currentMove];
15441         blackTimeRemaining = timeRemaining[1][currentMove];
15442         DisplayBothClocks();
15443         DisplayMove(currentMove - 1);
15444         ClearHighlights();/*!! could figure this out*/
15445         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15446         SendToProgram("remove\n", &first);
15447         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15448         break;
15449
15450       case BeginningOfGame:
15451       default:
15452         break;
15453
15454       case IcsPlayingWhite:
15455       case IcsPlayingBlack:
15456         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15457             SendToICS(ics_prefix);
15458             SendToICS("takeback 2\n");
15459         } else {
15460             SendToICS(ics_prefix);
15461             SendToICS("takeback 1\n");
15462         }
15463         break;
15464     }
15465 }
15466
15467 void
15468 MoveNowEvent ()
15469 {
15470     ChessProgramState *cps;
15471
15472     switch (gameMode) {
15473       case MachinePlaysWhite:
15474         if (!WhiteOnMove(forwardMostMove)) {
15475             DisplayError(_("It is your turn"), 0);
15476             return;
15477         }
15478         cps = &first;
15479         break;
15480       case MachinePlaysBlack:
15481         if (WhiteOnMove(forwardMostMove)) {
15482             DisplayError(_("It is your turn"), 0);
15483             return;
15484         }
15485         cps = &first;
15486         break;
15487       case TwoMachinesPlay:
15488         if (WhiteOnMove(forwardMostMove) ==
15489             (first.twoMachinesColor[0] == 'w')) {
15490             cps = &first;
15491         } else {
15492             cps = &second;
15493         }
15494         break;
15495       case BeginningOfGame:
15496       default:
15497         return;
15498     }
15499     SendToProgram("?\n", cps);
15500 }
15501
15502 void
15503 TruncateGameEvent ()
15504 {
15505     EditGameEvent();
15506     if (gameMode != EditGame) return;
15507     TruncateGame();
15508 }
15509
15510 void
15511 TruncateGame ()
15512 {
15513     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15514     if (forwardMostMove > currentMove) {
15515         if (gameInfo.resultDetails != NULL) {
15516             free(gameInfo.resultDetails);
15517             gameInfo.resultDetails = NULL;
15518             gameInfo.result = GameUnfinished;
15519         }
15520         forwardMostMove = currentMove;
15521         HistorySet(parseList, backwardMostMove, forwardMostMove,
15522                    currentMove-1);
15523     }
15524 }
15525
15526 void
15527 HintEvent ()
15528 {
15529     if (appData.noChessProgram) return;
15530     switch (gameMode) {
15531       case MachinePlaysWhite:
15532         if (WhiteOnMove(forwardMostMove)) {
15533             DisplayError(_("Wait until your turn."), 0);
15534             return;
15535         }
15536         break;
15537       case BeginningOfGame:
15538       case MachinePlaysBlack:
15539         if (!WhiteOnMove(forwardMostMove)) {
15540             DisplayError(_("Wait until your turn."), 0);
15541             return;
15542         }
15543         break;
15544       default:
15545         DisplayError(_("No hint available"), 0);
15546         return;
15547     }
15548     SendToProgram("hint\n", &first);
15549     hintRequested = TRUE;
15550 }
15551
15552 void
15553 CreateBookEvent ()
15554 {
15555     ListGame * lg = (ListGame *) gameList.head;
15556     FILE *f, *g;
15557     int nItem;
15558     static int secondTime = FALSE;
15559
15560     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15561         DisplayError(_("Game list not loaded or empty"), 0);
15562         return;
15563     }
15564
15565     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15566         fclose(g);
15567         secondTime++;
15568         DisplayNote(_("Book file exists! Try again for overwrite."));
15569         return;
15570     }
15571
15572     creatingBook = TRUE;
15573     secondTime = FALSE;
15574
15575     /* Get list size */
15576     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15577         LoadGame(f, nItem, "", TRUE);
15578         AddGameToBook(TRUE);
15579         lg = (ListGame *) lg->node.succ;
15580     }
15581
15582     creatingBook = FALSE;
15583     FlushBook();
15584 }
15585
15586 void
15587 BookEvent ()
15588 {
15589     if (appData.noChessProgram) return;
15590     switch (gameMode) {
15591       case MachinePlaysWhite:
15592         if (WhiteOnMove(forwardMostMove)) {
15593             DisplayError(_("Wait until your turn."), 0);
15594             return;
15595         }
15596         break;
15597       case BeginningOfGame:
15598       case MachinePlaysBlack:
15599         if (!WhiteOnMove(forwardMostMove)) {
15600             DisplayError(_("Wait until your turn."), 0);
15601             return;
15602         }
15603         break;
15604       case EditPosition:
15605         EditPositionDone(TRUE);
15606         break;
15607       case TwoMachinesPlay:
15608         return;
15609       default:
15610         break;
15611     }
15612     SendToProgram("bk\n", &first);
15613     bookOutput[0] = NULLCHAR;
15614     bookRequested = TRUE;
15615 }
15616
15617 void
15618 AboutGameEvent ()
15619 {
15620     char *tags = PGNTags(&gameInfo);
15621     TagsPopUp(tags, CmailMsg());
15622     free(tags);
15623 }
15624
15625 /* end button procedures */
15626
15627 void
15628 PrintPosition (FILE *fp, int move)
15629 {
15630     int i, j;
15631
15632     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15633         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15634             char c = PieceToChar(boards[move][i][j]);
15635             fputc(c == 'x' ? '.' : c, fp);
15636             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15637         }
15638     }
15639     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15640       fprintf(fp, "white to play\n");
15641     else
15642       fprintf(fp, "black to play\n");
15643 }
15644
15645 void
15646 PrintOpponents (FILE *fp)
15647 {
15648     if (gameInfo.white != NULL) {
15649         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15650     } else {
15651         fprintf(fp, "\n");
15652     }
15653 }
15654
15655 /* Find last component of program's own name, using some heuristics */
15656 void
15657 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15658 {
15659     char *p, *q, c;
15660     int local = (strcmp(host, "localhost") == 0);
15661     while (!local && (p = strchr(prog, ';')) != NULL) {
15662         p++;
15663         while (*p == ' ') p++;
15664         prog = p;
15665     }
15666     if (*prog == '"' || *prog == '\'') {
15667         q = strchr(prog + 1, *prog);
15668     } else {
15669         q = strchr(prog, ' ');
15670     }
15671     if (q == NULL) q = prog + strlen(prog);
15672     p = q;
15673     while (p >= prog && *p != '/' && *p != '\\') p--;
15674     p++;
15675     if(p == prog && *p == '"') p++;
15676     c = *q; *q = 0;
15677     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15678     memcpy(buf, p, q - p);
15679     buf[q - p] = NULLCHAR;
15680     if (!local) {
15681         strcat(buf, "@");
15682         strcat(buf, host);
15683     }
15684 }
15685
15686 char *
15687 TimeControlTagValue ()
15688 {
15689     char buf[MSG_SIZ];
15690     if (!appData.clockMode) {
15691       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15692     } else if (movesPerSession > 0) {
15693       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15694     } else if (timeIncrement == 0) {
15695       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15696     } else {
15697       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15698     }
15699     return StrSave(buf);
15700 }
15701
15702 void
15703 SetGameInfo ()
15704 {
15705     /* This routine is used only for certain modes */
15706     VariantClass v = gameInfo.variant;
15707     ChessMove r = GameUnfinished;
15708     char *p = NULL;
15709
15710     if(keepInfo) return;
15711
15712     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15713         r = gameInfo.result;
15714         p = gameInfo.resultDetails;
15715         gameInfo.resultDetails = NULL;
15716     }
15717     ClearGameInfo(&gameInfo);
15718     gameInfo.variant = v;
15719
15720     switch (gameMode) {
15721       case MachinePlaysWhite:
15722         gameInfo.event = StrSave( appData.pgnEventHeader );
15723         gameInfo.site = StrSave(HostName());
15724         gameInfo.date = PGNDate();
15725         gameInfo.round = StrSave("-");
15726         gameInfo.white = StrSave(first.tidy);
15727         gameInfo.black = StrSave(UserName());
15728         gameInfo.timeControl = TimeControlTagValue();
15729         break;
15730
15731       case MachinePlaysBlack:
15732         gameInfo.event = StrSave( appData.pgnEventHeader );
15733         gameInfo.site = StrSave(HostName());
15734         gameInfo.date = PGNDate();
15735         gameInfo.round = StrSave("-");
15736         gameInfo.white = StrSave(UserName());
15737         gameInfo.black = StrSave(first.tidy);
15738         gameInfo.timeControl = TimeControlTagValue();
15739         break;
15740
15741       case TwoMachinesPlay:
15742         gameInfo.event = StrSave( appData.pgnEventHeader );
15743         gameInfo.site = StrSave(HostName());
15744         gameInfo.date = PGNDate();
15745         if (roundNr > 0) {
15746             char buf[MSG_SIZ];
15747             snprintf(buf, MSG_SIZ, "%d", roundNr);
15748             gameInfo.round = StrSave(buf);
15749         } else {
15750             gameInfo.round = StrSave("-");
15751         }
15752         if (first.twoMachinesColor[0] == 'w') {
15753             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15754             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15755         } else {
15756             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15757             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15758         }
15759         gameInfo.timeControl = TimeControlTagValue();
15760         break;
15761
15762       case EditGame:
15763         gameInfo.event = StrSave("Edited game");
15764         gameInfo.site = StrSave(HostName());
15765         gameInfo.date = PGNDate();
15766         gameInfo.round = StrSave("-");
15767         gameInfo.white = StrSave("-");
15768         gameInfo.black = StrSave("-");
15769         gameInfo.result = r;
15770         gameInfo.resultDetails = p;
15771         break;
15772
15773       case EditPosition:
15774         gameInfo.event = StrSave("Edited position");
15775         gameInfo.site = StrSave(HostName());
15776         gameInfo.date = PGNDate();
15777         gameInfo.round = StrSave("-");
15778         gameInfo.white = StrSave("-");
15779         gameInfo.black = StrSave("-");
15780         break;
15781
15782       case IcsPlayingWhite:
15783       case IcsPlayingBlack:
15784       case IcsObserving:
15785       case IcsExamining:
15786         break;
15787
15788       case PlayFromGameFile:
15789         gameInfo.event = StrSave("Game from non-PGN file");
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       default:
15798         break;
15799     }
15800 }
15801
15802 void
15803 ReplaceComment (int index, char *text)
15804 {
15805     int len;
15806     char *p;
15807     float score;
15808
15809     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15810        pvInfoList[index-1].depth == len &&
15811        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15812        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15813     while (*text == '\n') text++;
15814     len = strlen(text);
15815     while (len > 0 && text[len - 1] == '\n') len--;
15816
15817     if (commentList[index] != NULL)
15818       free(commentList[index]);
15819
15820     if (len == 0) {
15821         commentList[index] = NULL;
15822         return;
15823     }
15824   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15825       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15826       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15827     commentList[index] = (char *) malloc(len + 2);
15828     strncpy(commentList[index], text, len);
15829     commentList[index][len] = '\n';
15830     commentList[index][len + 1] = NULLCHAR;
15831   } else {
15832     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15833     char *p;
15834     commentList[index] = (char *) malloc(len + 7);
15835     safeStrCpy(commentList[index], "{\n", 3);
15836     safeStrCpy(commentList[index]+2, text, len+1);
15837     commentList[index][len+2] = NULLCHAR;
15838     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15839     strcat(commentList[index], "\n}\n");
15840   }
15841 }
15842
15843 void
15844 CrushCRs (char *text)
15845 {
15846   char *p = text;
15847   char *q = text;
15848   char ch;
15849
15850   do {
15851     ch = *p++;
15852     if (ch == '\r') continue;
15853     *q++ = ch;
15854   } while (ch != '\0');
15855 }
15856
15857 void
15858 AppendComment (int index, char *text, Boolean addBraces)
15859 /* addBraces  tells if we should add {} */
15860 {
15861     int oldlen, len;
15862     char *old;
15863
15864 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15865     if(addBraces == 3) addBraces = 0; else // force appending literally
15866     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15867
15868     CrushCRs(text);
15869     while (*text == '\n') text++;
15870     len = strlen(text);
15871     while (len > 0 && text[len - 1] == '\n') len--;
15872     text[len] = NULLCHAR;
15873
15874     if (len == 0) return;
15875
15876     if (commentList[index] != NULL) {
15877       Boolean addClosingBrace = addBraces;
15878         old = commentList[index];
15879         oldlen = strlen(old);
15880         while(commentList[index][oldlen-1] ==  '\n')
15881           commentList[index][--oldlen] = NULLCHAR;
15882         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15883         safeStrCpy(commentList[index], old, oldlen + len + 6);
15884         free(old);
15885         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15886         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15887           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15888           while (*text == '\n') { text++; len--; }
15889           commentList[index][--oldlen] = NULLCHAR;
15890       }
15891         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15892         else          strcat(commentList[index], "\n");
15893         strcat(commentList[index], text);
15894         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15895         else          strcat(commentList[index], "\n");
15896     } else {
15897         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15898         if(addBraces)
15899           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15900         else commentList[index][0] = NULLCHAR;
15901         strcat(commentList[index], text);
15902         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15903         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15904     }
15905 }
15906
15907 static char *
15908 FindStr (char * text, char * sub_text)
15909 {
15910     char * result = strstr( text, sub_text );
15911
15912     if( result != NULL ) {
15913         result += strlen( sub_text );
15914     }
15915
15916     return result;
15917 }
15918
15919 /* [AS] Try to extract PV info from PGN comment */
15920 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15921 char *
15922 GetInfoFromComment (int index, char * text)
15923 {
15924     char * sep = text, *p;
15925
15926     if( text != NULL && index > 0 ) {
15927         int score = 0;
15928         int depth = 0;
15929         int time = -1, sec = 0, deci;
15930         char * s_eval = FindStr( text, "[%eval " );
15931         char * s_emt = FindStr( text, "[%emt " );
15932 #if 0
15933         if( s_eval != NULL || s_emt != NULL ) {
15934 #else
15935         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15936 #endif
15937             /* New style */
15938             char delim;
15939
15940             if( s_eval != NULL ) {
15941                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15942                     return text;
15943                 }
15944
15945                 if( delim != ']' ) {
15946                     return text;
15947                 }
15948             }
15949
15950             if( s_emt != NULL ) {
15951             }
15952                 return text;
15953         }
15954         else {
15955             /* We expect something like: [+|-]nnn.nn/dd */
15956             int score_lo = 0;
15957
15958             if(*text != '{') return text; // [HGM] braces: must be normal comment
15959
15960             sep = strchr( text, '/' );
15961             if( sep == NULL || sep < (text+4) ) {
15962                 return text;
15963             }
15964
15965             p = text;
15966             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15967             if(p[1] == '(') { // comment starts with PV
15968                p = strchr(p, ')'); // locate end of PV
15969                if(p == NULL || sep < p+5) return text;
15970                // at this point we have something like "{(.*) +0.23/6 ..."
15971                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15972                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15973                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15974             }
15975             time = -1; sec = -1; deci = -1;
15976             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15977                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15978                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15979                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15980                 return text;
15981             }
15982
15983             if( score_lo < 0 || score_lo >= 100 ) {
15984                 return text;
15985             }
15986
15987             if(sec >= 0) time = 600*time + 10*sec; else
15988             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15989
15990             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15991
15992             /* [HGM] PV time: now locate end of PV info */
15993             while( *++sep >= '0' && *sep <= '9'); // strip depth
15994             if(time >= 0)
15995             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15996             if(sec >= 0)
15997             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15998             if(deci >= 0)
15999             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16000             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16001         }
16002
16003         if( depth <= 0 ) {
16004             return text;
16005         }
16006
16007         if( time < 0 ) {
16008             time = -1;
16009         }
16010
16011         pvInfoList[index-1].depth = depth;
16012         pvInfoList[index-1].score = score;
16013         pvInfoList[index-1].time  = 10*time; // centi-sec
16014         if(*sep == '}') *sep = 0; else *--sep = '{';
16015         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16016     }
16017     return sep;
16018 }
16019
16020 void
16021 SendToProgram (char *message, ChessProgramState *cps)
16022 {
16023     int count, outCount, error;
16024     char buf[MSG_SIZ];
16025
16026     if (cps->pr == NoProc) return;
16027     Attention(cps);
16028
16029     if (appData.debugMode) {
16030         TimeMark now;
16031         GetTimeMark(&now);
16032         fprintf(debugFP, "%ld >%-6s: %s",
16033                 SubtractTimeMarks(&now, &programStartTime),
16034                 cps->which, message);
16035         if(serverFP)
16036             fprintf(serverFP, "%ld >%-6s: %s",
16037                 SubtractTimeMarks(&now, &programStartTime),
16038                 cps->which, message), fflush(serverFP);
16039     }
16040
16041     count = strlen(message);
16042     outCount = OutputToProcess(cps->pr, message, count, &error);
16043     if (outCount < count && !exiting
16044                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16045       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16046       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16047         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16048             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16049                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16050                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16051                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16052             } else {
16053                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16054                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16055                 gameInfo.result = res;
16056             }
16057             gameInfo.resultDetails = StrSave(buf);
16058         }
16059         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16060         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16061     }
16062 }
16063
16064 void
16065 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16066 {
16067     char *end_str;
16068     char buf[MSG_SIZ];
16069     ChessProgramState *cps = (ChessProgramState *)closure;
16070
16071     if (isr != cps->isr) return; /* Killed intentionally */
16072     if (count <= 0) {
16073         if (count == 0) {
16074             RemoveInputSource(cps->isr);
16075             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16076                     _(cps->which), cps->program);
16077             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16078             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16079                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16080                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16081                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16082                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16083                 } else {
16084                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16085                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16086                     gameInfo.result = res;
16087                 }
16088                 gameInfo.resultDetails = StrSave(buf);
16089             }
16090             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16091             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16092         } else {
16093             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16094                     _(cps->which), cps->program);
16095             RemoveInputSource(cps->isr);
16096
16097             /* [AS] Program is misbehaving badly... kill it */
16098             if( count == -2 ) {
16099                 DestroyChildProcess( cps->pr, 9 );
16100                 cps->pr = NoProc;
16101             }
16102
16103             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16104         }
16105         return;
16106     }
16107
16108     if ((end_str = strchr(message, '\r')) != NULL)
16109       *end_str = NULLCHAR;
16110     if ((end_str = strchr(message, '\n')) != NULL)
16111       *end_str = NULLCHAR;
16112
16113     if (appData.debugMode) {
16114         TimeMark now; int print = 1;
16115         char *quote = ""; char c; int i;
16116
16117         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16118                 char start = message[0];
16119                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16120                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16121                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16122                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16123                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16124                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16125                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16126                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16127                    sscanf(message, "hint: %c", &c)!=1 &&
16128                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16129                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16130                     print = (appData.engineComments >= 2);
16131                 }
16132                 message[0] = start; // restore original message
16133         }
16134         if(print) {
16135                 GetTimeMark(&now);
16136                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16137                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16138                         quote,
16139                         message);
16140                 if(serverFP)
16141                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16142                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16143                         quote,
16144                         message), fflush(serverFP);
16145         }
16146     }
16147
16148     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16149     if (appData.icsEngineAnalyze) {
16150         if (strstr(message, "whisper") != NULL ||
16151              strstr(message, "kibitz") != NULL ||
16152             strstr(message, "tellics") != NULL) return;
16153     }
16154
16155     HandleMachineMove(message, cps);
16156 }
16157
16158
16159 void
16160 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16161 {
16162     char buf[MSG_SIZ];
16163     int seconds;
16164
16165     if( timeControl_2 > 0 ) {
16166         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16167             tc = timeControl_2;
16168         }
16169     }
16170     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16171     inc /= cps->timeOdds;
16172     st  /= cps->timeOdds;
16173
16174     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16175
16176     if (st > 0) {
16177       /* Set exact time per move, normally using st command */
16178       if (cps->stKludge) {
16179         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16180         seconds = st % 60;
16181         if (seconds == 0) {
16182           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16183         } else {
16184           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16185         }
16186       } else {
16187         snprintf(buf, MSG_SIZ, "st %d\n", st);
16188       }
16189     } else {
16190       /* Set conventional or incremental time control, using level command */
16191       if (seconds == 0) {
16192         /* Note old gnuchess bug -- minutes:seconds used to not work.
16193            Fixed in later versions, but still avoid :seconds
16194            when seconds is 0. */
16195         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16196       } else {
16197         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16198                  seconds, inc/1000.);
16199       }
16200     }
16201     SendToProgram(buf, cps);
16202
16203     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16204     /* Orthogonally, limit search to given depth */
16205     if (sd > 0) {
16206       if (cps->sdKludge) {
16207         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16208       } else {
16209         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16210       }
16211       SendToProgram(buf, cps);
16212     }
16213
16214     if(cps->nps >= 0) { /* [HGM] nps */
16215         if(cps->supportsNPS == FALSE)
16216           cps->nps = -1; // don't use if engine explicitly says not supported!
16217         else {
16218           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16219           SendToProgram(buf, cps);
16220         }
16221     }
16222 }
16223
16224 ChessProgramState *
16225 WhitePlayer ()
16226 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16227 {
16228     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16229        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16230         return &second;
16231     return &first;
16232 }
16233
16234 void
16235 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16236 {
16237     char message[MSG_SIZ];
16238     long time, otime;
16239
16240     /* Note: this routine must be called when the clocks are stopped
16241        or when they have *just* been set or switched; otherwise
16242        it will be off by the time since the current tick started.
16243     */
16244     if (machineWhite) {
16245         time = whiteTimeRemaining / 10;
16246         otime = blackTimeRemaining / 10;
16247     } else {
16248         time = blackTimeRemaining / 10;
16249         otime = whiteTimeRemaining / 10;
16250     }
16251     /* [HGM] translate opponent's time by time-odds factor */
16252     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16253
16254     if (time <= 0) time = 1;
16255     if (otime <= 0) otime = 1;
16256
16257     snprintf(message, MSG_SIZ, "time %ld\n", time);
16258     SendToProgram(message, cps);
16259
16260     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16261     SendToProgram(message, cps);
16262 }
16263
16264 char *
16265 EngineDefinedVariant (ChessProgramState *cps, int n)
16266 {   // return name of n-th unknown variant that engine supports
16267     static char buf[MSG_SIZ];
16268     char *p, *s = cps->variants;
16269     if(!s) return NULL;
16270     do { // parse string from variants feature
16271       VariantClass v;
16272         p = strchr(s, ',');
16273         if(p) *p = NULLCHAR;
16274       v = StringToVariant(s);
16275       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16276         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16277             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16278         }
16279         if(p) *p++ = ',';
16280         if(n < 0) return buf;
16281     } while(s = p);
16282     return NULL;
16283 }
16284
16285 int
16286 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16287 {
16288   char buf[MSG_SIZ];
16289   int len = strlen(name);
16290   int val;
16291
16292   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16293     (*p) += len + 1;
16294     sscanf(*p, "%d", &val);
16295     *loc = (val != 0);
16296     while (**p && **p != ' ')
16297       (*p)++;
16298     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16299     SendToProgram(buf, cps);
16300     return TRUE;
16301   }
16302   return FALSE;
16303 }
16304
16305 int
16306 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16307 {
16308   char buf[MSG_SIZ];
16309   int len = strlen(name);
16310   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16311     (*p) += len + 1;
16312     sscanf(*p, "%d", loc);
16313     while (**p && **p != ' ') (*p)++;
16314     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16315     SendToProgram(buf, cps);
16316     return TRUE;
16317   }
16318   return FALSE;
16319 }
16320
16321 int
16322 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16323 {
16324   char buf[MSG_SIZ];
16325   int len = strlen(name);
16326   if (strncmp((*p), name, len) == 0
16327       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16328     (*p) += len + 2;
16329     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16330     sscanf(*p, "%[^\"]", *loc);
16331     while (**p && **p != '\"') (*p)++;
16332     if (**p == '\"') (*p)++;
16333     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16334     SendToProgram(buf, cps);
16335     return TRUE;
16336   }
16337   return FALSE;
16338 }
16339
16340 int
16341 ParseOption (Option *opt, ChessProgramState *cps)
16342 // [HGM] options: process the string that defines an engine option, and determine
16343 // name, type, default value, and allowed value range
16344 {
16345         char *p, *q, buf[MSG_SIZ];
16346         int n, min = (-1)<<31, max = 1<<31, def;
16347
16348         if(p = strstr(opt->name, " -spin ")) {
16349             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16350             if(max < min) max = min; // enforce consistency
16351             if(def < min) def = min;
16352             if(def > max) def = max;
16353             opt->value = def;
16354             opt->min = min;
16355             opt->max = max;
16356             opt->type = Spin;
16357         } else if((p = strstr(opt->name, " -slider "))) {
16358             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16359             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16360             if(max < min) max = min; // enforce consistency
16361             if(def < min) def = min;
16362             if(def > max) def = max;
16363             opt->value = def;
16364             opt->min = min;
16365             opt->max = max;
16366             opt->type = Spin; // Slider;
16367         } else if((p = strstr(opt->name, " -string "))) {
16368             opt->textValue = p+9;
16369             opt->type = TextBox;
16370         } else if((p = strstr(opt->name, " -file "))) {
16371             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16372             opt->textValue = p+7;
16373             opt->type = FileName; // FileName;
16374         } else if((p = strstr(opt->name, " -path "))) {
16375             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16376             opt->textValue = p+7;
16377             opt->type = PathName; // PathName;
16378         } else if(p = strstr(opt->name, " -check ")) {
16379             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16380             opt->value = (def != 0);
16381             opt->type = CheckBox;
16382         } else if(p = strstr(opt->name, " -combo ")) {
16383             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16384             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16385             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16386             opt->value = n = 0;
16387             while(q = StrStr(q, " /// ")) {
16388                 n++; *q = 0;    // count choices, and null-terminate each of them
16389                 q += 5;
16390                 if(*q == '*') { // remember default, which is marked with * prefix
16391                     q++;
16392                     opt->value = n;
16393                 }
16394                 cps->comboList[cps->comboCnt++] = q;
16395             }
16396             cps->comboList[cps->comboCnt++] = NULL;
16397             opt->max = n + 1;
16398             opt->type = ComboBox;
16399         } else if(p = strstr(opt->name, " -button")) {
16400             opt->type = Button;
16401         } else if(p = strstr(opt->name, " -save")) {
16402             opt->type = SaveButton;
16403         } else return FALSE;
16404         *p = 0; // terminate option name
16405         // now look if the command-line options define a setting for this engine option.
16406         if(cps->optionSettings && cps->optionSettings[0])
16407             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16408         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16409           snprintf(buf, MSG_SIZ, "option %s", p);
16410                 if(p = strstr(buf, ",")) *p = 0;
16411                 if(q = strchr(buf, '=')) switch(opt->type) {
16412                     case ComboBox:
16413                         for(n=0; n<opt->max; n++)
16414                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16415                         break;
16416                     case TextBox:
16417                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16418                         break;
16419                     case Spin:
16420                     case CheckBox:
16421                         opt->value = atoi(q+1);
16422                     default:
16423                         break;
16424                 }
16425                 strcat(buf, "\n");
16426                 SendToProgram(buf, cps);
16427         }
16428         return TRUE;
16429 }
16430
16431 void
16432 FeatureDone (ChessProgramState *cps, int val)
16433 {
16434   DelayedEventCallback cb = GetDelayedEvent();
16435   if ((cb == InitBackEnd3 && cps == &first) ||
16436       (cb == SettingsMenuIfReady && cps == &second) ||
16437       (cb == LoadEngine) ||
16438       (cb == TwoMachinesEventIfReady)) {
16439     CancelDelayedEvent();
16440     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16441   }
16442   cps->initDone = val;
16443   if(val) cps->reload = FALSE;
16444 }
16445
16446 /* Parse feature command from engine */
16447 void
16448 ParseFeatures (char *args, ChessProgramState *cps)
16449 {
16450   char *p = args;
16451   char *q = NULL;
16452   int val;
16453   char buf[MSG_SIZ];
16454
16455   for (;;) {
16456     while (*p == ' ') p++;
16457     if (*p == NULLCHAR) return;
16458
16459     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16460     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16461     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16462     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16463     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16464     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16465     if (BoolFeature(&p, "reuse", &val, cps)) {
16466       /* Engine can disable reuse, but can't enable it if user said no */
16467       if (!val) cps->reuse = FALSE;
16468       continue;
16469     }
16470     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16471     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16472       if (gameMode == TwoMachinesPlay) {
16473         DisplayTwoMachinesTitle();
16474       } else {
16475         DisplayTitle("");
16476       }
16477       continue;
16478     }
16479     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16480     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16481     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16482     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16483     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16484     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16485     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16486     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16487     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16488     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16489     if (IntFeature(&p, "done", &val, cps)) {
16490       FeatureDone(cps, val);
16491       continue;
16492     }
16493     /* Added by Tord: */
16494     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16495     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16496     /* End of additions by Tord */
16497
16498     /* [HGM] added features: */
16499     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16500     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16501     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16502     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16503     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16504     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16505     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16506     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16507         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16508         FREE(cps->option[cps->nrOptions].name);
16509         cps->option[cps->nrOptions].name = q; q = NULL;
16510         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16511           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16512             SendToProgram(buf, cps);
16513             continue;
16514         }
16515         if(cps->nrOptions >= MAX_OPTIONS) {
16516             cps->nrOptions--;
16517             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16518             DisplayError(buf, 0);
16519         }
16520         continue;
16521     }
16522     /* End of additions by HGM */
16523
16524     /* unknown feature: complain and skip */
16525     q = p;
16526     while (*q && *q != '=') q++;
16527     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16528     SendToProgram(buf, cps);
16529     p = q;
16530     if (*p == '=') {
16531       p++;
16532       if (*p == '\"') {
16533         p++;
16534         while (*p && *p != '\"') p++;
16535         if (*p == '\"') p++;
16536       } else {
16537         while (*p && *p != ' ') p++;
16538       }
16539     }
16540   }
16541
16542 }
16543
16544 void
16545 PeriodicUpdatesEvent (int newState)
16546 {
16547     if (newState == appData.periodicUpdates)
16548       return;
16549
16550     appData.periodicUpdates=newState;
16551
16552     /* Display type changes, so update it now */
16553 //    DisplayAnalysis();
16554
16555     /* Get the ball rolling again... */
16556     if (newState) {
16557         AnalysisPeriodicEvent(1);
16558         StartAnalysisClock();
16559     }
16560 }
16561
16562 void
16563 PonderNextMoveEvent (int newState)
16564 {
16565     if (newState == appData.ponderNextMove) return;
16566     if (gameMode == EditPosition) EditPositionDone(TRUE);
16567     if (newState) {
16568         SendToProgram("hard\n", &first);
16569         if (gameMode == TwoMachinesPlay) {
16570             SendToProgram("hard\n", &second);
16571         }
16572     } else {
16573         SendToProgram("easy\n", &first);
16574         thinkOutput[0] = NULLCHAR;
16575         if (gameMode == TwoMachinesPlay) {
16576             SendToProgram("easy\n", &second);
16577         }
16578     }
16579     appData.ponderNextMove = newState;
16580 }
16581
16582 void
16583 NewSettingEvent (int option, int *feature, char *command, int value)
16584 {
16585     char buf[MSG_SIZ];
16586
16587     if (gameMode == EditPosition) EditPositionDone(TRUE);
16588     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16589     if(feature == NULL || *feature) SendToProgram(buf, &first);
16590     if (gameMode == TwoMachinesPlay) {
16591         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16592     }
16593 }
16594
16595 void
16596 ShowThinkingEvent ()
16597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16598 {
16599     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16600     int newState = appData.showThinking
16601         // [HGM] thinking: other features now need thinking output as well
16602         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16603
16604     if (oldState == newState) return;
16605     oldState = newState;
16606     if (gameMode == EditPosition) EditPositionDone(TRUE);
16607     if (oldState) {
16608         SendToProgram("post\n", &first);
16609         if (gameMode == TwoMachinesPlay) {
16610             SendToProgram("post\n", &second);
16611         }
16612     } else {
16613         SendToProgram("nopost\n", &first);
16614         thinkOutput[0] = NULLCHAR;
16615         if (gameMode == TwoMachinesPlay) {
16616             SendToProgram("nopost\n", &second);
16617         }
16618     }
16619 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16620 }
16621
16622 void
16623 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16624 {
16625   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16626   if (pr == NoProc) return;
16627   AskQuestion(title, question, replyPrefix, pr);
16628 }
16629
16630 void
16631 TypeInEvent (char firstChar)
16632 {
16633     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16634         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16635         gameMode == AnalyzeMode || gameMode == EditGame ||
16636         gameMode == EditPosition || gameMode == IcsExamining ||
16637         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16638         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16639                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16640                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16641         gameMode == Training) PopUpMoveDialog(firstChar);
16642 }
16643
16644 void
16645 TypeInDoneEvent (char *move)
16646 {
16647         Board board;
16648         int n, fromX, fromY, toX, toY;
16649         char promoChar;
16650         ChessMove moveType;
16651
16652         // [HGM] FENedit
16653         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16654                 EditPositionPasteFEN(move);
16655                 return;
16656         }
16657         // [HGM] movenum: allow move number to be typed in any mode
16658         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16659           ToNrEvent(2*n-1);
16660           return;
16661         }
16662         // undocumented kludge: allow command-line option to be typed in!
16663         // (potentially fatal, and does not implement the effect of the option.)
16664         // should only be used for options that are values on which future decisions will be made,
16665         // and definitely not on options that would be used during initialization.
16666         if(strstr(move, "!!! -") == move) {
16667             ParseArgsFromString(move+4);
16668             return;
16669         }
16670
16671       if (gameMode != EditGame && currentMove != forwardMostMove &&
16672         gameMode != Training) {
16673         DisplayMoveError(_("Displayed move is not current"));
16674       } else {
16675         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16676           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16677         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16678         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16679           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16680           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16681         } else {
16682           DisplayMoveError(_("Could not parse move"));
16683         }
16684       }
16685 }
16686
16687 void
16688 DisplayMove (int moveNumber)
16689 {
16690     char message[MSG_SIZ];
16691     char res[MSG_SIZ];
16692     char cpThinkOutput[MSG_SIZ];
16693
16694     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16695
16696     if (moveNumber == forwardMostMove - 1 ||
16697         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16698
16699         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16700
16701         if (strchr(cpThinkOutput, '\n')) {
16702             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16703         }
16704     } else {
16705         *cpThinkOutput = NULLCHAR;
16706     }
16707
16708     /* [AS] Hide thinking from human user */
16709     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16710         *cpThinkOutput = NULLCHAR;
16711         if( thinkOutput[0] != NULLCHAR ) {
16712             int i;
16713
16714             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16715                 cpThinkOutput[i] = '.';
16716             }
16717             cpThinkOutput[i] = NULLCHAR;
16718             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16719         }
16720     }
16721
16722     if (moveNumber == forwardMostMove - 1 &&
16723         gameInfo.resultDetails != NULL) {
16724         if (gameInfo.resultDetails[0] == NULLCHAR) {
16725           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16726         } else {
16727           snprintf(res, MSG_SIZ, " {%s} %s",
16728                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16729         }
16730     } else {
16731         res[0] = NULLCHAR;
16732     }
16733
16734     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16735         DisplayMessage(res, cpThinkOutput);
16736     } else {
16737       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16738                 WhiteOnMove(moveNumber) ? " " : ".. ",
16739                 parseList[moveNumber], res);
16740         DisplayMessage(message, cpThinkOutput);
16741     }
16742 }
16743
16744 void
16745 DisplayComment (int moveNumber, char *text)
16746 {
16747     char title[MSG_SIZ];
16748
16749     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16750       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16751     } else {
16752       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16753               WhiteOnMove(moveNumber) ? " " : ".. ",
16754               parseList[moveNumber]);
16755     }
16756     if (text != NULL && (appData.autoDisplayComment || commentUp))
16757         CommentPopUp(title, text);
16758 }
16759
16760 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16761  * might be busy thinking or pondering.  It can be omitted if your
16762  * gnuchess is configured to stop thinking immediately on any user
16763  * input.  However, that gnuchess feature depends on the FIONREAD
16764  * ioctl, which does not work properly on some flavors of Unix.
16765  */
16766 void
16767 Attention (ChessProgramState *cps)
16768 {
16769 #if ATTENTION
16770     if (!cps->useSigint) return;
16771     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16772     switch (gameMode) {
16773       case MachinePlaysWhite:
16774       case MachinePlaysBlack:
16775       case TwoMachinesPlay:
16776       case IcsPlayingWhite:
16777       case IcsPlayingBlack:
16778       case AnalyzeMode:
16779       case AnalyzeFile:
16780         /* Skip if we know it isn't thinking */
16781         if (!cps->maybeThinking) return;
16782         if (appData.debugMode)
16783           fprintf(debugFP, "Interrupting %s\n", cps->which);
16784         InterruptChildProcess(cps->pr);
16785         cps->maybeThinking = FALSE;
16786         break;
16787       default:
16788         break;
16789     }
16790 #endif /*ATTENTION*/
16791 }
16792
16793 int
16794 CheckFlags ()
16795 {
16796     if (whiteTimeRemaining <= 0) {
16797         if (!whiteFlag) {
16798             whiteFlag = TRUE;
16799             if (appData.icsActive) {
16800                 if (appData.autoCallFlag &&
16801                     gameMode == IcsPlayingBlack && !blackFlag) {
16802                   SendToICS(ics_prefix);
16803                   SendToICS("flag\n");
16804                 }
16805             } else {
16806                 if (blackFlag) {
16807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16808                 } else {
16809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16810                     if (appData.autoCallFlag) {
16811                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16812                         return TRUE;
16813                     }
16814                 }
16815             }
16816         }
16817     }
16818     if (blackTimeRemaining <= 0) {
16819         if (!blackFlag) {
16820             blackFlag = TRUE;
16821             if (appData.icsActive) {
16822                 if (appData.autoCallFlag &&
16823                     gameMode == IcsPlayingWhite && !whiteFlag) {
16824                   SendToICS(ics_prefix);
16825                   SendToICS("flag\n");
16826                 }
16827             } else {
16828                 if (whiteFlag) {
16829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16830                 } else {
16831                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16832                     if (appData.autoCallFlag) {
16833                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16834                         return TRUE;
16835                     }
16836                 }
16837             }
16838         }
16839     }
16840     return FALSE;
16841 }
16842
16843 void
16844 CheckTimeControl ()
16845 {
16846     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16847         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16848
16849     /*
16850      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16851      */
16852     if ( !WhiteOnMove(forwardMostMove) ) {
16853         /* White made time control */
16854         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16855         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16856         /* [HGM] time odds: correct new time quota for time odds! */
16857                                             / WhitePlayer()->timeOdds;
16858         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16859     } else {
16860         lastBlack -= blackTimeRemaining;
16861         /* Black made time control */
16862         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16863                                             / WhitePlayer()->other->timeOdds;
16864         lastWhite = whiteTimeRemaining;
16865     }
16866 }
16867
16868 void
16869 DisplayBothClocks ()
16870 {
16871     int wom = gameMode == EditPosition ?
16872       !blackPlaysFirst : WhiteOnMove(currentMove);
16873     DisplayWhiteClock(whiteTimeRemaining, wom);
16874     DisplayBlackClock(blackTimeRemaining, !wom);
16875 }
16876
16877
16878 /* Timekeeping seems to be a portability nightmare.  I think everyone
16879    has ftime(), but I'm really not sure, so I'm including some ifdefs
16880    to use other calls if you don't.  Clocks will be less accurate if
16881    you have neither ftime nor gettimeofday.
16882 */
16883
16884 /* VS 2008 requires the #include outside of the function */
16885 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16886 #include <sys/timeb.h>
16887 #endif
16888
16889 /* Get the current time as a TimeMark */
16890 void
16891 GetTimeMark (TimeMark *tm)
16892 {
16893 #if HAVE_GETTIMEOFDAY
16894
16895     struct timeval timeVal;
16896     struct timezone timeZone;
16897
16898     gettimeofday(&timeVal, &timeZone);
16899     tm->sec = (long) timeVal.tv_sec;
16900     tm->ms = (int) (timeVal.tv_usec / 1000L);
16901
16902 #else /*!HAVE_GETTIMEOFDAY*/
16903 #if HAVE_FTIME
16904
16905 // include <sys/timeb.h> / moved to just above start of function
16906     struct timeb timeB;
16907
16908     ftime(&timeB);
16909     tm->sec = (long) timeB.time;
16910     tm->ms = (int) timeB.millitm;
16911
16912 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16913     tm->sec = (long) time(NULL);
16914     tm->ms = 0;
16915 #endif
16916 #endif
16917 }
16918
16919 /* Return the difference in milliseconds between two
16920    time marks.  We assume the difference will fit in a long!
16921 */
16922 long
16923 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16924 {
16925     return 1000L*(tm2->sec - tm1->sec) +
16926            (long) (tm2->ms - tm1->ms);
16927 }
16928
16929
16930 /*
16931  * Code to manage the game clocks.
16932  *
16933  * In tournament play, black starts the clock and then white makes a move.
16934  * We give the human user a slight advantage if he is playing white---the
16935  * clocks don't run until he makes his first move, so it takes zero time.
16936  * Also, we don't account for network lag, so we could get out of sync
16937  * with GNU Chess's clock -- but then, referees are always right.
16938  */
16939
16940 static TimeMark tickStartTM;
16941 static long intendedTickLength;
16942
16943 long
16944 NextTickLength (long timeRemaining)
16945 {
16946     long nominalTickLength, nextTickLength;
16947
16948     if (timeRemaining > 0L && timeRemaining <= 10000L)
16949       nominalTickLength = 100L;
16950     else
16951       nominalTickLength = 1000L;
16952     nextTickLength = timeRemaining % nominalTickLength;
16953     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16954
16955     return nextTickLength;
16956 }
16957
16958 /* Adjust clock one minute up or down */
16959 void
16960 AdjustClock (Boolean which, int dir)
16961 {
16962     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16963     if(which) blackTimeRemaining += 60000*dir;
16964     else      whiteTimeRemaining += 60000*dir;
16965     DisplayBothClocks();
16966     adjustedClock = TRUE;
16967 }
16968
16969 /* Stop clocks and reset to a fresh time control */
16970 void
16971 ResetClocks ()
16972 {
16973     (void) StopClockTimer();
16974     if (appData.icsActive) {
16975         whiteTimeRemaining = blackTimeRemaining = 0;
16976     } else if (searchTime) {
16977         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16978         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16979     } else { /* [HGM] correct new time quote for time odds */
16980         whiteTC = blackTC = fullTimeControlString;
16981         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16982         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16983     }
16984     if (whiteFlag || blackFlag) {
16985         DisplayTitle("");
16986         whiteFlag = blackFlag = FALSE;
16987     }
16988     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16989     DisplayBothClocks();
16990     adjustedClock = FALSE;
16991 }
16992
16993 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16994
16995 /* Decrement running clock by amount of time that has passed */
16996 void
16997 DecrementClocks ()
16998 {
16999     long timeRemaining;
17000     long lastTickLength, fudge;
17001     TimeMark now;
17002
17003     if (!appData.clockMode) return;
17004     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17005
17006     GetTimeMark(&now);
17007
17008     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17009
17010     /* Fudge if we woke up a little too soon */
17011     fudge = intendedTickLength - lastTickLength;
17012     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17013
17014     if (WhiteOnMove(forwardMostMove)) {
17015         if(whiteNPS >= 0) lastTickLength = 0;
17016         timeRemaining = whiteTimeRemaining -= lastTickLength;
17017         if(timeRemaining < 0 && !appData.icsActive) {
17018             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17019             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17020                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17021                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17022             }
17023         }
17024         DisplayWhiteClock(whiteTimeRemaining - fudge,
17025                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17026     } else {
17027         if(blackNPS >= 0) lastTickLength = 0;
17028         timeRemaining = blackTimeRemaining -= lastTickLength;
17029         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17030             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17031             if(suddenDeath) {
17032                 blackStartMove = forwardMostMove;
17033                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17034             }
17035         }
17036         DisplayBlackClock(blackTimeRemaining - fudge,
17037                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17038     }
17039     if (CheckFlags()) return;
17040
17041     if(twoBoards) { // count down secondary board's clocks as well
17042         activePartnerTime -= lastTickLength;
17043         partnerUp = 1;
17044         if(activePartner == 'W')
17045             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17046         else
17047             DisplayBlackClock(activePartnerTime, TRUE);
17048         partnerUp = 0;
17049     }
17050
17051     tickStartTM = now;
17052     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17053     StartClockTimer(intendedTickLength);
17054
17055     /* if the time remaining has fallen below the alarm threshold, sound the
17056      * alarm. if the alarm has sounded and (due to a takeback or time control
17057      * with increment) the time remaining has increased to a level above the
17058      * threshold, reset the alarm so it can sound again.
17059      */
17060
17061     if (appData.icsActive && appData.icsAlarm) {
17062
17063         /* make sure we are dealing with the user's clock */
17064         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17065                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17066            )) return;
17067
17068         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17069             alarmSounded = FALSE;
17070         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17071             PlayAlarmSound();
17072             alarmSounded = TRUE;
17073         }
17074     }
17075 }
17076
17077
17078 /* A player has just moved, so stop the previously running
17079    clock and (if in clock mode) start the other one.
17080    We redisplay both clocks in case we're in ICS mode, because
17081    ICS gives us an update to both clocks after every move.
17082    Note that this routine is called *after* forwardMostMove
17083    is updated, so the last fractional tick must be subtracted
17084    from the color that is *not* on move now.
17085 */
17086 void
17087 SwitchClocks (int newMoveNr)
17088 {
17089     long lastTickLength;
17090     TimeMark now;
17091     int flagged = FALSE;
17092
17093     GetTimeMark(&now);
17094
17095     if (StopClockTimer() && appData.clockMode) {
17096         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17097         if (!WhiteOnMove(forwardMostMove)) {
17098             if(blackNPS >= 0) lastTickLength = 0;
17099             blackTimeRemaining -= lastTickLength;
17100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17101 //         if(pvInfoList[forwardMostMove].time == -1)
17102                  pvInfoList[forwardMostMove].time =               // use GUI time
17103                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17104         } else {
17105            if(whiteNPS >= 0) lastTickLength = 0;
17106            whiteTimeRemaining -= lastTickLength;
17107            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17108 //         if(pvInfoList[forwardMostMove].time == -1)
17109                  pvInfoList[forwardMostMove].time =
17110                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17111         }
17112         flagged = CheckFlags();
17113     }
17114     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17115     CheckTimeControl();
17116
17117     if (flagged || !appData.clockMode) return;
17118
17119     switch (gameMode) {
17120       case MachinePlaysBlack:
17121       case MachinePlaysWhite:
17122       case BeginningOfGame:
17123         if (pausing) return;
17124         break;
17125
17126       case EditGame:
17127       case PlayFromGameFile:
17128       case IcsExamining:
17129         return;
17130
17131       default:
17132         break;
17133     }
17134
17135     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17136         if(WhiteOnMove(forwardMostMove))
17137              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17138         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17139     }
17140
17141     tickStartTM = now;
17142     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17143       whiteTimeRemaining : blackTimeRemaining);
17144     StartClockTimer(intendedTickLength);
17145 }
17146
17147
17148 /* Stop both clocks */
17149 void
17150 StopClocks ()
17151 {
17152     long lastTickLength;
17153     TimeMark now;
17154
17155     if (!StopClockTimer()) return;
17156     if (!appData.clockMode) return;
17157
17158     GetTimeMark(&now);
17159
17160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17161     if (WhiteOnMove(forwardMostMove)) {
17162         if(whiteNPS >= 0) lastTickLength = 0;
17163         whiteTimeRemaining -= lastTickLength;
17164         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17165     } else {
17166         if(blackNPS >= 0) lastTickLength = 0;
17167         blackTimeRemaining -= lastTickLength;
17168         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17169     }
17170     CheckFlags();
17171 }
17172
17173 /* Start clock of player on move.  Time may have been reset, so
17174    if clock is already running, stop and restart it. */
17175 void
17176 StartClocks ()
17177 {
17178     (void) StopClockTimer(); /* in case it was running already */
17179     DisplayBothClocks();
17180     if (CheckFlags()) return;
17181
17182     if (!appData.clockMode) return;
17183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17184
17185     GetTimeMark(&tickStartTM);
17186     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17187       whiteTimeRemaining : blackTimeRemaining);
17188
17189    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17190     whiteNPS = blackNPS = -1;
17191     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17192        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17193         whiteNPS = first.nps;
17194     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17195        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17196         blackNPS = first.nps;
17197     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17198         whiteNPS = second.nps;
17199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17200         blackNPS = second.nps;
17201     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17202
17203     StartClockTimer(intendedTickLength);
17204 }
17205
17206 char *
17207 TimeString (long ms)
17208 {
17209     long second, minute, hour, day;
17210     char *sign = "";
17211     static char buf[32];
17212
17213     if (ms > 0 && ms <= 9900) {
17214       /* convert milliseconds to tenths, rounding up */
17215       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17216
17217       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17218       return buf;
17219     }
17220
17221     /* convert milliseconds to seconds, rounding up */
17222     /* use floating point to avoid strangeness of integer division
17223        with negative dividends on many machines */
17224     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17225
17226     if (second < 0) {
17227         sign = "-";
17228         second = -second;
17229     }
17230
17231     day = second / (60 * 60 * 24);
17232     second = second % (60 * 60 * 24);
17233     hour = second / (60 * 60);
17234     second = second % (60 * 60);
17235     minute = second / 60;
17236     second = second % 60;
17237
17238     if (day > 0)
17239       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17240               sign, day, hour, minute, second);
17241     else if (hour > 0)
17242       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17243     else
17244       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17245
17246     return buf;
17247 }
17248
17249
17250 /*
17251  * This is necessary because some C libraries aren't ANSI C compliant yet.
17252  */
17253 char *
17254 StrStr (char *string, char *match)
17255 {
17256     int i, length;
17257
17258     length = strlen(match);
17259
17260     for (i = strlen(string) - length; i >= 0; i--, string++)
17261       if (!strncmp(match, string, length))
17262         return string;
17263
17264     return NULL;
17265 }
17266
17267 char *
17268 StrCaseStr (char *string, char *match)
17269 {
17270     int i, j, length;
17271
17272     length = strlen(match);
17273
17274     for (i = strlen(string) - length; i >= 0; i--, string++) {
17275         for (j = 0; j < length; j++) {
17276             if (ToLower(match[j]) != ToLower(string[j]))
17277               break;
17278         }
17279         if (j == length) return string;
17280     }
17281
17282     return NULL;
17283 }
17284
17285 #ifndef _amigados
17286 int
17287 StrCaseCmp (char *s1, char *s2)
17288 {
17289     char c1, c2;
17290
17291     for (;;) {
17292         c1 = ToLower(*s1++);
17293         c2 = ToLower(*s2++);
17294         if (c1 > c2) return 1;
17295         if (c1 < c2) return -1;
17296         if (c1 == NULLCHAR) return 0;
17297     }
17298 }
17299
17300
17301 int
17302 ToLower (int c)
17303 {
17304     return isupper(c) ? tolower(c) : c;
17305 }
17306
17307
17308 int
17309 ToUpper (int c)
17310 {
17311     return islower(c) ? toupper(c) : c;
17312 }
17313 #endif /* !_amigados    */
17314
17315 char *
17316 StrSave (char *s)
17317 {
17318   char *ret;
17319
17320   if ((ret = (char *) malloc(strlen(s) + 1)))
17321     {
17322       safeStrCpy(ret, s, strlen(s)+1);
17323     }
17324   return ret;
17325 }
17326
17327 char *
17328 StrSavePtr (char *s, char **savePtr)
17329 {
17330     if (*savePtr) {
17331         free(*savePtr);
17332     }
17333     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17334       safeStrCpy(*savePtr, s, strlen(s)+1);
17335     }
17336     return(*savePtr);
17337 }
17338
17339 char *
17340 PGNDate ()
17341 {
17342     time_t clock;
17343     struct tm *tm;
17344     char buf[MSG_SIZ];
17345
17346     clock = time((time_t *)NULL);
17347     tm = localtime(&clock);
17348     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17349             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17350     return StrSave(buf);
17351 }
17352
17353
17354 char *
17355 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17356 {
17357     int i, j, fromX, fromY, toX, toY;
17358     int whiteToPlay;
17359     char buf[MSG_SIZ];
17360     char *p, *q;
17361     int emptycount;
17362     ChessSquare piece;
17363
17364     whiteToPlay = (gameMode == EditPosition) ?
17365       !blackPlaysFirst : (move % 2 == 0);
17366     p = buf;
17367
17368     /* Piece placement data */
17369     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17370         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17371         emptycount = 0;
17372         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17373             if (boards[move][i][j] == EmptySquare) {
17374                 emptycount++;
17375             } else { ChessSquare piece = boards[move][i][j];
17376                 if (emptycount > 0) {
17377                     if(emptycount<10) /* [HGM] can be >= 10 */
17378                         *p++ = '0' + emptycount;
17379                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17380                     emptycount = 0;
17381                 }
17382                 if(PieceToChar(piece) == '+') {
17383                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17384                     *p++ = '+';
17385                     piece = (ChessSquare)(DEMOTED piece);
17386                 }
17387                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17388                 if(p[-1] == '~') {
17389                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17390                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17391                     *p++ = '~';
17392                 }
17393             }
17394         }
17395         if (emptycount > 0) {
17396             if(emptycount<10) /* [HGM] can be >= 10 */
17397                 *p++ = '0' + emptycount;
17398             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17399             emptycount = 0;
17400         }
17401         *p++ = '/';
17402     }
17403     *(p - 1) = ' ';
17404
17405     /* [HGM] print Crazyhouse or Shogi holdings */
17406     if( gameInfo.holdingsWidth ) {
17407         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17408         q = p;
17409         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17410             piece = boards[move][i][BOARD_WIDTH-1];
17411             if( piece != EmptySquare )
17412               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17413                   *p++ = PieceToChar(piece);
17414         }
17415         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17416             piece = boards[move][BOARD_HEIGHT-i-1][0];
17417             if( piece != EmptySquare )
17418               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17419                   *p++ = PieceToChar(piece);
17420         }
17421
17422         if( q == p ) *p++ = '-';
17423         *p++ = ']';
17424         *p++ = ' ';
17425     }
17426
17427     /* Active color */
17428     *p++ = whiteToPlay ? 'w' : 'b';
17429     *p++ = ' ';
17430
17431   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17432     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17433   } else {
17434   if(nrCastlingRights) {
17435      q = p;
17436      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17437        /* [HGM] write directly from rights */
17438            if(boards[move][CASTLING][2] != NoRights &&
17439               boards[move][CASTLING][0] != NoRights   )
17440                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17441            if(boards[move][CASTLING][2] != NoRights &&
17442               boards[move][CASTLING][1] != NoRights   )
17443                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17444            if(boards[move][CASTLING][5] != NoRights &&
17445               boards[move][CASTLING][3] != NoRights   )
17446                 *p++ = boards[move][CASTLING][3] + AAA;
17447            if(boards[move][CASTLING][5] != NoRights &&
17448               boards[move][CASTLING][4] != NoRights   )
17449                 *p++ = boards[move][CASTLING][4] + AAA;
17450      } else {
17451
17452         /* [HGM] write true castling rights */
17453         if( nrCastlingRights == 6 ) {
17454             int q, k=0;
17455             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17456                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17457             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17458                  boards[move][CASTLING][2] != NoRights  );
17459             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17460                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17461                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17462                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17463                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17464             }
17465             if(q) *p++ = 'Q';
17466             k = 0;
17467             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17468                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17469             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17470                  boards[move][CASTLING][5] != NoRights  );
17471             if(gameInfo.variant == VariantSChess) {
17472                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17473                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17474                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17475                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17476             }
17477             if(q) *p++ = 'q';
17478         }
17479      }
17480      if (q == p) *p++ = '-'; /* No castling rights */
17481      *p++ = ' ';
17482   }
17483
17484   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17485      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17486      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17487     /* En passant target square */
17488     if (move > backwardMostMove) {
17489         fromX = moveList[move - 1][0] - AAA;
17490         fromY = moveList[move - 1][1] - ONE;
17491         toX = moveList[move - 1][2] - AAA;
17492         toY = moveList[move - 1][3] - ONE;
17493         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17494             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17495             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17496             fromX == toX) {
17497             /* 2-square pawn move just happened */
17498             *p++ = toX + AAA;
17499             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17500         } else {
17501             *p++ = '-';
17502         }
17503     } else if(move == backwardMostMove) {
17504         // [HGM] perhaps we should always do it like this, and forget the above?
17505         if((signed char)boards[move][EP_STATUS] >= 0) {
17506             *p++ = boards[move][EP_STATUS] + AAA;
17507             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17508         } else {
17509             *p++ = '-';
17510         }
17511     } else {
17512         *p++ = '-';
17513     }
17514     *p++ = ' ';
17515   }
17516   }
17517
17518     if(moveCounts)
17519     {   int i = 0, j=move;
17520
17521         /* [HGM] find reversible plies */
17522         if (appData.debugMode) { int k;
17523             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17524             for(k=backwardMostMove; k<=forwardMostMove; k++)
17525                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17526
17527         }
17528
17529         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17530         if( j == backwardMostMove ) i += initialRulePlies;
17531         sprintf(p, "%d ", i);
17532         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17533
17534         /* Fullmove number */
17535         sprintf(p, "%d", (move / 2) + 1);
17536     } else *--p = NULLCHAR;
17537
17538     return StrSave(buf);
17539 }
17540
17541 Boolean
17542 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17543 {
17544     int i, j, k, w=0;
17545     char *p, c;
17546     int emptycount, virgin[BOARD_FILES];
17547     ChessSquare piece;
17548
17549     p = fen;
17550
17551     /* Piece placement data */
17552     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17553         j = 0;
17554         for (;;) {
17555             if (*p == '/' || *p == ' ' || *p == '[' ) {
17556                 if(j > w) w = j;
17557                 emptycount = gameInfo.boardWidth - j;
17558                 while (emptycount--)
17559                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17560                 if (*p == '/') p++;
17561                 else if(autoSize) { // we stumbled unexpectedly into end of board
17562                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17563                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17564                     }
17565                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17566                 }
17567                 break;
17568 #if(BOARD_FILES >= 10)
17569             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17570                 p++; emptycount=10;
17571                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17572                 while (emptycount--)
17573                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17574 #endif
17575             } else if (*p == '*') {
17576                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17577             } else if (isdigit(*p)) {
17578                 emptycount = *p++ - '0';
17579                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17580                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17581                 while (emptycount--)
17582                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17583             } else if (*p == '+' || isalpha(*p)) {
17584                 if (j >= gameInfo.boardWidth) return FALSE;
17585                 if(*p=='+') {
17586                     piece = CharToPiece(*++p);
17587                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17588                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17589                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17590                 } else piece = CharToPiece(*p++);
17591
17592                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17593                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17594                     piece = (ChessSquare) (PROMOTED piece);
17595                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17596                     p++;
17597                 }
17598                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17599             } else {
17600                 return FALSE;
17601             }
17602         }
17603     }
17604     while (*p == '/' || *p == ' ') p++;
17605
17606     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17607
17608     /* [HGM] by default clear Crazyhouse holdings, if present */
17609     if(gameInfo.holdingsWidth) {
17610        for(i=0; i<BOARD_HEIGHT; i++) {
17611            board[i][0]             = EmptySquare; /* black holdings */
17612            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17613            board[i][1]             = (ChessSquare) 0; /* black counts */
17614            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17615        }
17616     }
17617
17618     /* [HGM] look for Crazyhouse holdings here */
17619     while(*p==' ') p++;
17620     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17621         if(*p == '[') p++;
17622         if(*p == '-' ) p++; /* empty holdings */ else {
17623             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17624             /* if we would allow FEN reading to set board size, we would   */
17625             /* have to add holdings and shift the board read so far here   */
17626             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17627                 p++;
17628                 if((int) piece >= (int) BlackPawn ) {
17629                     i = (int)piece - (int)BlackPawn;
17630                     i = PieceToNumber((ChessSquare)i);
17631                     if( i >= gameInfo.holdingsSize ) return FALSE;
17632                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17633                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17634                 } else {
17635                     i = (int)piece - (int)WhitePawn;
17636                     i = PieceToNumber((ChessSquare)i);
17637                     if( i >= gameInfo.holdingsSize ) return FALSE;
17638                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17639                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17640                 }
17641             }
17642         }
17643         if(*p == ']') p++;
17644     }
17645
17646     while(*p == ' ') p++;
17647
17648     /* Active color */
17649     c = *p++;
17650     if(appData.colorNickNames) {
17651       if( c == appData.colorNickNames[0] ) c = 'w'; else
17652       if( c == appData.colorNickNames[1] ) c = 'b';
17653     }
17654     switch (c) {
17655       case 'w':
17656         *blackPlaysFirst = FALSE;
17657         break;
17658       case 'b':
17659         *blackPlaysFirst = TRUE;
17660         break;
17661       default:
17662         return FALSE;
17663     }
17664
17665     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17666     /* return the extra info in global variiables             */
17667
17668     /* set defaults in case FEN is incomplete */
17669     board[EP_STATUS] = EP_UNKNOWN;
17670     for(i=0; i<nrCastlingRights; i++ ) {
17671         board[CASTLING][i] =
17672             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17673     }   /* assume possible unless obviously impossible */
17674     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17675     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17676     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17677                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17678     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17679     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17680     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17681                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17682     FENrulePlies = 0;
17683
17684     while(*p==' ') p++;
17685     if(nrCastlingRights) {
17686       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17687       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17688           /* castling indicator present, so default becomes no castlings */
17689           for(i=0; i<nrCastlingRights; i++ ) {
17690                  board[CASTLING][i] = NoRights;
17691           }
17692       }
17693       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17694              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17695              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17696              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17697         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17698
17699         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17700             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17701             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17702         }
17703         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17704             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17705         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17706                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17707         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17708                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17709         switch(c) {
17710           case'K':
17711               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17712               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17713               board[CASTLING][2] = whiteKingFile;
17714               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17715               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17716               break;
17717           case'Q':
17718               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17719               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17720               board[CASTLING][2] = whiteKingFile;
17721               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17722               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17723               break;
17724           case'k':
17725               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17726               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17727               board[CASTLING][5] = blackKingFile;
17728               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17729               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17730               break;
17731           case'q':
17732               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17733               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17734               board[CASTLING][5] = blackKingFile;
17735               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17736               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17737           case '-':
17738               break;
17739           default: /* FRC castlings */
17740               if(c >= 'a') { /* black rights */
17741                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17742                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17743                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17744                   if(i == BOARD_RGHT) break;
17745                   board[CASTLING][5] = i;
17746                   c -= AAA;
17747                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17748                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17749                   if(c > i)
17750                       board[CASTLING][3] = c;
17751                   else
17752                       board[CASTLING][4] = c;
17753               } else { /* white rights */
17754                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17755                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17756                     if(board[0][i] == WhiteKing) break;
17757                   if(i == BOARD_RGHT) break;
17758                   board[CASTLING][2] = i;
17759                   c -= AAA - 'a' + 'A';
17760                   if(board[0][c] >= WhiteKing) break;
17761                   if(c > i)
17762                       board[CASTLING][0] = c;
17763                   else
17764                       board[CASTLING][1] = c;
17765               }
17766         }
17767       }
17768       for(i=0; i<nrCastlingRights; i++)
17769         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17770       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17771     if (appData.debugMode) {
17772         fprintf(debugFP, "FEN castling rights:");
17773         for(i=0; i<nrCastlingRights; i++)
17774         fprintf(debugFP, " %d", board[CASTLING][i]);
17775         fprintf(debugFP, "\n");
17776     }
17777
17778       while(*p==' ') p++;
17779     }
17780
17781     /* read e.p. field in games that know e.p. capture */
17782     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17783        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17784        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17785       if(*p=='-') {
17786         p++; board[EP_STATUS] = EP_NONE;
17787       } else {
17788          char c = *p++ - AAA;
17789
17790          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17791          if(*p >= '0' && *p <='9') p++;
17792          board[EP_STATUS] = c;
17793       }
17794     }
17795
17796
17797     if(sscanf(p, "%d", &i) == 1) {
17798         FENrulePlies = i; /* 50-move ply counter */
17799         /* (The move number is still ignored)    */
17800     }
17801
17802     return TRUE;
17803 }
17804
17805 void
17806 EditPositionPasteFEN (char *fen)
17807 {
17808   if (fen != NULL) {
17809     Board initial_position;
17810
17811     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17812       DisplayError(_("Bad FEN position in clipboard"), 0);
17813       return ;
17814     } else {
17815       int savedBlackPlaysFirst = blackPlaysFirst;
17816       EditPositionEvent();
17817       blackPlaysFirst = savedBlackPlaysFirst;
17818       CopyBoard(boards[0], initial_position);
17819       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17820       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17821       DisplayBothClocks();
17822       DrawPosition(FALSE, boards[currentMove]);
17823     }
17824   }
17825 }
17826
17827 static char cseq[12] = "\\   ";
17828
17829 Boolean
17830 set_cont_sequence (char *new_seq)
17831 {
17832     int len;
17833     Boolean ret;
17834
17835     // handle bad attempts to set the sequence
17836         if (!new_seq)
17837                 return 0; // acceptable error - no debug
17838
17839     len = strlen(new_seq);
17840     ret = (len > 0) && (len < sizeof(cseq));
17841     if (ret)
17842       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17843     else if (appData.debugMode)
17844       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17845     return ret;
17846 }
17847
17848 /*
17849     reformat a source message so words don't cross the width boundary.  internal
17850     newlines are not removed.  returns the wrapped size (no null character unless
17851     included in source message).  If dest is NULL, only calculate the size required
17852     for the dest buffer.  lp argument indicats line position upon entry, and it's
17853     passed back upon exit.
17854 */
17855 int
17856 wrap (char *dest, char *src, int count, int width, int *lp)
17857 {
17858     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17859
17860     cseq_len = strlen(cseq);
17861     old_line = line = *lp;
17862     ansi = len = clen = 0;
17863
17864     for (i=0; i < count; i++)
17865     {
17866         if (src[i] == '\033')
17867             ansi = 1;
17868
17869         // if we hit the width, back up
17870         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17871         {
17872             // store i & len in case the word is too long
17873             old_i = i, old_len = len;
17874
17875             // find the end of the last word
17876             while (i && src[i] != ' ' && src[i] != '\n')
17877             {
17878                 i--;
17879                 len--;
17880             }
17881
17882             // word too long?  restore i & len before splitting it
17883             if ((old_i-i+clen) >= width)
17884             {
17885                 i = old_i;
17886                 len = old_len;
17887             }
17888
17889             // extra space?
17890             if (i && src[i-1] == ' ')
17891                 len--;
17892
17893             if (src[i] != ' ' && src[i] != '\n')
17894             {
17895                 i--;
17896                 if (len)
17897                     len--;
17898             }
17899
17900             // now append the newline and continuation sequence
17901             if (dest)
17902                 dest[len] = '\n';
17903             len++;
17904             if (dest)
17905                 strncpy(dest+len, cseq, cseq_len);
17906             len += cseq_len;
17907             line = cseq_len;
17908             clen = cseq_len;
17909             continue;
17910         }
17911
17912         if (dest)
17913             dest[len] = src[i];
17914         len++;
17915         if (!ansi)
17916             line++;
17917         if (src[i] == '\n')
17918             line = 0;
17919         if (src[i] == 'm')
17920             ansi = 0;
17921     }
17922     if (dest && appData.debugMode)
17923     {
17924         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17925             count, width, line, len, *lp);
17926         show_bytes(debugFP, src, count);
17927         fprintf(debugFP, "\ndest: ");
17928         show_bytes(debugFP, dest, len);
17929         fprintf(debugFP, "\n");
17930     }
17931     *lp = dest ? line : old_line;
17932
17933     return len;
17934 }
17935
17936 // [HGM] vari: routines for shelving variations
17937 Boolean modeRestore = FALSE;
17938
17939 void
17940 PushInner (int firstMove, int lastMove)
17941 {
17942         int i, j, nrMoves = lastMove - firstMove;
17943
17944         // push current tail of game on stack
17945         savedResult[storedGames] = gameInfo.result;
17946         savedDetails[storedGames] = gameInfo.resultDetails;
17947         gameInfo.resultDetails = NULL;
17948         savedFirst[storedGames] = firstMove;
17949         savedLast [storedGames] = lastMove;
17950         savedFramePtr[storedGames] = framePtr;
17951         framePtr -= nrMoves; // reserve space for the boards
17952         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17953             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17954             for(j=0; j<MOVE_LEN; j++)
17955                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17956             for(j=0; j<2*MOVE_LEN; j++)
17957                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17958             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17959             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17960             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17961             pvInfoList[firstMove+i-1].depth = 0;
17962             commentList[framePtr+i] = commentList[firstMove+i];
17963             commentList[firstMove+i] = NULL;
17964         }
17965
17966         storedGames++;
17967         forwardMostMove = firstMove; // truncate game so we can start variation
17968 }
17969
17970 void
17971 PushTail (int firstMove, int lastMove)
17972 {
17973         if(appData.icsActive) { // only in local mode
17974                 forwardMostMove = currentMove; // mimic old ICS behavior
17975                 return;
17976         }
17977         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17978
17979         PushInner(firstMove, lastMove);
17980         if(storedGames == 1) GreyRevert(FALSE);
17981         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17982 }
17983
17984 void
17985 PopInner (Boolean annotate)
17986 {
17987         int i, j, nrMoves;
17988         char buf[8000], moveBuf[20];
17989
17990         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17991         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17992         nrMoves = savedLast[storedGames] - currentMove;
17993         if(annotate) {
17994                 int cnt = 10;
17995                 if(!WhiteOnMove(currentMove))
17996                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17997                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17998                 for(i=currentMove; i<forwardMostMove; i++) {
17999                         if(WhiteOnMove(i))
18000                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18001                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18002                         strcat(buf, moveBuf);
18003                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18004                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18005                 }
18006                 strcat(buf, ")");
18007         }
18008         for(i=1; i<=nrMoves; i++) { // copy last variation back
18009             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18010             for(j=0; j<MOVE_LEN; j++)
18011                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18012             for(j=0; j<2*MOVE_LEN; j++)
18013                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18014             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18015             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18016             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18017             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18018             commentList[currentMove+i] = commentList[framePtr+i];
18019             commentList[framePtr+i] = NULL;
18020         }
18021         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18022         framePtr = savedFramePtr[storedGames];
18023         gameInfo.result = savedResult[storedGames];
18024         if(gameInfo.resultDetails != NULL) {
18025             free(gameInfo.resultDetails);
18026       }
18027         gameInfo.resultDetails = savedDetails[storedGames];
18028         forwardMostMove = currentMove + nrMoves;
18029 }
18030
18031 Boolean
18032 PopTail (Boolean annotate)
18033 {
18034         if(appData.icsActive) return FALSE; // only in local mode
18035         if(!storedGames) return FALSE; // sanity
18036         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18037
18038         PopInner(annotate);
18039         if(currentMove < forwardMostMove) ForwardEvent(); else
18040         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18041
18042         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18043         return TRUE;
18044 }
18045
18046 void
18047 CleanupTail ()
18048 {       // remove all shelved variations
18049         int i;
18050         for(i=0; i<storedGames; i++) {
18051             if(savedDetails[i])
18052                 free(savedDetails[i]);
18053             savedDetails[i] = NULL;
18054         }
18055         for(i=framePtr; i<MAX_MOVES; i++) {
18056                 if(commentList[i]) free(commentList[i]);
18057                 commentList[i] = NULL;
18058         }
18059         framePtr = MAX_MOVES-1;
18060         storedGames = 0;
18061 }
18062
18063 void
18064 LoadVariation (int index, char *text)
18065 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18066         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18067         int level = 0, move;
18068
18069         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18070         // first find outermost bracketing variation
18071         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18072             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18073                 if(*p == '{') wait = '}'; else
18074                 if(*p == '[') wait = ']'; else
18075                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18076                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18077             }
18078             if(*p == wait) wait = NULLCHAR; // closing ]} found
18079             p++;
18080         }
18081         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18082         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18083         end[1] = NULLCHAR; // clip off comment beyond variation
18084         ToNrEvent(currentMove-1);
18085         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18086         // kludge: use ParsePV() to append variation to game
18087         move = currentMove;
18088         ParsePV(start, TRUE, TRUE);
18089         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18090         ClearPremoveHighlights();
18091         CommentPopDown();
18092         ToNrEvent(currentMove+1);
18093 }
18094
18095 void
18096 LoadTheme ()
18097 {
18098     char *p, *q, buf[MSG_SIZ];
18099     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18100         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18101         ParseArgsFromString(buf);
18102         ActivateTheme(TRUE); // also redo colors
18103         return;
18104     }
18105     p = nickName;
18106     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18107     {
18108         int len;
18109         q = appData.themeNames;
18110         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18111       if(appData.useBitmaps) {
18112         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18113                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18114                 appData.liteBackTextureMode,
18115                 appData.darkBackTextureMode );
18116       } else {
18117         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18118                 Col2Text(2),   // lightSquareColor
18119                 Col2Text(3) ); // darkSquareColor
18120       }
18121       if(appData.useBorder) {
18122         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18123                 appData.border);
18124       } else {
18125         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18126       }
18127       if(appData.useFont) {
18128         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18129                 appData.renderPiecesWithFont,
18130                 appData.fontToPieceTable,
18131                 Col2Text(9),    // appData.fontBackColorWhite
18132                 Col2Text(10) ); // appData.fontForeColorBlack
18133       } else {
18134         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18135                 appData.pieceDirectory);
18136         if(!appData.pieceDirectory[0])
18137           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18138                 Col2Text(0),   // whitePieceColor
18139                 Col2Text(1) ); // blackPieceColor
18140       }
18141       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18142                 Col2Text(4),   // highlightSquareColor
18143                 Col2Text(5) ); // premoveHighlightColor
18144         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18145         if(insert != q) insert[-1] = NULLCHAR;
18146         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18147         if(q)   free(q);
18148     }
18149     ActivateTheme(FALSE);
18150 }