0e0f80eec18f95d78e9a46defdb1b336a71bd612
[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
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 };
577
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 };
591
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 };
598
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 };
605
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 };
612
613 #ifdef GOTHIC
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !GOTHIC
621 #define GothicArray CapablancaArray
622 #endif // !GOTHIC
623
624 #ifdef FALCON
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
630 };
631 #else // !FALCON
632 #define FalconArray CapablancaArray
633 #endif // !FALCON
634
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
641
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 };
649 ChessSquare ChuArray[6][BOARD_FILES] = {
650     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
651       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
652     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
653       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
654     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
655       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
656     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
657       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
658     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
659       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
660     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
661       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
662 };
663 #else // !(BOARD_FILES>=12)
664 #define CourierArray CapablancaArray
665 #define ChuArray CapablancaArray
666 #endif // !(BOARD_FILES>=12)
667
668
669 Board initialPosition;
670
671
672 /* Convert str to a rating. Checks for special cases of "----",
673
674    "++++", etc. Also strips ()'s */
675 int
676 string_to_rating (char *str)
677 {
678   while(*str && !isdigit(*str)) ++str;
679   if (!*str)
680     return 0;   /* One of the special "no rating" cases */
681   else
682     return atoi(str);
683 }
684
685 void
686 ClearProgramStats ()
687 {
688     /* Init programStats */
689     programStats.movelist[0] = 0;
690     programStats.depth = 0;
691     programStats.nr_moves = 0;
692     programStats.moves_left = 0;
693     programStats.nodes = 0;
694     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
695     programStats.score = 0;
696     programStats.got_only_move = 0;
697     programStats.got_fail = 0;
698     programStats.line_is_book = 0;
699 }
700
701 void
702 CommonEngineInit ()
703 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711
712     first.other = &second;
713     second.other = &first;
714
715     { float norm = 1;
716         if(appData.timeOddsMode) {
717             norm = appData.timeOdds[0];
718             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
719         }
720         first.timeOdds  = appData.timeOdds[0]/norm;
721         second.timeOdds = appData.timeOdds[1]/norm;
722     }
723
724     if(programVersion) free(programVersion);
725     if (appData.noChessProgram) {
726         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
727         sprintf(programVersion, "%s", PACKAGE_STRING);
728     } else {
729       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
730       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
731       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
732     }
733 }
734
735 void
736 UnloadEngine (ChessProgramState *cps)
737 {
738         /* Kill off first chess program */
739         if (cps->isr != NULL)
740           RemoveInputSource(cps->isr);
741         cps->isr = NULL;
742
743         if (cps->pr != NoProc) {
744             ExitAnalyzeMode();
745             DoSleep( appData.delayBeforeQuit );
746             SendToProgram("quit\n", cps);
747             DoSleep( appData.delayAfterQuit );
748             DestroyChildProcess(cps->pr, cps->useSigterm);
749         }
750         cps->pr = NoProc;
751         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
752 }
753
754 void
755 ClearOptions (ChessProgramState *cps)
756 {
757     int i;
758     cps->nrOptions = cps->comboCnt = 0;
759     for(i=0; i<MAX_OPTIONS; i++) {
760         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
761         cps->option[i].textValue = 0;
762     }
763 }
764
765 char *engineNames[] = {
766   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
767      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
768 N_("first"),
769   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
770      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
771 N_("second")
772 };
773
774 void
775 InitEngine (ChessProgramState *cps, int n)
776 {   // [HGM] all engine initialiation put in a function that does one engine
777
778     ClearOptions(cps);
779
780     cps->which = engineNames[n];
781     cps->maybeThinking = FALSE;
782     cps->pr = NoProc;
783     cps->isr = NULL;
784     cps->sendTime = 2;
785     cps->sendDrawOffers = 1;
786
787     cps->program = appData.chessProgram[n];
788     cps->host = appData.host[n];
789     cps->dir = appData.directory[n];
790     cps->initString = appData.engInitString[n];
791     cps->computerString = appData.computerString[n];
792     cps->useSigint  = TRUE;
793     cps->useSigterm = TRUE;
794     cps->reuse = appData.reuse[n];
795     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
796     cps->useSetboard = FALSE;
797     cps->useSAN = FALSE;
798     cps->usePing = FALSE;
799     cps->lastPing = 0;
800     cps->lastPong = 0;
801     cps->usePlayother = FALSE;
802     cps->useColors = TRUE;
803     cps->useUsermove = FALSE;
804     cps->sendICS = FALSE;
805     cps->sendName = appData.icsActive;
806     cps->sdKludge = FALSE;
807     cps->stKludge = FALSE;
808     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
809     TidyProgramName(cps->program, cps->host, cps->tidy);
810     cps->matchWins = 0;
811     ASSIGN(cps->variants, appData.variant);
812     cps->analysisSupport = 2; /* detect */
813     cps->analyzing = FALSE;
814     cps->initDone = FALSE;
815     cps->reload = FALSE;
816
817     /* New features added by Tord: */
818     cps->useFEN960 = FALSE;
819     cps->useOOCastle = TRUE;
820     /* End of new features added by Tord. */
821     cps->fenOverride  = appData.fenOverride[n];
822
823     /* [HGM] time odds: set factor for each machine */
824     cps->timeOdds  = appData.timeOdds[n];
825
826     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
827     cps->accumulateTC = appData.accumulateTC[n];
828     cps->maxNrOfSessions = 1;
829
830     /* [HGM] debug */
831     cps->debug = FALSE;
832
833     cps->supportsNPS = UNKNOWN;
834     cps->memSize = FALSE;
835     cps->maxCores = FALSE;
836     ASSIGN(cps->egtFormats, "");
837
838     /* [HGM] options */
839     cps->optionSettings  = appData.engOptions[n];
840
841     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
842     cps->isUCI = appData.isUCI[n]; /* [AS] */
843     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
844     cps->highlight = 0;
845
846     if (appData.protocolVersion[n] > PROTOVER
847         || appData.protocolVersion[n] < 1)
848       {
849         char buf[MSG_SIZ];
850         int len;
851
852         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853                        appData.protocolVersion[n]);
854         if( (len >= MSG_SIZ) && appData.debugMode )
855           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856
857         DisplayFatalError(buf, 0, 2);
858       }
859     else
860       {
861         cps->protocolVersion = appData.protocolVersion[n];
862       }
863
864     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
865     ParseFeatures(appData.featureDefaults, cps);
866 }
867
868 ChessProgramState *savCps;
869
870 GameMode oldMode;
871
872 void
873 LoadEngine ()
874 {
875     int i;
876     if(WaitForEngine(savCps, LoadEngine)) return;
877     CommonEngineInit(); // recalculate time odds
878     if(gameInfo.variant != StringToVariant(appData.variant)) {
879         // we changed variant when loading the engine; this forces us to reset
880         Reset(TRUE, savCps != &first);
881         oldMode = BeginningOfGame; // to prevent restoring old mode
882     }
883     InitChessProgram(savCps, FALSE);
884     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
885     DisplayMessage("", "");
886     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
887     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
888     ThawUI();
889     SetGNUMode();
890     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
891 }
892
893 void
894 ReplaceEngine (ChessProgramState *cps, int n)
895 {
896     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
897     keepInfo = 1;
898     if(oldMode != BeginningOfGame) EditGameEvent();
899     keepInfo = 0;
900     UnloadEngine(cps);
901     appData.noChessProgram = FALSE;
902     appData.clockMode = TRUE;
903     InitEngine(cps, n);
904     UpdateLogos(TRUE);
905     if(n) return; // only startup first engine immediately; second can wait
906     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
907     LoadEngine();
908 }
909
910 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
911 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
912
913 static char resetOptions[] =
914         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
915         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
916         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
917         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
918
919 void
920 FloatToFront(char **list, char *engineLine)
921 {
922     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
923     int i=0;
924     if(appData.recentEngines <= 0) return;
925     TidyProgramName(engineLine, "localhost", tidy+1);
926     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
927     strncpy(buf+1, *list, MSG_SIZ-50);
928     if(p = strstr(buf, tidy)) { // tidy name appears in list
929         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
930         while(*p++ = *++q); // squeeze out
931     }
932     strcat(tidy, buf+1); // put list behind tidy name
933     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
934     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
935     ASSIGN(*list, tidy+1);
936 }
937
938 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
939
940 void
941 Load (ChessProgramState *cps, int i)
942 {
943     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
944     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
945         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
946         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
947         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
948         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
949         appData.firstProtocolVersion = PROTOVER;
950         ParseArgsFromString(buf);
951         SwapEngines(i);
952         ReplaceEngine(cps, i);
953         FloatToFront(&appData.recentEngineList, engineLine);
954         return;
955     }
956     p = engineName;
957     while(q = strchr(p, SLASH)) p = q+1;
958     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
959     if(engineDir[0] != NULLCHAR) {
960         ASSIGN(appData.directory[i], engineDir); p = engineName;
961     } else if(p != engineName) { // derive directory from engine path, when not given
962         p[-1] = 0;
963         ASSIGN(appData.directory[i], engineName);
964         p[-1] = SLASH;
965         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
966     } else { ASSIGN(appData.directory[i], "."); }
967     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
968     if(params[0]) {
969         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
970         snprintf(command, MSG_SIZ, "%s %s", p, params);
971         p = command;
972     }
973     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
974     ASSIGN(appData.chessProgram[i], p);
975     appData.isUCI[i] = isUCI;
976     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
977     appData.hasOwnBookUCI[i] = hasBook;
978     if(!nickName[0]) useNick = FALSE;
979     if(useNick) ASSIGN(appData.pgnName[i], nickName);
980     if(addToList) {
981         int len;
982         char quote;
983         q = firstChessProgramNames;
984         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
985         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
986         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
987                         quote, p, quote, appData.directory[i],
988                         useNick ? " -fn \"" : "",
989                         useNick ? nickName : "",
990                         useNick ? "\"" : "",
991                         v1 ? " -firstProtocolVersion 1" : "",
992                         hasBook ? "" : " -fNoOwnBookUCI",
993                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
994                         storeVariant ? " -variant " : "",
995                         storeVariant ? VariantName(gameInfo.variant) : "");
996         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
997         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
998         if(insert != q) insert[-1] = NULLCHAR;
999         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1000         if(q)   free(q);
1001         FloatToFront(&appData.recentEngineList, buf);
1002     }
1003     ReplaceEngine(cps, i);
1004 }
1005
1006 void
1007 InitTimeControls ()
1008 {
1009     int matched, min, sec;
1010     /*
1011      * Parse timeControl resource
1012      */
1013     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1014                           appData.movesPerSession)) {
1015         char buf[MSG_SIZ];
1016         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1017         DisplayFatalError(buf, 0, 2);
1018     }
1019
1020     /*
1021      * Parse searchTime resource
1022      */
1023     if (*appData.searchTime != NULLCHAR) {
1024         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1025         if (matched == 1) {
1026             searchTime = min * 60;
1027         } else if (matched == 2) {
1028             searchTime = min * 60 + sec;
1029         } else {
1030             char buf[MSG_SIZ];
1031             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1032             DisplayFatalError(buf, 0, 2);
1033         }
1034     }
1035 }
1036
1037 void
1038 InitBackEnd1 ()
1039 {
1040
1041     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1042     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1043
1044     GetTimeMark(&programStartTime);
1045     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1046     appData.seedBase = random() + (random()<<15);
1047     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1048
1049     ClearProgramStats();
1050     programStats.ok_to_send = 1;
1051     programStats.seen_stat = 0;
1052
1053     /*
1054      * Initialize game list
1055      */
1056     ListNew(&gameList);
1057
1058
1059     /*
1060      * Internet chess server status
1061      */
1062     if (appData.icsActive) {
1063         appData.matchMode = FALSE;
1064         appData.matchGames = 0;
1065 #if ZIPPY
1066         appData.noChessProgram = !appData.zippyPlay;
1067 #else
1068         appData.zippyPlay = FALSE;
1069         appData.zippyTalk = FALSE;
1070         appData.noChessProgram = TRUE;
1071 #endif
1072         if (*appData.icsHelper != NULLCHAR) {
1073             appData.useTelnet = TRUE;
1074             appData.telnetProgram = appData.icsHelper;
1075         }
1076     } else {
1077         appData.zippyTalk = appData.zippyPlay = FALSE;
1078     }
1079
1080     /* [AS] Initialize pv info list [HGM] and game state */
1081     {
1082         int i, j;
1083
1084         for( i=0; i<=framePtr; i++ ) {
1085             pvInfoList[i].depth = -1;
1086             boards[i][EP_STATUS] = EP_NONE;
1087             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1088         }
1089     }
1090
1091     InitTimeControls();
1092
1093     /* [AS] Adjudication threshold */
1094     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1095
1096     InitEngine(&first, 0);
1097     InitEngine(&second, 1);
1098     CommonEngineInit();
1099
1100     pairing.which = "pairing"; // pairing engine
1101     pairing.pr = NoProc;
1102     pairing.isr = NULL;
1103     pairing.program = appData.pairingEngine;
1104     pairing.host = "localhost";
1105     pairing.dir = ".";
1106
1107     if (appData.icsActive) {
1108         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1109     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1110         appData.clockMode = FALSE;
1111         first.sendTime = second.sendTime = 0;
1112     }
1113
1114 #if ZIPPY
1115     /* Override some settings from environment variables, for backward
1116        compatibility.  Unfortunately it's not feasible to have the env
1117        vars just set defaults, at least in xboard.  Ugh.
1118     */
1119     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1120       ZippyInit();
1121     }
1122 #endif
1123
1124     if (!appData.icsActive) {
1125       char buf[MSG_SIZ];
1126       int len;
1127
1128       /* Check for variants that are supported only in ICS mode,
1129          or not at all.  Some that are accepted here nevertheless
1130          have bugs; see comments below.
1131       */
1132       VariantClass variant = StringToVariant(appData.variant);
1133       switch (variant) {
1134       case VariantBughouse:     /* need four players and two boards */
1135       case VariantKriegspiel:   /* need to hide pieces and move details */
1136         /* case VariantFischeRandom: (Fabien: moved below) */
1137         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1138         if( (len >= MSG_SIZ) && appData.debugMode )
1139           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140
1141         DisplayFatalError(buf, 0, 2);
1142         return;
1143
1144       case VariantUnknown:
1145       case VariantLoadable:
1146       case Variant29:
1147       case Variant30:
1148       case Variant31:
1149       case Variant32:
1150       case Variant33:
1151       case Variant34:
1152       case Variant35:
1153       case Variant36:
1154       default:
1155         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1156         if( (len >= MSG_SIZ) && appData.debugMode )
1157           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1158
1159         DisplayFatalError(buf, 0, 2);
1160         return;
1161
1162       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1163       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1164       case VariantGothic:     /* [HGM] should work */
1165       case VariantCapablanca: /* [HGM] should work */
1166       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1167       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1168       case VariantChu:        /* [HGM] experimental */
1169       case VariantKnightmate: /* [HGM] should work */
1170       case VariantCylinder:   /* [HGM] untested */
1171       case VariantFalcon:     /* [HGM] untested */
1172       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1173                                  offboard interposition not understood */
1174       case VariantNormal:     /* definitely works! */
1175       case VariantWildCastle: /* pieces not automatically shuffled */
1176       case VariantNoCastle:   /* pieces not automatically shuffled */
1177       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1178       case VariantLosers:     /* should work except for win condition,
1179                                  and doesn't know captures are mandatory */
1180       case VariantSuicide:    /* should work except for win condition,
1181                                  and doesn't know captures are mandatory */
1182       case VariantGiveaway:   /* should work except for win condition,
1183                                  and doesn't know captures are mandatory */
1184       case VariantTwoKings:   /* should work */
1185       case VariantAtomic:     /* should work except for win condition */
1186       case Variant3Check:     /* should work except for win condition */
1187       case VariantShatranj:   /* should work except for all win conditions */
1188       case VariantMakruk:     /* should work except for draw countdown */
1189       case VariantASEAN :     /* should work except for draw countdown */
1190       case VariantBerolina:   /* might work if TestLegality is off */
1191       case VariantCapaRandom: /* should work */
1192       case VariantJanus:      /* should work */
1193       case VariantSuper:      /* experimental */
1194       case VariantGreat:      /* experimental, requires legality testing to be off */
1195       case VariantSChess:     /* S-Chess, should work */
1196       case VariantGrand:      /* should work */
1197       case VariantSpartan:    /* should work */
1198         break;
1199       }
1200     }
1201
1202 }
1203
1204 int
1205 NextIntegerFromString (char ** str, long * value)
1206 {
1207     int result = -1;
1208     char * s = *str;
1209
1210     while( *s == ' ' || *s == '\t' ) {
1211         s++;
1212     }
1213
1214     *value = 0;
1215
1216     if( *s >= '0' && *s <= '9' ) {
1217         while( *s >= '0' && *s <= '9' ) {
1218             *value = *value * 10 + (*s - '0');
1219             s++;
1220         }
1221
1222         result = 0;
1223     }
1224
1225     *str = s;
1226
1227     return result;
1228 }
1229
1230 int
1231 NextTimeControlFromString (char ** str, long * value)
1232 {
1233     long temp;
1234     int result = NextIntegerFromString( str, &temp );
1235
1236     if( result == 0 ) {
1237         *value = temp * 60; /* Minutes */
1238         if( **str == ':' ) {
1239             (*str)++;
1240             result = NextIntegerFromString( str, &temp );
1241             *value += temp; /* Seconds */
1242         }
1243     }
1244
1245     return result;
1246 }
1247
1248 int
1249 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1250 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1251     int result = -1, type = 0; long temp, temp2;
1252
1253     if(**str != ':') return -1; // old params remain in force!
1254     (*str)++;
1255     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1256     if( NextIntegerFromString( str, &temp ) ) return -1;
1257     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1258
1259     if(**str != '/') {
1260         /* time only: incremental or sudden-death time control */
1261         if(**str == '+') { /* increment follows; read it */
1262             (*str)++;
1263             if(**str == '!') type = *(*str)++; // Bronstein TC
1264             if(result = NextIntegerFromString( str, &temp2)) return -1;
1265             *inc = temp2 * 1000;
1266             if(**str == '.') { // read fraction of increment
1267                 char *start = ++(*str);
1268                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1269                 temp2 *= 1000;
1270                 while(start++ < *str) temp2 /= 10;
1271                 *inc += temp2;
1272             }
1273         } else *inc = 0;
1274         *moves = 0; *tc = temp * 1000; *incType = type;
1275         return 0;
1276     }
1277
1278     (*str)++; /* classical time control */
1279     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1280
1281     if(result == 0) {
1282         *moves = temp;
1283         *tc    = temp2 * 1000;
1284         *inc   = 0;
1285         *incType = type;
1286     }
1287     return result;
1288 }
1289
1290 int
1291 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1292 {   /* [HGM] get time to add from the multi-session time-control string */
1293     int incType, moves=1; /* kludge to force reading of first session */
1294     long time, increment;
1295     char *s = tcString;
1296
1297     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1298     do {
1299         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1300         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1301         if(movenr == -1) return time;    /* last move before new session     */
1302         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1303         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1304         if(!moves) return increment;     /* current session is incremental   */
1305         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1306     } while(movenr >= -1);               /* try again for next session       */
1307
1308     return 0; // no new time quota on this move
1309 }
1310
1311 int
1312 ParseTimeControl (char *tc, float ti, int mps)
1313 {
1314   long tc1;
1315   long tc2;
1316   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1317   int min, sec=0;
1318
1319   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1320   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1321       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1322   if(ti > 0) {
1323
1324     if(mps)
1325       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1326     else
1327       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1328   } else {
1329     if(mps)
1330       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1331     else
1332       snprintf(buf, MSG_SIZ, ":%s", mytc);
1333   }
1334   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1335
1336   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1337     return FALSE;
1338   }
1339
1340   if( *tc == '/' ) {
1341     /* Parse second time control */
1342     tc++;
1343
1344     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1345       return FALSE;
1346     }
1347
1348     if( tc2 == 0 ) {
1349       return FALSE;
1350     }
1351
1352     timeControl_2 = tc2 * 1000;
1353   }
1354   else {
1355     timeControl_2 = 0;
1356   }
1357
1358   if( tc1 == 0 ) {
1359     return FALSE;
1360   }
1361
1362   timeControl = tc1 * 1000;
1363
1364   if (ti >= 0) {
1365     timeIncrement = ti * 1000;  /* convert to ms */
1366     movesPerSession = 0;
1367   } else {
1368     timeIncrement = 0;
1369     movesPerSession = mps;
1370   }
1371   return TRUE;
1372 }
1373
1374 void
1375 InitBackEnd2 ()
1376 {
1377     if (appData.debugMode) {
1378 #    ifdef __GIT_VERSION
1379       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1380 #    else
1381       fprintf(debugFP, "Version: %s\n", programVersion);
1382 #    endif
1383     }
1384     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1385
1386     set_cont_sequence(appData.wrapContSeq);
1387     if (appData.matchGames > 0) {
1388         appData.matchMode = TRUE;
1389     } else if (appData.matchMode) {
1390         appData.matchGames = 1;
1391     }
1392     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1393         appData.matchGames = appData.sameColorGames;
1394     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1395         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1396         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1397     }
1398     Reset(TRUE, FALSE);
1399     if (appData.noChessProgram || first.protocolVersion == 1) {
1400       InitBackEnd3();
1401     } else {
1402       /* kludge: allow timeout for initial "feature" commands */
1403       FreezeUI();
1404       DisplayMessage("", _("Starting chess program"));
1405       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1406     }
1407 }
1408
1409 int
1410 CalculateIndex (int index, int gameNr)
1411 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1412     int res;
1413     if(index > 0) return index; // fixed nmber
1414     if(index == 0) return 1;
1415     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1416     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1417     return res;
1418 }
1419
1420 int
1421 LoadGameOrPosition (int gameNr)
1422 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1423     if (*appData.loadGameFile != NULLCHAR) {
1424         if (!LoadGameFromFile(appData.loadGameFile,
1425                 CalculateIndex(appData.loadGameIndex, gameNr),
1426                               appData.loadGameFile, FALSE)) {
1427             DisplayFatalError(_("Bad game file"), 0, 1);
1428             return 0;
1429         }
1430     } else if (*appData.loadPositionFile != NULLCHAR) {
1431         if (!LoadPositionFromFile(appData.loadPositionFile,
1432                 CalculateIndex(appData.loadPositionIndex, gameNr),
1433                                   appData.loadPositionFile)) {
1434             DisplayFatalError(_("Bad position file"), 0, 1);
1435             return 0;
1436         }
1437     }
1438     return 1;
1439 }
1440
1441 void
1442 ReserveGame (int gameNr, char resChar)
1443 {
1444     FILE *tf = fopen(appData.tourneyFile, "r+");
1445     char *p, *q, c, buf[MSG_SIZ];
1446     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1447     safeStrCpy(buf, lastMsg, MSG_SIZ);
1448     DisplayMessage(_("Pick new game"), "");
1449     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1450     ParseArgsFromFile(tf);
1451     p = q = appData.results;
1452     if(appData.debugMode) {
1453       char *r = appData.participants;
1454       fprintf(debugFP, "results = '%s'\n", p);
1455       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1456       fprintf(debugFP, "\n");
1457     }
1458     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1459     nextGame = q - p;
1460     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1461     safeStrCpy(q, p, strlen(p) + 2);
1462     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1463     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1464     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1465         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1466         q[nextGame] = '*';
1467     }
1468     fseek(tf, -(strlen(p)+4), SEEK_END);
1469     c = fgetc(tf);
1470     if(c != '"') // depending on DOS or Unix line endings we can be one off
1471          fseek(tf, -(strlen(p)+2), SEEK_END);
1472     else fseek(tf, -(strlen(p)+3), SEEK_END);
1473     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1474     DisplayMessage(buf, "");
1475     free(p); appData.results = q;
1476     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1477        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1478       int round = appData.defaultMatchGames * appData.tourneyType;
1479       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1480          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1481         UnloadEngine(&first);  // next game belongs to other pairing;
1482         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1483     }
1484     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1485 }
1486
1487 void
1488 MatchEvent (int mode)
1489 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1490         int dummy;
1491         if(matchMode) { // already in match mode: switch it off
1492             abortMatch = TRUE;
1493             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1494             return;
1495         }
1496 //      if(gameMode != BeginningOfGame) {
1497 //          DisplayError(_("You can only start a match from the initial position."), 0);
1498 //          return;
1499 //      }
1500         abortMatch = FALSE;
1501         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1502         /* Set up machine vs. machine match */
1503         nextGame = 0;
1504         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1505         if(appData.tourneyFile[0]) {
1506             ReserveGame(-1, 0);
1507             if(nextGame > appData.matchGames) {
1508                 char buf[MSG_SIZ];
1509                 if(strchr(appData.results, '*') == NULL) {
1510                     FILE *f;
1511                     appData.tourneyCycles++;
1512                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1513                         fclose(f);
1514                         NextTourneyGame(-1, &dummy);
1515                         ReserveGame(-1, 0);
1516                         if(nextGame <= appData.matchGames) {
1517                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1518                             matchMode = mode;
1519                             ScheduleDelayedEvent(NextMatchGame, 10000);
1520                             return;
1521                         }
1522                     }
1523                 }
1524                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1525                 DisplayError(buf, 0);
1526                 appData.tourneyFile[0] = 0;
1527                 return;
1528             }
1529         } else
1530         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1531             DisplayFatalError(_("Can't have a match with no chess programs"),
1532                               0, 2);
1533             return;
1534         }
1535         matchMode = mode;
1536         matchGame = roundNr = 1;
1537         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1538         NextMatchGame();
1539 }
1540
1541 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1542
1543 void
1544 InitBackEnd3 P((void))
1545 {
1546     GameMode initialMode;
1547     char buf[MSG_SIZ];
1548     int err, len;
1549
1550     InitChessProgram(&first, startedFromSetupPosition);
1551
1552     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1553         free(programVersion);
1554         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1555         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1556         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1557     }
1558
1559     if (appData.icsActive) {
1560 #ifdef WIN32
1561         /* [DM] Make a console window if needed [HGM] merged ifs */
1562         ConsoleCreate();
1563 #endif
1564         err = establish();
1565         if (err != 0)
1566           {
1567             if (*appData.icsCommPort != NULLCHAR)
1568               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1569                              appData.icsCommPort);
1570             else
1571               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1572                         appData.icsHost, appData.icsPort);
1573
1574             if( (len >= MSG_SIZ) && appData.debugMode )
1575               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1576
1577             DisplayFatalError(buf, err, 1);
1578             return;
1579         }
1580         SetICSMode();
1581         telnetISR =
1582           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1583         fromUserISR =
1584           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1585         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1586             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1587     } else if (appData.noChessProgram) {
1588         SetNCPMode();
1589     } else {
1590         SetGNUMode();
1591     }
1592
1593     if (*appData.cmailGameName != NULLCHAR) {
1594         SetCmailMode();
1595         OpenLoopback(&cmailPR);
1596         cmailISR =
1597           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1598     }
1599
1600     ThawUI();
1601     DisplayMessage("", "");
1602     if (StrCaseCmp(appData.initialMode, "") == 0) {
1603       initialMode = BeginningOfGame;
1604       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1605         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1606         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1607         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1608         ModeHighlight();
1609       }
1610     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1611       initialMode = TwoMachinesPlay;
1612     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1613       initialMode = AnalyzeFile;
1614     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1615       initialMode = AnalyzeMode;
1616     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1617       initialMode = MachinePlaysWhite;
1618     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1619       initialMode = MachinePlaysBlack;
1620     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1621       initialMode = EditGame;
1622     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1623       initialMode = EditPosition;
1624     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1625       initialMode = Training;
1626     } else {
1627       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1628       if( (len >= MSG_SIZ) && appData.debugMode )
1629         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631       DisplayFatalError(buf, 0, 2);
1632       return;
1633     }
1634
1635     if (appData.matchMode) {
1636         if(appData.tourneyFile[0]) { // start tourney from command line
1637             FILE *f;
1638             if(f = fopen(appData.tourneyFile, "r")) {
1639                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1640                 fclose(f);
1641                 appData.clockMode = TRUE;
1642                 SetGNUMode();
1643             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1644         }
1645         MatchEvent(TRUE);
1646     } else if (*appData.cmailGameName != NULLCHAR) {
1647         /* Set up cmail mode */
1648         ReloadCmailMsgEvent(TRUE);
1649     } else {
1650         /* Set up other modes */
1651         if (initialMode == AnalyzeFile) {
1652           if (*appData.loadGameFile == NULLCHAR) {
1653             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1654             return;
1655           }
1656         }
1657         if (*appData.loadGameFile != NULLCHAR) {
1658             (void) LoadGameFromFile(appData.loadGameFile,
1659                                     appData.loadGameIndex,
1660                                     appData.loadGameFile, TRUE);
1661         } else if (*appData.loadPositionFile != NULLCHAR) {
1662             (void) LoadPositionFromFile(appData.loadPositionFile,
1663                                         appData.loadPositionIndex,
1664                                         appData.loadPositionFile);
1665             /* [HGM] try to make self-starting even after FEN load */
1666             /* to allow automatic setup of fairy variants with wtm */
1667             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1668                 gameMode = BeginningOfGame;
1669                 setboardSpoiledMachineBlack = 1;
1670             }
1671             /* [HGM] loadPos: make that every new game uses the setup */
1672             /* from file as long as we do not switch variant          */
1673             if(!blackPlaysFirst) {
1674                 startedFromPositionFile = TRUE;
1675                 CopyBoard(filePosition, boards[0]);
1676             }
1677         }
1678         if (initialMode == AnalyzeMode) {
1679           if (appData.noChessProgram) {
1680             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1681             return;
1682           }
1683           if (appData.icsActive) {
1684             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1685             return;
1686           }
1687           AnalyzeModeEvent();
1688         } else if (initialMode == AnalyzeFile) {
1689           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1690           ShowThinkingEvent();
1691           AnalyzeFileEvent();
1692           AnalysisPeriodicEvent(1);
1693         } else if (initialMode == MachinePlaysWhite) {
1694           if (appData.noChessProgram) {
1695             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1696                               0, 2);
1697             return;
1698           }
1699           if (appData.icsActive) {
1700             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1701                               0, 2);
1702             return;
1703           }
1704           MachineWhiteEvent();
1705         } else if (initialMode == MachinePlaysBlack) {
1706           if (appData.noChessProgram) {
1707             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1708                               0, 2);
1709             return;
1710           }
1711           if (appData.icsActive) {
1712             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1713                               0, 2);
1714             return;
1715           }
1716           MachineBlackEvent();
1717         } else if (initialMode == TwoMachinesPlay) {
1718           if (appData.noChessProgram) {
1719             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1720                               0, 2);
1721             return;
1722           }
1723           if (appData.icsActive) {
1724             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1725                               0, 2);
1726             return;
1727           }
1728           TwoMachinesEvent();
1729         } else if (initialMode == EditGame) {
1730           EditGameEvent();
1731         } else if (initialMode == EditPosition) {
1732           EditPositionEvent();
1733         } else if (initialMode == Training) {
1734           if (*appData.loadGameFile == NULLCHAR) {
1735             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1736             return;
1737           }
1738           TrainingEvent();
1739         }
1740     }
1741 }
1742
1743 void
1744 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1745 {
1746     DisplayBook(current+1);
1747
1748     MoveHistorySet( movelist, first, last, current, pvInfoList );
1749
1750     EvalGraphSet( first, last, current, pvInfoList );
1751
1752     MakeEngineOutputTitle();
1753 }
1754
1755 /*
1756  * Establish will establish a contact to a remote host.port.
1757  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1758  *  used to talk to the host.
1759  * Returns 0 if okay, error code if not.
1760  */
1761 int
1762 establish ()
1763 {
1764     char buf[MSG_SIZ];
1765
1766     if (*appData.icsCommPort != NULLCHAR) {
1767         /* Talk to the host through a serial comm port */
1768         return OpenCommPort(appData.icsCommPort, &icsPR);
1769
1770     } else if (*appData.gateway != NULLCHAR) {
1771         if (*appData.remoteShell == NULLCHAR) {
1772             /* Use the rcmd protocol to run telnet program on a gateway host */
1773             snprintf(buf, sizeof(buf), "%s %s %s",
1774                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1775             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1776
1777         } else {
1778             /* Use the rsh program to run telnet program on a gateway host */
1779             if (*appData.remoteUser == NULLCHAR) {
1780                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1781                         appData.gateway, appData.telnetProgram,
1782                         appData.icsHost, appData.icsPort);
1783             } else {
1784                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1785                         appData.remoteShell, appData.gateway,
1786                         appData.remoteUser, appData.telnetProgram,
1787                         appData.icsHost, appData.icsPort);
1788             }
1789             return StartChildProcess(buf, "", &icsPR);
1790
1791         }
1792     } else if (appData.useTelnet) {
1793         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1794
1795     } else {
1796         /* TCP socket interface differs somewhat between
1797            Unix and NT; handle details in the front end.
1798            */
1799         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1800     }
1801 }
1802
1803 void
1804 EscapeExpand (char *p, char *q)
1805 {       // [HGM] initstring: routine to shape up string arguments
1806         while(*p++ = *q++) if(p[-1] == '\\')
1807             switch(*q++) {
1808                 case 'n': p[-1] = '\n'; break;
1809                 case 'r': p[-1] = '\r'; break;
1810                 case 't': p[-1] = '\t'; break;
1811                 case '\\': p[-1] = '\\'; break;
1812                 case 0: *p = 0; return;
1813                 default: p[-1] = q[-1]; break;
1814             }
1815 }
1816
1817 void
1818 show_bytes (FILE *fp, char *buf, int count)
1819 {
1820     while (count--) {
1821         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1822             fprintf(fp, "\\%03o", *buf & 0xff);
1823         } else {
1824             putc(*buf, fp);
1825         }
1826         buf++;
1827     }
1828     fflush(fp);
1829 }
1830
1831 /* Returns an errno value */
1832 int
1833 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1834 {
1835     char buf[8192], *p, *q, *buflim;
1836     int left, newcount, outcount;
1837
1838     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1839         *appData.gateway != NULLCHAR) {
1840         if (appData.debugMode) {
1841             fprintf(debugFP, ">ICS: ");
1842             show_bytes(debugFP, message, count);
1843             fprintf(debugFP, "\n");
1844         }
1845         return OutputToProcess(pr, message, count, outError);
1846     }
1847
1848     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1849     p = message;
1850     q = buf;
1851     left = count;
1852     newcount = 0;
1853     while (left) {
1854         if (q >= buflim) {
1855             if (appData.debugMode) {
1856                 fprintf(debugFP, ">ICS: ");
1857                 show_bytes(debugFP, buf, newcount);
1858                 fprintf(debugFP, "\n");
1859             }
1860             outcount = OutputToProcess(pr, buf, newcount, outError);
1861             if (outcount < newcount) return -1; /* to be sure */
1862             q = buf;
1863             newcount = 0;
1864         }
1865         if (*p == '\n') {
1866             *q++ = '\r';
1867             newcount++;
1868         } else if (((unsigned char) *p) == TN_IAC) {
1869             *q++ = (char) TN_IAC;
1870             newcount ++;
1871         }
1872         *q++ = *p++;
1873         newcount++;
1874         left--;
1875     }
1876     if (appData.debugMode) {
1877         fprintf(debugFP, ">ICS: ");
1878         show_bytes(debugFP, buf, newcount);
1879         fprintf(debugFP, "\n");
1880     }
1881     outcount = OutputToProcess(pr, buf, newcount, outError);
1882     if (outcount < newcount) return -1; /* to be sure */
1883     return count;
1884 }
1885
1886 void
1887 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1888 {
1889     int outError, outCount;
1890     static int gotEof = 0;
1891     static FILE *ini;
1892
1893     /* Pass data read from player on to ICS */
1894     if (count > 0) {
1895         gotEof = 0;
1896         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1897         if (outCount < count) {
1898             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899         }
1900         if(have_sent_ICS_logon == 2) {
1901           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1902             fprintf(ini, "%s", message);
1903             have_sent_ICS_logon = 3;
1904           } else
1905             have_sent_ICS_logon = 1;
1906         } else if(have_sent_ICS_logon == 3) {
1907             fprintf(ini, "%s", message);
1908             fclose(ini);
1909           have_sent_ICS_logon = 1;
1910         }
1911     } else if (count < 0) {
1912         RemoveInputSource(isr);
1913         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1914     } else if (gotEof++ > 0) {
1915         RemoveInputSource(isr);
1916         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1917     }
1918 }
1919
1920 void
1921 KeepAlive ()
1922 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1923     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1924     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1925     SendToICS("date\n");
1926     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1927 }
1928
1929 /* added routine for printf style output to ics */
1930 void
1931 ics_printf (char *format, ...)
1932 {
1933     char buffer[MSG_SIZ];
1934     va_list args;
1935
1936     va_start(args, format);
1937     vsnprintf(buffer, sizeof(buffer), format, args);
1938     buffer[sizeof(buffer)-1] = '\0';
1939     SendToICS(buffer);
1940     va_end(args);
1941 }
1942
1943 void
1944 SendToICS (char *s)
1945 {
1946     int count, outCount, outError;
1947
1948     if (icsPR == NoProc) return;
1949
1950     count = strlen(s);
1951     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1952     if (outCount < count) {
1953         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954     }
1955 }
1956
1957 /* This is used for sending logon scripts to the ICS. Sending
1958    without a delay causes problems when using timestamp on ICC
1959    (at least on my machine). */
1960 void
1961 SendToICSDelayed (char *s, long msdelay)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     if (appData.debugMode) {
1969         fprintf(debugFP, ">ICS: ");
1970         show_bytes(debugFP, s, count);
1971         fprintf(debugFP, "\n");
1972     }
1973     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1974                                       msdelay);
1975     if (outCount < count) {
1976         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1977     }
1978 }
1979
1980
1981 /* Remove all highlighting escape sequences in s
1982    Also deletes any suffix starting with '('
1983    */
1984 char *
1985 StripHighlightAndTitle (char *s)
1986 {
1987     static char retbuf[MSG_SIZ];
1988     char *p = retbuf;
1989
1990     while (*s != NULLCHAR) {
1991         while (*s == '\033') {
1992             while (*s != NULLCHAR && !isalpha(*s)) s++;
1993             if (*s != NULLCHAR) s++;
1994         }
1995         while (*s != NULLCHAR && *s != '\033') {
1996             if (*s == '(' || *s == '[') {
1997                 *p = NULLCHAR;
1998                 return retbuf;
1999             }
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 /* Remove all highlighting escape sequences in s */
2008 char *
2009 StripHighlight (char *s)
2010 {
2011     static char retbuf[MSG_SIZ];
2012     char *p = retbuf;
2013
2014     while (*s != NULLCHAR) {
2015         while (*s == '\033') {
2016             while (*s != NULLCHAR && !isalpha(*s)) s++;
2017             if (*s != NULLCHAR) s++;
2018         }
2019         while (*s != NULLCHAR && *s != '\033') {
2020             *p++ = *s++;
2021         }
2022     }
2023     *p = NULLCHAR;
2024     return retbuf;
2025 }
2026
2027 char engineVariant[MSG_SIZ];
2028 char *variantNames[] = VARIANT_NAMES;
2029 char *
2030 VariantName (VariantClass v)
2031 {
2032     if(v == VariantUnknown || *engineVariant) return engineVariant;
2033     return variantNames[v];
2034 }
2035
2036
2037 /* Identify a variant from the strings the chess servers use or the
2038    PGN Variant tag names we use. */
2039 VariantClass
2040 StringToVariant (char *e)
2041 {
2042     char *p;
2043     int wnum = -1;
2044     VariantClass v = VariantNormal;
2045     int i, found = FALSE;
2046     char buf[MSG_SIZ];
2047     int len;
2048
2049     if (!e) return v;
2050
2051     /* [HGM] skip over optional board-size prefixes */
2052     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2053         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2054         while( *e++ != '_');
2055     }
2056
2057     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2058         v = VariantNormal;
2059         found = TRUE;
2060     } else
2061     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2062       if (StrCaseStr(e, variantNames[i])) {
2063         v = (VariantClass) i;
2064         found = TRUE;
2065         break;
2066       }
2067     }
2068
2069     if (!found) {
2070       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2071           || StrCaseStr(e, "wild/fr")
2072           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2073         v = VariantFischeRandom;
2074       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2075                  (i = 1, p = StrCaseStr(e, "w"))) {
2076         p += i;
2077         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2078         if (isdigit(*p)) {
2079           wnum = atoi(p);
2080         } else {
2081           wnum = -1;
2082         }
2083         switch (wnum) {
2084         case 0: /* FICS only, actually */
2085         case 1:
2086           /* Castling legal even if K starts on d-file */
2087           v = VariantWildCastle;
2088           break;
2089         case 2:
2090         case 3:
2091         case 4:
2092           /* Castling illegal even if K & R happen to start in
2093              normal positions. */
2094           v = VariantNoCastle;
2095           break;
2096         case 5:
2097         case 7:
2098         case 8:
2099         case 10:
2100         case 11:
2101         case 12:
2102         case 13:
2103         case 14:
2104         case 15:
2105         case 18:
2106         case 19:
2107           /* Castling legal iff K & R start in normal positions */
2108           v = VariantNormal;
2109           break;
2110         case 6:
2111         case 20:
2112         case 21:
2113           /* Special wilds for position setup; unclear what to do here */
2114           v = VariantLoadable;
2115           break;
2116         case 9:
2117           /* Bizarre ICC game */
2118           v = VariantTwoKings;
2119           break;
2120         case 16:
2121           v = VariantKriegspiel;
2122           break;
2123         case 17:
2124           v = VariantLosers;
2125           break;
2126         case 22:
2127           v = VariantFischeRandom;
2128           break;
2129         case 23:
2130           v = VariantCrazyhouse;
2131           break;
2132         case 24:
2133           v = VariantBughouse;
2134           break;
2135         case 25:
2136           v = Variant3Check;
2137           break;
2138         case 26:
2139           /* Not quite the same as FICS suicide! */
2140           v = VariantGiveaway;
2141           break;
2142         case 27:
2143           v = VariantAtomic;
2144           break;
2145         case 28:
2146           v = VariantShatranj;
2147           break;
2148
2149         /* Temporary names for future ICC types.  The name *will* change in
2150            the next xboard/WinBoard release after ICC defines it. */
2151         case 29:
2152           v = Variant29;
2153           break;
2154         case 30:
2155           v = Variant30;
2156           break;
2157         case 31:
2158           v = Variant31;
2159           break;
2160         case 32:
2161           v = Variant32;
2162           break;
2163         case 33:
2164           v = Variant33;
2165           break;
2166         case 34:
2167           v = Variant34;
2168           break;
2169         case 35:
2170           v = Variant35;
2171           break;
2172         case 36:
2173           v = Variant36;
2174           break;
2175         case 37:
2176           v = VariantShogi;
2177           break;
2178         case 38:
2179           v = VariantXiangqi;
2180           break;
2181         case 39:
2182           v = VariantCourier;
2183           break;
2184         case 40:
2185           v = VariantGothic;
2186           break;
2187         case 41:
2188           v = VariantCapablanca;
2189           break;
2190         case 42:
2191           v = VariantKnightmate;
2192           break;
2193         case 43:
2194           v = VariantFairy;
2195           break;
2196         case 44:
2197           v = VariantCylinder;
2198           break;
2199         case 45:
2200           v = VariantFalcon;
2201           break;
2202         case 46:
2203           v = VariantCapaRandom;
2204           break;
2205         case 47:
2206           v = VariantBerolina;
2207           break;
2208         case 48:
2209           v = VariantJanus;
2210           break;
2211         case 49:
2212           v = VariantSuper;
2213           break;
2214         case 50:
2215           v = VariantGreat;
2216           break;
2217         case -1:
2218           /* Found "wild" or "w" in the string but no number;
2219              must assume it's normal chess. */
2220           v = VariantNormal;
2221           break;
2222         default:
2223           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2224           if( (len >= MSG_SIZ) && appData.debugMode )
2225             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2226
2227           DisplayError(buf, 0);
2228           v = VariantUnknown;
2229           break;
2230         }
2231       }
2232     }
2233     if (appData.debugMode) {
2234       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2235               e, wnum, VariantName(v));
2236     }
2237     return v;
2238 }
2239
2240 static int leftover_start = 0, leftover_len = 0;
2241 char star_match[STAR_MATCH_N][MSG_SIZ];
2242
2243 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2244    advance *index beyond it, and set leftover_start to the new value of
2245    *index; else return FALSE.  If pattern contains the character '*', it
2246    matches any sequence of characters not containing '\r', '\n', or the
2247    character following the '*' (if any), and the matched sequence(s) are
2248    copied into star_match.
2249    */
2250 int
2251 looking_at ( char *buf, int *index, char *pattern)
2252 {
2253     char *bufp = &buf[*index], *patternp = pattern;
2254     int star_count = 0;
2255     char *matchp = star_match[0];
2256
2257     for (;;) {
2258         if (*patternp == NULLCHAR) {
2259             *index = leftover_start = bufp - buf;
2260             *matchp = NULLCHAR;
2261             return TRUE;
2262         }
2263         if (*bufp == NULLCHAR) return FALSE;
2264         if (*patternp == '*') {
2265             if (*bufp == *(patternp + 1)) {
2266                 *matchp = NULLCHAR;
2267                 matchp = star_match[++star_count];
2268                 patternp += 2;
2269                 bufp++;
2270                 continue;
2271             } else if (*bufp == '\n' || *bufp == '\r') {
2272                 patternp++;
2273                 if (*patternp == NULLCHAR)
2274                   continue;
2275                 else
2276                   return FALSE;
2277             } else {
2278                 *matchp++ = *bufp++;
2279                 continue;
2280             }
2281         }
2282         if (*patternp != *bufp) return FALSE;
2283         patternp++;
2284         bufp++;
2285     }
2286 }
2287
2288 void
2289 SendToPlayer (char *data, int length)
2290 {
2291     int error, outCount;
2292     outCount = OutputToProcess(NoProc, data, length, &error);
2293     if (outCount < length) {
2294         DisplayFatalError(_("Error writing to display"), error, 1);
2295     }
2296 }
2297
2298 void
2299 PackHolding (char packed[], char *holding)
2300 {
2301     char *p = holding;
2302     char *q = packed;
2303     int runlength = 0;
2304     int curr = 9999;
2305     do {
2306         if (*p == curr) {
2307             runlength++;
2308         } else {
2309             switch (runlength) {
2310               case 0:
2311                 break;
2312               case 1:
2313                 *q++ = curr;
2314                 break;
2315               case 2:
2316                 *q++ = curr;
2317                 *q++ = curr;
2318                 break;
2319               default:
2320                 sprintf(q, "%d", runlength);
2321                 while (*q) q++;
2322                 *q++ = curr;
2323                 break;
2324             }
2325             runlength = 1;
2326             curr = *p;
2327         }
2328     } while (*p++);
2329     *q = NULLCHAR;
2330 }
2331
2332 /* Telnet protocol requests from the front end */
2333 void
2334 TelnetRequest (unsigned char ddww, unsigned char option)
2335 {
2336     unsigned char msg[3];
2337     int outCount, outError;
2338
2339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2340
2341     if (appData.debugMode) {
2342         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2343         switch (ddww) {
2344           case TN_DO:
2345             ddwwStr = "DO";
2346             break;
2347           case TN_DONT:
2348             ddwwStr = "DONT";
2349             break;
2350           case TN_WILL:
2351             ddwwStr = "WILL";
2352             break;
2353           case TN_WONT:
2354             ddwwStr = "WONT";
2355             break;
2356           default:
2357             ddwwStr = buf1;
2358             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2359             break;
2360         }
2361         switch (option) {
2362           case TN_ECHO:
2363             optionStr = "ECHO";
2364             break;
2365           default:
2366             optionStr = buf2;
2367             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2368             break;
2369         }
2370         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2371     }
2372     msg[0] = TN_IAC;
2373     msg[1] = ddww;
2374     msg[2] = option;
2375     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2376     if (outCount < 3) {
2377         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2378     }
2379 }
2380
2381 void
2382 DoEcho ()
2383 {
2384     if (!appData.icsActive) return;
2385     TelnetRequest(TN_DO, TN_ECHO);
2386 }
2387
2388 void
2389 DontEcho ()
2390 {
2391     if (!appData.icsActive) return;
2392     TelnetRequest(TN_DONT, TN_ECHO);
2393 }
2394
2395 void
2396 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2397 {
2398     /* put the holdings sent to us by the server on the board holdings area */
2399     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2400     char p;
2401     ChessSquare piece;
2402
2403     if(gameInfo.holdingsWidth < 2)  return;
2404     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2405         return; // prevent overwriting by pre-board holdings
2406
2407     if( (int)lowestPiece >= BlackPawn ) {
2408         holdingsColumn = 0;
2409         countsColumn = 1;
2410         holdingsStartRow = BOARD_HEIGHT-1;
2411         direction = -1;
2412     } else {
2413         holdingsColumn = BOARD_WIDTH-1;
2414         countsColumn = BOARD_WIDTH-2;
2415         holdingsStartRow = 0;
2416         direction = 1;
2417     }
2418
2419     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2420         board[i][holdingsColumn] = EmptySquare;
2421         board[i][countsColumn]   = (ChessSquare) 0;
2422     }
2423     while( (p=*holdings++) != NULLCHAR ) {
2424         piece = CharToPiece( ToUpper(p) );
2425         if(piece == EmptySquare) continue;
2426         /*j = (int) piece - (int) WhitePawn;*/
2427         j = PieceToNumber(piece);
2428         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2429         if(j < 0) continue;               /* should not happen */
2430         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2431         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2432         board[holdingsStartRow+j*direction][countsColumn]++;
2433     }
2434 }
2435
2436
2437 void
2438 VariantSwitch (Board board, VariantClass newVariant)
2439 {
2440    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2441    static Board oldBoard;
2442
2443    startedFromPositionFile = FALSE;
2444    if(gameInfo.variant == newVariant) return;
2445
2446    /* [HGM] This routine is called each time an assignment is made to
2447     * gameInfo.variant during a game, to make sure the board sizes
2448     * are set to match the new variant. If that means adding or deleting
2449     * holdings, we shift the playing board accordingly
2450     * This kludge is needed because in ICS observe mode, we get boards
2451     * of an ongoing game without knowing the variant, and learn about the
2452     * latter only later. This can be because of the move list we requested,
2453     * in which case the game history is refilled from the beginning anyway,
2454     * but also when receiving holdings of a crazyhouse game. In the latter
2455     * case we want to add those holdings to the already received position.
2456     */
2457
2458
2459    if (appData.debugMode) {
2460      fprintf(debugFP, "Switch board from %s to %s\n",
2461              VariantName(gameInfo.variant), VariantName(newVariant));
2462      setbuf(debugFP, NULL);
2463    }
2464    shuffleOpenings = 0;       /* [HGM] shuffle */
2465    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2466    switch(newVariant)
2467      {
2468      case VariantShogi:
2469        newWidth = 9;  newHeight = 9;
2470        gameInfo.holdingsSize = 7;
2471      case VariantBughouse:
2472      case VariantCrazyhouse:
2473        newHoldingsWidth = 2; break;
2474      case VariantGreat:
2475        newWidth = 10;
2476      case VariantSuper:
2477        newHoldingsWidth = 2;
2478        gameInfo.holdingsSize = 8;
2479        break;
2480      case VariantGothic:
2481      case VariantCapablanca:
2482      case VariantCapaRandom:
2483        newWidth = 10;
2484      default:
2485        newHoldingsWidth = gameInfo.holdingsSize = 0;
2486      };
2487
2488    if(newWidth  != gameInfo.boardWidth  ||
2489       newHeight != gameInfo.boardHeight ||
2490       newHoldingsWidth != gameInfo.holdingsWidth ) {
2491
2492      /* shift position to new playing area, if needed */
2493      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2494        for(i=0; i<BOARD_HEIGHT; i++)
2495          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2496            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2497              board[i][j];
2498        for(i=0; i<newHeight; i++) {
2499          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2500          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2501        }
2502      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2503        for(i=0; i<BOARD_HEIGHT; i++)
2504          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2505            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506              board[i][j];
2507      }
2508      board[HOLDINGS_SET] = 0;
2509      gameInfo.boardWidth  = newWidth;
2510      gameInfo.boardHeight = newHeight;
2511      gameInfo.holdingsWidth = newHoldingsWidth;
2512      gameInfo.variant = newVariant;
2513      InitDrawingSizes(-2, 0);
2514    } else gameInfo.variant = newVariant;
2515    CopyBoard(oldBoard, board);   // remember correctly formatted board
2516      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2517    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2518 }
2519
2520 static int loggedOn = FALSE;
2521
2522 /*-- Game start info cache: --*/
2523 int gs_gamenum;
2524 char gs_kind[MSG_SIZ];
2525 static char player1Name[128] = "";
2526 static char player2Name[128] = "";
2527 static char cont_seq[] = "\n\\   ";
2528 static int player1Rating = -1;
2529 static int player2Rating = -1;
2530 /*----------------------------*/
2531
2532 ColorClass curColor = ColorNormal;
2533 int suppressKibitz = 0;
2534
2535 // [HGM] seekgraph
2536 Boolean soughtPending = FALSE;
2537 Boolean seekGraphUp;
2538 #define MAX_SEEK_ADS 200
2539 #define SQUARE 0x80
2540 char *seekAdList[MAX_SEEK_ADS];
2541 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2542 float tcList[MAX_SEEK_ADS];
2543 char colorList[MAX_SEEK_ADS];
2544 int nrOfSeekAds = 0;
2545 int minRating = 1010, maxRating = 2800;
2546 int hMargin = 10, vMargin = 20, h, w;
2547 extern int squareSize, lineGap;
2548
2549 void
2550 PlotSeekAd (int i)
2551 {
2552         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2553         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2554         if(r < minRating+100 && r >=0 ) r = minRating+100;
2555         if(r > maxRating) r = maxRating;
2556         if(tc < 1.f) tc = 1.f;
2557         if(tc > 95.f) tc = 95.f;
2558         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2559         y = ((double)r - minRating)/(maxRating - minRating)
2560             * (h-vMargin-squareSize/8-1) + vMargin;
2561         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2562         if(strstr(seekAdList[i], " u ")) color = 1;
2563         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2564            !strstr(seekAdList[i], "bullet") &&
2565            !strstr(seekAdList[i], "blitz") &&
2566            !strstr(seekAdList[i], "standard") ) color = 2;
2567         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2568         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2569 }
2570
2571 void
2572 PlotSingleSeekAd (int i)
2573 {
2574         PlotSeekAd(i);
2575 }
2576
2577 void
2578 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2579 {
2580         char buf[MSG_SIZ], *ext = "";
2581         VariantClass v = StringToVariant(type);
2582         if(strstr(type, "wild")) {
2583             ext = type + 4; // append wild number
2584             if(v == VariantFischeRandom) type = "chess960"; else
2585             if(v == VariantLoadable) type = "setup"; else
2586             type = VariantName(v);
2587         }
2588         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2589         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2590             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2591             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2592             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2593             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2594             seekNrList[nrOfSeekAds] = nr;
2595             zList[nrOfSeekAds] = 0;
2596             seekAdList[nrOfSeekAds++] = StrSave(buf);
2597             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2598         }
2599 }
2600
2601 void
2602 EraseSeekDot (int i)
2603 {
2604     int x = xList[i], y = yList[i], d=squareSize/4, k;
2605     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2606     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2607     // now replot every dot that overlapped
2608     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2609         int xx = xList[k], yy = yList[k];
2610         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2611             DrawSeekDot(xx, yy, colorList[k]);
2612     }
2613 }
2614
2615 void
2616 RemoveSeekAd (int nr)
2617 {
2618         int i;
2619         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2620             EraseSeekDot(i);
2621             if(seekAdList[i]) free(seekAdList[i]);
2622             seekAdList[i] = seekAdList[--nrOfSeekAds];
2623             seekNrList[i] = seekNrList[nrOfSeekAds];
2624             ratingList[i] = ratingList[nrOfSeekAds];
2625             colorList[i]  = colorList[nrOfSeekAds];
2626             tcList[i] = tcList[nrOfSeekAds];
2627             xList[i]  = xList[nrOfSeekAds];
2628             yList[i]  = yList[nrOfSeekAds];
2629             zList[i]  = zList[nrOfSeekAds];
2630             seekAdList[nrOfSeekAds] = NULL;
2631             break;
2632         }
2633 }
2634
2635 Boolean
2636 MatchSoughtLine (char *line)
2637 {
2638     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2639     int nr, base, inc, u=0; char dummy;
2640
2641     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2642        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2643        (u=1) &&
2644        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2645         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2646         // match: compact and save the line
2647         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2648         return TRUE;
2649     }
2650     return FALSE;
2651 }
2652
2653 int
2654 DrawSeekGraph ()
2655 {
2656     int i;
2657     if(!seekGraphUp) return FALSE;
2658     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2659     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2660
2661     DrawSeekBackground(0, 0, w, h);
2662     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2663     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2664     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2665         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2666         yy = h-1-yy;
2667         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2668         if(i%500 == 0) {
2669             char buf[MSG_SIZ];
2670             snprintf(buf, MSG_SIZ, "%d", i);
2671             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2672         }
2673     }
2674     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2675     for(i=1; i<100; i+=(i<10?1:5)) {
2676         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2677         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2678         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2679             char buf[MSG_SIZ];
2680             snprintf(buf, MSG_SIZ, "%d", i);
2681             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2682         }
2683     }
2684     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2685     return TRUE;
2686 }
2687
2688 int
2689 SeekGraphClick (ClickType click, int x, int y, int moving)
2690 {
2691     static int lastDown = 0, displayed = 0, lastSecond;
2692     if(y < 0) return FALSE;
2693     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2694         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2695         if(!seekGraphUp) return FALSE;
2696         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2697         DrawPosition(TRUE, NULL);
2698         return TRUE;
2699     }
2700     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2701         if(click == Release || moving) return FALSE;
2702         nrOfSeekAds = 0;
2703         soughtPending = TRUE;
2704         SendToICS(ics_prefix);
2705         SendToICS("sought\n"); // should this be "sought all"?
2706     } else { // issue challenge based on clicked ad
2707         int dist = 10000; int i, closest = 0, second = 0;
2708         for(i=0; i<nrOfSeekAds; i++) {
2709             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2710             if(d < dist) { dist = d; closest = i; }
2711             second += (d - zList[i] < 120); // count in-range ads
2712             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2713         }
2714         if(dist < 120) {
2715             char buf[MSG_SIZ];
2716             second = (second > 1);
2717             if(displayed != closest || second != lastSecond) {
2718                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2719                 lastSecond = second; displayed = closest;
2720             }
2721             if(click == Press) {
2722                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2723                 lastDown = closest;
2724                 return TRUE;
2725             } // on press 'hit', only show info
2726             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2727             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2728             SendToICS(ics_prefix);
2729             SendToICS(buf);
2730             return TRUE; // let incoming board of started game pop down the graph
2731         } else if(click == Release) { // release 'miss' is ignored
2732             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2733             if(moving == 2) { // right up-click
2734                 nrOfSeekAds = 0; // refresh graph
2735                 soughtPending = TRUE;
2736                 SendToICS(ics_prefix);
2737                 SendToICS("sought\n"); // should this be "sought all"?
2738             }
2739             return TRUE;
2740         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2741         // press miss or release hit 'pop down' seek graph
2742         seekGraphUp = FALSE;
2743         DrawPosition(TRUE, NULL);
2744     }
2745     return TRUE;
2746 }
2747
2748 void
2749 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2750 {
2751 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2752 #define STARTED_NONE 0
2753 #define STARTED_MOVES 1
2754 #define STARTED_BOARD 2
2755 #define STARTED_OBSERVE 3
2756 #define STARTED_HOLDINGS 4
2757 #define STARTED_CHATTER 5
2758 #define STARTED_COMMENT 6
2759 #define STARTED_MOVES_NOHIDE 7
2760
2761     static int started = STARTED_NONE;
2762     static char parse[20000];
2763     static int parse_pos = 0;
2764     static char buf[BUF_SIZE + 1];
2765     static int firstTime = TRUE, intfSet = FALSE;
2766     static ColorClass prevColor = ColorNormal;
2767     static int savingComment = FALSE;
2768     static int cmatch = 0; // continuation sequence match
2769     char *bp;
2770     char str[MSG_SIZ];
2771     int i, oldi;
2772     int buf_len;
2773     int next_out;
2774     int tkind;
2775     int backup;    /* [DM] For zippy color lines */
2776     char *p;
2777     char talker[MSG_SIZ]; // [HGM] chat
2778     int channel;
2779
2780     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2781
2782     if (appData.debugMode) {
2783       if (!error) {
2784         fprintf(debugFP, "<ICS: ");
2785         show_bytes(debugFP, data, count);
2786         fprintf(debugFP, "\n");
2787       }
2788     }
2789
2790     if (appData.debugMode) { int f = forwardMostMove;
2791         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2792                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2793                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2794     }
2795     if (count > 0) {
2796         /* If last read ended with a partial line that we couldn't parse,
2797            prepend it to the new read and try again. */
2798         if (leftover_len > 0) {
2799             for (i=0; i<leftover_len; i++)
2800               buf[i] = buf[leftover_start + i];
2801         }
2802
2803     /* copy new characters into the buffer */
2804     bp = buf + leftover_len;
2805     buf_len=leftover_len;
2806     for (i=0; i<count; i++)
2807     {
2808         // ignore these
2809         if (data[i] == '\r')
2810             continue;
2811
2812         // join lines split by ICS?
2813         if (!appData.noJoin)
2814         {
2815             /*
2816                 Joining just consists of finding matches against the
2817                 continuation sequence, and discarding that sequence
2818                 if found instead of copying it.  So, until a match
2819                 fails, there's nothing to do since it might be the
2820                 complete sequence, and thus, something we don't want
2821                 copied.
2822             */
2823             if (data[i] == cont_seq[cmatch])
2824             {
2825                 cmatch++;
2826                 if (cmatch == strlen(cont_seq))
2827                 {
2828                     cmatch = 0; // complete match.  just reset the counter
2829
2830                     /*
2831                         it's possible for the ICS to not include the space
2832                         at the end of the last word, making our [correct]
2833                         join operation fuse two separate words.  the server
2834                         does this when the space occurs at the width setting.
2835                     */
2836                     if (!buf_len || buf[buf_len-1] != ' ')
2837                     {
2838                         *bp++ = ' ';
2839                         buf_len++;
2840                     }
2841                 }
2842                 continue;
2843             }
2844             else if (cmatch)
2845             {
2846                 /*
2847                     match failed, so we have to copy what matched before
2848                     falling through and copying this character.  In reality,
2849                     this will only ever be just the newline character, but
2850                     it doesn't hurt to be precise.
2851                 */
2852                 strncpy(bp, cont_seq, cmatch);
2853                 bp += cmatch;
2854                 buf_len += cmatch;
2855                 cmatch = 0;
2856             }
2857         }
2858
2859         // copy this char
2860         *bp++ = data[i];
2861         buf_len++;
2862     }
2863
2864         buf[buf_len] = NULLCHAR;
2865 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2866         next_out = 0;
2867         leftover_start = 0;
2868
2869         i = 0;
2870         while (i < buf_len) {
2871             /* Deal with part of the TELNET option negotiation
2872                protocol.  We refuse to do anything beyond the
2873                defaults, except that we allow the WILL ECHO option,
2874                which ICS uses to turn off password echoing when we are
2875                directly connected to it.  We reject this option
2876                if localLineEditing mode is on (always on in xboard)
2877                and we are talking to port 23, which might be a real
2878                telnet server that will try to keep WILL ECHO on permanently.
2879              */
2880             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2881                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2882                 unsigned char option;
2883                 oldi = i;
2884                 switch ((unsigned char) buf[++i]) {
2885                   case TN_WILL:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<WILL ");
2888                     switch (option = (unsigned char) buf[++i]) {
2889                       case TN_ECHO:
2890                         if (appData.debugMode)
2891                           fprintf(debugFP, "ECHO ");
2892                         /* Reply only if this is a change, according
2893                            to the protocol rules. */
2894                         if (remoteEchoOption) break;
2895                         if (appData.localLineEditing &&
2896                             atoi(appData.icsPort) == TN_PORT) {
2897                             TelnetRequest(TN_DONT, TN_ECHO);
2898                         } else {
2899                             EchoOff();
2900                             TelnetRequest(TN_DO, TN_ECHO);
2901                             remoteEchoOption = TRUE;
2902                         }
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", option);
2907                         /* Whatever this is, we don't want it. */
2908                         TelnetRequest(TN_DONT, option);
2909                         break;
2910                     }
2911                     break;
2912                   case TN_WONT:
2913                     if (appData.debugMode)
2914                       fprintf(debugFP, "\n<WONT ");
2915                     switch (option = (unsigned char) buf[++i]) {
2916                       case TN_ECHO:
2917                         if (appData.debugMode)
2918                           fprintf(debugFP, "ECHO ");
2919                         /* Reply only if this is a change, according
2920                            to the protocol rules. */
2921                         if (!remoteEchoOption) break;
2922                         EchoOn();
2923                         TelnetRequest(TN_DONT, TN_ECHO);
2924                         remoteEchoOption = FALSE;
2925                         break;
2926                       default:
2927                         if (appData.debugMode)
2928                           fprintf(debugFP, "%d ", (unsigned char) option);
2929                         /* Whatever this is, it must already be turned
2930                            off, because we never agree to turn on
2931                            anything non-default, so according to the
2932                            protocol rules, we don't reply. */
2933                         break;
2934                     }
2935                     break;
2936                   case TN_DO:
2937                     if (appData.debugMode)
2938                       fprintf(debugFP, "\n<DO ");
2939                     switch (option = (unsigned char) buf[++i]) {
2940                       default:
2941                         /* Whatever this is, we refuse to do it. */
2942                         if (appData.debugMode)
2943                           fprintf(debugFP, "%d ", option);
2944                         TelnetRequest(TN_WONT, option);
2945                         break;
2946                     }
2947                     break;
2948                   case TN_DONT:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<DONT ");
2951                     switch (option = (unsigned char) buf[++i]) {
2952                       default:
2953                         if (appData.debugMode)
2954                           fprintf(debugFP, "%d ", option);
2955                         /* Whatever this is, we are already not doing
2956                            it, because we never agree to do anything
2957                            non-default, so according to the protocol
2958                            rules, we don't reply. */
2959                         break;
2960                     }
2961                     break;
2962                   case TN_IAC:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<IAC ");
2965                     /* Doubled IAC; pass it through */
2966                     i--;
2967                     break;
2968                   default:
2969                     if (appData.debugMode)
2970                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2971                     /* Drop all other telnet commands on the floor */
2972                     break;
2973                 }
2974                 if (oldi > next_out)
2975                   SendToPlayer(&buf[next_out], oldi - next_out);
2976                 if (++i > next_out)
2977                   next_out = i;
2978                 continue;
2979             }
2980
2981             /* OK, this at least will *usually* work */
2982             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2983                 loggedOn = TRUE;
2984             }
2985
2986             if (loggedOn && !intfSet) {
2987                 if (ics_type == ICS_ICC) {
2988                   snprintf(str, MSG_SIZ,
2989                           "/set-quietly interface %s\n/set-quietly style 12\n",
2990                           programVersion);
2991                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2992                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2993                 } else if (ics_type == ICS_CHESSNET) {
2994                   snprintf(str, MSG_SIZ, "/style 12\n");
2995                 } else {
2996                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2997                   strcat(str, programVersion);
2998                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3001 #ifdef WIN32
3002                   strcat(str, "$iset nohighlight 1\n");
3003 #endif
3004                   strcat(str, "$iset lock 1\n$style 12\n");
3005                 }
3006                 SendToICS(str);
3007                 NotifyFrontendLogin();
3008                 intfSet = TRUE;
3009             }
3010
3011             if (started == STARTED_COMMENT) {
3012                 /* Accumulate characters in comment */
3013                 parse[parse_pos++] = buf[i];
3014                 if (buf[i] == '\n') {
3015                     parse[parse_pos] = NULLCHAR;
3016                     if(chattingPartner>=0) {
3017                         char mess[MSG_SIZ];
3018                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3019                         OutputChatMessage(chattingPartner, mess);
3020                         chattingPartner = -1;
3021                         next_out = i+1; // [HGM] suppress printing in ICS window
3022                     } else
3023                     if(!suppressKibitz) // [HGM] kibitz
3024                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3025                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3026                         int nrDigit = 0, nrAlph = 0, j;
3027                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3028                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3029                         parse[parse_pos] = NULLCHAR;
3030                         // try to be smart: if it does not look like search info, it should go to
3031                         // ICS interaction window after all, not to engine-output window.
3032                         for(j=0; j<parse_pos; j++) { // count letters and digits
3033                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3034                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3035                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3036                         }
3037                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3038                             int depth=0; float score;
3039                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3040                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3041                                 pvInfoList[forwardMostMove-1].depth = depth;
3042                                 pvInfoList[forwardMostMove-1].score = 100*score;
3043                             }
3044                             OutputKibitz(suppressKibitz, parse);
3045                         } else {
3046                             char tmp[MSG_SIZ];
3047                             if(gameMode == IcsObserving) // restore original ICS messages
3048                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3049                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3050                             else
3051                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3052                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3053                             SendToPlayer(tmp, strlen(tmp));
3054                         }
3055                         next_out = i+1; // [HGM] suppress printing in ICS window
3056                     }
3057                     started = STARTED_NONE;
3058                 } else {
3059                     /* Don't match patterns against characters in comment */
3060                     i++;
3061                     continue;
3062                 }
3063             }
3064             if (started == STARTED_CHATTER) {
3065                 if (buf[i] != '\n') {
3066                     /* Don't match patterns against characters in chatter */
3067                     i++;
3068                     continue;
3069                 }
3070                 started = STARTED_NONE;
3071                 if(suppressKibitz) next_out = i+1;
3072             }
3073
3074             /* Kludge to deal with rcmd protocol */
3075             if (firstTime && looking_at(buf, &i, "\001*")) {
3076                 DisplayFatalError(&buf[1], 0, 1);
3077                 continue;
3078             } else {
3079                 firstTime = FALSE;
3080             }
3081
3082             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3083                 ics_type = ICS_ICC;
3084                 ics_prefix = "/";
3085                 if (appData.debugMode)
3086                   fprintf(debugFP, "ics_type %d\n", ics_type);
3087                 continue;
3088             }
3089             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3090                 ics_type = ICS_FICS;
3091                 ics_prefix = "$";
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, "ics_type %d\n", ics_type);
3094                 continue;
3095             }
3096             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3097                 ics_type = ICS_CHESSNET;
3098                 ics_prefix = "/";
3099                 if (appData.debugMode)
3100                   fprintf(debugFP, "ics_type %d\n", ics_type);
3101                 continue;
3102             }
3103
3104             if (!loggedOn &&
3105                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3106                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3107                  looking_at(buf, &i, "will be \"*\""))) {
3108               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3109               continue;
3110             }
3111
3112             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3113               char buf[MSG_SIZ];
3114               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3115               DisplayIcsInteractionTitle(buf);
3116               have_set_title = TRUE;
3117             }
3118
3119             /* skip finger notes */
3120             if (started == STARTED_NONE &&
3121                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3122                  (buf[i] == '1' && buf[i+1] == '0')) &&
3123                 buf[i+2] == ':' && buf[i+3] == ' ') {
3124               started = STARTED_CHATTER;
3125               i += 3;
3126               continue;
3127             }
3128
3129             oldi = i;
3130             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3131             if(appData.seekGraph) {
3132                 if(soughtPending && MatchSoughtLine(buf+i)) {
3133                     i = strstr(buf+i, "rated") - buf;
3134                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135                     next_out = leftover_start = i;
3136                     started = STARTED_CHATTER;
3137                     suppressKibitz = TRUE;
3138                     continue;
3139                 }
3140                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3141                         && looking_at(buf, &i, "* ads displayed")) {
3142                     soughtPending = FALSE;
3143                     seekGraphUp = TRUE;
3144                     DrawSeekGraph();
3145                     continue;
3146                 }
3147                 if(appData.autoRefresh) {
3148                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3149                         int s = (ics_type == ICS_ICC); // ICC format differs
3150                         if(seekGraphUp)
3151                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3152                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3153                         looking_at(buf, &i, "*% "); // eat prompt
3154                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3155                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = i; // suppress
3157                         continue;
3158                     }
3159                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3160                         char *p = star_match[0];
3161                         while(*p) {
3162                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3163                             while(*p && *p++ != ' '); // next
3164                         }
3165                         looking_at(buf, &i, "*% "); // eat prompt
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         continue;
3169                     }
3170                 }
3171             }
3172
3173             /* skip formula vars */
3174             if (started == STARTED_NONE &&
3175                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3176               started = STARTED_CHATTER;
3177               i += 3;
3178               continue;
3179             }
3180
3181             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3182             if (appData.autoKibitz && started == STARTED_NONE &&
3183                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3184                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3185                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3186                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3187                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3188                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3189                         suppressKibitz = TRUE;
3190                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                         next_out = i;
3192                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3193                                 && (gameMode == IcsPlayingWhite)) ||
3194                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3195                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3196                             started = STARTED_CHATTER; // own kibitz we simply discard
3197                         else {
3198                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3199                             parse_pos = 0; parse[0] = NULLCHAR;
3200                             savingComment = TRUE;
3201                             suppressKibitz = gameMode != IcsObserving ? 2 :
3202                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3203                         }
3204                         continue;
3205                 } else
3206                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3207                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3208                          && atoi(star_match[0])) {
3209                     // suppress the acknowledgements of our own autoKibitz
3210                     char *p;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3213                     SendToPlayer(star_match[0], strlen(star_match[0]));
3214                     if(looking_at(buf, &i, "*% ")) // eat prompt
3215                         suppressKibitz = FALSE;
3216                     next_out = i;
3217                     continue;
3218                 }
3219             } // [HGM] kibitz: end of patch
3220
3221             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3222
3223             // [HGM] chat: intercept tells by users for which we have an open chat window
3224             channel = -1;
3225             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3226                                            looking_at(buf, &i, "* whispers:") ||
3227                                            looking_at(buf, &i, "* kibitzes:") ||
3228                                            looking_at(buf, &i, "* shouts:") ||
3229                                            looking_at(buf, &i, "* c-shouts:") ||
3230                                            looking_at(buf, &i, "--> * ") ||
3231                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3232                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3233                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3234                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3235                 int p;
3236                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3237                 chattingPartner = -1;
3238
3239                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3240                 for(p=0; p<MAX_CHAT; p++) {
3241                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3242                     talker[0] = '['; strcat(talker, "] ");
3243                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3244                     chattingPartner = p; break;
3245                     }
3246                 } else
3247                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("kibitzes", chatPartner[p])) {
3250                         talker[0] = '['; strcat(talker, "] ");
3251                         chattingPartner = p; break;
3252                     }
3253                 } else
3254                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3255                 for(p=0; p<MAX_CHAT; p++) {
3256                     if(!strcmp("whispers", chatPartner[p])) {
3257                         talker[0] = '['; strcat(talker, "] ");
3258                         chattingPartner = p; break;
3259                     }
3260                 } else
3261                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3262                   if(buf[i-8] == '-' && buf[i-3] == 't')
3263                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3264                     if(!strcmp("c-shouts", chatPartner[p])) {
3265                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3266                         chattingPartner = p; break;
3267                     }
3268                   }
3269                   if(chattingPartner < 0)
3270                   for(p=0; p<MAX_CHAT; p++) {
3271                     if(!strcmp("shouts", chatPartner[p])) {
3272                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3273                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3274                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3275                         chattingPartner = p; break;
3276                     }
3277                   }
3278                 }
3279                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3280                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3281                     talker[0] = 0; Colorize(ColorTell, FALSE);
3282                     chattingPartner = p; break;
3283                 }
3284                 if(chattingPartner<0) i = oldi; else {
3285                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3286                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     started = STARTED_COMMENT;
3289                     parse_pos = 0; parse[0] = NULLCHAR;
3290                     savingComment = 3 + chattingPartner; // counts as TRUE
3291                     suppressKibitz = TRUE;
3292                     continue;
3293                 }
3294             } // [HGM] chat: end of patch
3295
3296           backup = i;
3297             if (appData.zippyTalk || appData.zippyPlay) {
3298                 /* [DM] Backup address for color zippy lines */
3299 #if ZIPPY
3300                if (loggedOn == TRUE)
3301                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3302                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3303 #endif
3304             } // [DM] 'else { ' deleted
3305                 if (
3306                     /* Regular tells and says */
3307                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3308                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3309                     looking_at(buf, &i, "* says: ") ||
3310                     /* Don't color "message" or "messages" output */
3311                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3312                     looking_at(buf, &i, "*. * at *:*: ") ||
3313                     looking_at(buf, &i, "--* (*:*): ") ||
3314                     /* Message notifications (same color as tells) */
3315                     looking_at(buf, &i, "* has left a message ") ||
3316                     looking_at(buf, &i, "* just sent you a message:\n") ||
3317                     /* Whispers and kibitzes */
3318                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3319                     looking_at(buf, &i, "* kibitzes: ") ||
3320                     /* Channel tells */
3321                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3322
3323                   if (tkind == 1 && strchr(star_match[0], ':')) {
3324                       /* Avoid "tells you:" spoofs in channels */
3325                      tkind = 3;
3326                   }
3327                   if (star_match[0][0] == NULLCHAR ||
3328                       strchr(star_match[0], ' ') ||
3329                       (tkind == 3 && strchr(star_match[1], ' '))) {
3330                     /* Reject bogus matches */
3331                     i = oldi;
3332                   } else {
3333                     if (appData.colorize) {
3334                       if (oldi > next_out) {
3335                         SendToPlayer(&buf[next_out], oldi - next_out);
3336                         next_out = oldi;
3337                       }
3338                       switch (tkind) {
3339                       case 1:
3340                         Colorize(ColorTell, FALSE);
3341                         curColor = ColorTell;
3342                         break;
3343                       case 2:
3344                         Colorize(ColorKibitz, FALSE);
3345                         curColor = ColorKibitz;
3346                         break;
3347                       case 3:
3348                         p = strrchr(star_match[1], '(');
3349                         if (p == NULL) {
3350                           p = star_match[1];
3351                         } else {
3352                           p++;
3353                         }
3354                         if (atoi(p) == 1) {
3355                           Colorize(ColorChannel1, FALSE);
3356                           curColor = ColorChannel1;
3357                         } else {
3358                           Colorize(ColorChannel, FALSE);
3359                           curColor = ColorChannel;
3360                         }
3361                         break;
3362                       case 5:
3363                         curColor = ColorNormal;
3364                         break;
3365                       }
3366                     }
3367                     if (started == STARTED_NONE && appData.autoComment &&
3368                         (gameMode == IcsObserving ||
3369                          gameMode == IcsPlayingWhite ||
3370                          gameMode == IcsPlayingBlack)) {
3371                       parse_pos = i - oldi;
3372                       memcpy(parse, &buf[oldi], parse_pos);
3373                       parse[parse_pos] = NULLCHAR;
3374                       started = STARTED_COMMENT;
3375                       savingComment = TRUE;
3376                     } else {
3377                       started = STARTED_CHATTER;
3378                       savingComment = FALSE;
3379                     }
3380                     loggedOn = TRUE;
3381                     continue;
3382                   }
3383                 }
3384
3385                 if (looking_at(buf, &i, "* s-shouts: ") ||
3386                     looking_at(buf, &i, "* c-shouts: ")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorSShout, FALSE);
3393                         curColor = ColorSShout;
3394                     }
3395                     loggedOn = TRUE;
3396                     started = STARTED_CHATTER;
3397                     continue;
3398                 }
3399
3400                 if (looking_at(buf, &i, "--->")) {
3401                     loggedOn = TRUE;
3402                     continue;
3403                 }
3404
3405                 if (looking_at(buf, &i, "* shouts: ") ||
3406                     looking_at(buf, &i, "--> ")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorShout, FALSE);
3413                         curColor = ColorShout;
3414                     }
3415                     loggedOn = TRUE;
3416                     started = STARTED_CHATTER;
3417                     continue;
3418                 }
3419
3420                 if (looking_at( buf, &i, "Challenge:")) {
3421                     if (appData.colorize) {
3422                         if (oldi > next_out) {
3423                             SendToPlayer(&buf[next_out], oldi - next_out);
3424                             next_out = oldi;
3425                         }
3426                         Colorize(ColorChallenge, FALSE);
3427                         curColor = ColorChallenge;
3428                     }
3429                     loggedOn = TRUE;
3430                     continue;
3431                 }
3432
3433                 if (looking_at(buf, &i, "* offers you") ||
3434                     looking_at(buf, &i, "* offers to be") ||
3435                     looking_at(buf, &i, "* would like to") ||
3436                     looking_at(buf, &i, "* requests to") ||
3437                     looking_at(buf, &i, "Your opponent offers") ||
3438                     looking_at(buf, &i, "Your opponent requests")) {
3439
3440                     if (appData.colorize) {
3441                         if (oldi > next_out) {
3442                             SendToPlayer(&buf[next_out], oldi - next_out);
3443                             next_out = oldi;
3444                         }
3445                         Colorize(ColorRequest, FALSE);
3446                         curColor = ColorRequest;
3447                     }
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* (*) seeking")) {
3452                     if (appData.colorize) {
3453                         if (oldi > next_out) {
3454                             SendToPlayer(&buf[next_out], oldi - next_out);
3455                             next_out = oldi;
3456                         }
3457                         Colorize(ColorSeek, FALSE);
3458                         curColor = ColorSeek;
3459                     }
3460                     continue;
3461             }
3462
3463           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3464
3465             if (looking_at(buf, &i, "\\   ")) {
3466                 if (prevColor != ColorNormal) {
3467                     if (oldi > next_out) {
3468                         SendToPlayer(&buf[next_out], oldi - next_out);
3469                         next_out = oldi;
3470                     }
3471                     Colorize(prevColor, TRUE);
3472                     curColor = prevColor;
3473                 }
3474                 if (savingComment) {
3475                     parse_pos = i - oldi;
3476                     memcpy(parse, &buf[oldi], parse_pos);
3477                     parse[parse_pos] = NULLCHAR;
3478                     started = STARTED_COMMENT;
3479                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3480                         chattingPartner = savingComment - 3; // kludge to remember the box
3481                 } else {
3482                     started = STARTED_CHATTER;
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i, "Black Strength :") ||
3488                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3489                 looking_at(buf, &i, "<10>") ||
3490                 looking_at(buf, &i, "#@#")) {
3491                 /* Wrong board style */
3492                 loggedOn = TRUE;
3493                 SendToICS(ics_prefix);
3494                 SendToICS("set style 12\n");
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "login:")) {
3501               if (!have_sent_ICS_logon) {
3502                 if(ICSInitScript())
3503                   have_sent_ICS_logon = 1;
3504                 else // no init script was found
3505                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3506               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3507                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3508               }
3509                 continue;
3510             }
3511
3512             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3513                 (looking_at(buf, &i, "\n<12> ") ||
3514                  looking_at(buf, &i, "<12> "))) {
3515                 loggedOn = TRUE;
3516                 if (oldi > next_out) {
3517                     SendToPlayer(&buf[next_out], oldi - next_out);
3518                 }
3519                 next_out = i;
3520                 started = STARTED_BOARD;
3521                 parse_pos = 0;
3522                 continue;
3523             }
3524
3525             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3526                 looking_at(buf, &i, "<b1> ")) {
3527                 if (oldi > next_out) {
3528                     SendToPlayer(&buf[next_out], oldi - next_out);
3529                 }
3530                 next_out = i;
3531                 started = STARTED_HOLDINGS;
3532                 parse_pos = 0;
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3537                 loggedOn = TRUE;
3538                 /* Header for a move list -- first line */
3539
3540                 switch (ics_getting_history) {
3541                   case H_FALSE:
3542                     switch (gameMode) {
3543                       case IcsIdle:
3544                       case BeginningOfGame:
3545                         /* User typed "moves" or "oldmoves" while we
3546                            were idle.  Pretend we asked for these
3547                            moves and soak them up so user can step
3548                            through them and/or save them.
3549                            */
3550                         Reset(FALSE, TRUE);
3551                         gameMode = IcsObserving;
3552                         ModeHighlight();
3553                         ics_gamenum = -1;
3554                         ics_getting_history = H_GOT_UNREQ_HEADER;
3555                         break;
3556                       case EditGame: /*?*/
3557                       case EditPosition: /*?*/
3558                         /* Should above feature work in these modes too? */
3559                         /* For now it doesn't */
3560                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3561                         break;
3562                       default:
3563                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3564                         break;
3565                     }
3566                     break;
3567                   case H_REQUESTED:
3568                     /* Is this the right one? */
3569                     if (gameInfo.white && gameInfo.black &&
3570                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3571                         strcmp(gameInfo.black, star_match[2]) == 0) {
3572                         /* All is well */
3573                         ics_getting_history = H_GOT_REQ_HEADER;
3574                     }
3575                     break;
3576                   case H_GOT_REQ_HEADER:
3577                   case H_GOT_UNREQ_HEADER:
3578                   case H_GOT_UNWANTED_HEADER:
3579                   case H_GETTING_MOVES:
3580                     /* Should not happen */
3581                     DisplayError(_("Error gathering move list: two headers"), 0);
3582                     ics_getting_history = H_FALSE;
3583                     break;
3584                 }
3585
3586                 /* Save player ratings into gameInfo if needed */
3587                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3588                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3589                     (gameInfo.whiteRating == -1 ||
3590                      gameInfo.blackRating == -1)) {
3591
3592                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3593                     gameInfo.blackRating = string_to_rating(star_match[3]);
3594                     if (appData.debugMode)
3595                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3596                               gameInfo.whiteRating, gameInfo.blackRating);
3597                 }
3598                 continue;
3599             }
3600
3601             if (looking_at(buf, &i,
3602               "* * match, initial time: * minute*, increment: * second")) {
3603                 /* Header for a move list -- second line */
3604                 /* Initial board will follow if this is a wild game */
3605                 if (gameInfo.event != NULL) free(gameInfo.event);
3606                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3607                 gameInfo.event = StrSave(str);
3608                 /* [HGM] we switched variant. Translate boards if needed. */
3609                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3610                 continue;
3611             }
3612
3613             if (looking_at(buf, &i, "Move  ")) {
3614                 /* Beginning of a move list */
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     /* Normally should not happen */
3618                     /* Maybe user hit reset while we were parsing */
3619                     break;
3620                   case H_REQUESTED:
3621                     /* Happens if we are ignoring a move list that is not
3622                      * the one we just requested.  Common if the user
3623                      * tries to observe two games without turning off
3624                      * getMoveList */
3625                     break;
3626                   case H_GETTING_MOVES:
3627                     /* Should not happen */
3628                     DisplayError(_("Error gathering move list: nested"), 0);
3629                     ics_getting_history = H_FALSE;
3630                     break;
3631                   case H_GOT_REQ_HEADER:
3632                     ics_getting_history = H_GETTING_MOVES;
3633                     started = STARTED_MOVES;
3634                     parse_pos = 0;
3635                     if (oldi > next_out) {
3636                         SendToPlayer(&buf[next_out], oldi - next_out);
3637                     }
3638                     break;
3639                   case H_GOT_UNREQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES_NOHIDE;
3642                     parse_pos = 0;
3643                     break;
3644                   case H_GOT_UNWANTED_HEADER:
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647                 }
3648                 continue;
3649             }
3650
3651             if (looking_at(buf, &i, "% ") ||
3652                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3653                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3654                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3655                     soughtPending = FALSE;
3656                     seekGraphUp = TRUE;
3657                     DrawSeekGraph();
3658                 }
3659                 if(suppressKibitz) next_out = i;
3660                 savingComment = FALSE;
3661                 suppressKibitz = 0;
3662                 switch (started) {
3663                   case STARTED_MOVES:
3664                   case STARTED_MOVES_NOHIDE:
3665                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3666                     parse[parse_pos + i - oldi] = NULLCHAR;
3667                     ParseGameHistory(parse);
3668 #if ZIPPY
3669                     if (appData.zippyPlay && first.initDone) {
3670                         FeedMovesToProgram(&first, forwardMostMove);
3671                         if (gameMode == IcsPlayingWhite) {
3672                             if (WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("black\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, TRUE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, TRUE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         } else if (gameMode == IcsPlayingBlack) {
3696                             if (!WhiteOnMove(forwardMostMove)) {
3697                                 if (first.sendTime) {
3698                                   if (first.useColors) {
3699                                     SendToProgram("white\n", &first);
3700                                   }
3701                                   SendTimeRemaining(&first, FALSE);
3702                                 }
3703                                 if (first.useColors) {
3704                                   SendToProgram("black\n", &first);
3705                                 }
3706                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3707                                 first.maybeThinking = TRUE;
3708                             } else {
3709                                 if (first.usePlayother) {
3710                                   if (first.sendTime) {
3711                                     SendTimeRemaining(&first, FALSE);
3712                                   }
3713                                   SendToProgram("playother\n", &first);
3714                                   firstMove = FALSE;
3715                                 } else {
3716                                   firstMove = TRUE;
3717                                 }
3718                             }
3719                         }
3720                     }
3721 #endif
3722                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3723                         /* Moves came from oldmoves or moves command
3724                            while we weren't doing anything else.
3725                            */
3726                         currentMove = forwardMostMove;
3727                         ClearHighlights();/*!!could figure this out*/
3728                         flipView = appData.flipView;
3729                         DrawPosition(TRUE, boards[currentMove]);
3730                         DisplayBothClocks();
3731                         snprintf(str, MSG_SIZ, "%s %s %s",
3732                                 gameInfo.white, _("vs."),  gameInfo.black);
3733                         DisplayTitle(str);
3734                         gameMode = IcsIdle;
3735                     } else {
3736                         /* Moves were history of an active game */
3737                         if (gameInfo.resultDetails != NULL) {
3738                             free(gameInfo.resultDetails);
3739                             gameInfo.resultDetails = NULL;
3740                         }
3741                     }
3742                     HistorySet(parseList, backwardMostMove,
3743                                forwardMostMove, currentMove-1);
3744                     DisplayMove(currentMove - 1);
3745                     if (started == STARTED_MOVES) next_out = i;
3746                     started = STARTED_NONE;
3747                     ics_getting_history = H_FALSE;
3748                     break;
3749
3750                   case STARTED_OBSERVE:
3751                     started = STARTED_NONE;
3752                     SendToICS(ics_prefix);
3753                     SendToICS("refresh\n");
3754                     break;
3755
3756                   default:
3757                     break;
3758                 }
3759                 if(bookHit) { // [HGM] book: simulate book reply
3760                     static char bookMove[MSG_SIZ]; // a bit generous?
3761
3762                     programStats.nodes = programStats.depth = programStats.time =
3763                     programStats.score = programStats.got_only_move = 0;
3764                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3765
3766                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3767                     strcat(bookMove, bookHit);
3768                     HandleMachineMove(bookMove, &first);
3769                 }
3770                 continue;
3771             }
3772
3773             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3774                  started == STARTED_HOLDINGS ||
3775                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3776                 /* Accumulate characters in move list or board */
3777                 parse[parse_pos++] = buf[i];
3778             }
3779
3780             /* Start of game messages.  Mostly we detect start of game
3781                when the first board image arrives.  On some versions
3782                of the ICS, though, we need to do a "refresh" after starting
3783                to observe in order to get the current board right away. */
3784             if (looking_at(buf, &i, "Adding game * to observation list")) {
3785                 started = STARTED_OBSERVE;
3786                 continue;
3787             }
3788
3789             /* Handle auto-observe */
3790             if (appData.autoObserve &&
3791                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3792                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3793                 char *player;
3794                 /* Choose the player that was highlighted, if any. */
3795                 if (star_match[0][0] == '\033' ||
3796                     star_match[1][0] != '\033') {
3797                     player = star_match[0];
3798                 } else {
3799                     player = star_match[2];
3800                 }
3801                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3802                         ics_prefix, StripHighlightAndTitle(player));
3803                 SendToICS(str);
3804
3805                 /* Save ratings from notify string */
3806                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3807                 player1Rating = string_to_rating(star_match[1]);
3808                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3809                 player2Rating = string_to_rating(star_match[3]);
3810
3811                 if (appData.debugMode)
3812                   fprintf(debugFP,
3813                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3814                           player1Name, player1Rating,
3815                           player2Name, player2Rating);
3816
3817                 continue;
3818             }
3819
3820             /* Deal with automatic examine mode after a game,
3821                and with IcsObserving -> IcsExamining transition */
3822             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3823                 looking_at(buf, &i, "has made you an examiner of game *")) {
3824
3825                 int gamenum = atoi(star_match[0]);
3826                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3827                     gamenum == ics_gamenum) {
3828                     /* We were already playing or observing this game;
3829                        no need to refetch history */
3830                     gameMode = IcsExamining;
3831                     if (pausing) {
3832                         pauseExamForwardMostMove = forwardMostMove;
3833                     } else if (currentMove < forwardMostMove) {
3834                         ForwardInner(forwardMostMove);
3835                     }
3836                 } else {
3837                     /* I don't think this case really can happen */
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                 }
3841                 continue;
3842             }
3843
3844             /* Error messages */
3845 //          if (ics_user_moved) {
3846             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3847                 if (looking_at(buf, &i, "Illegal move") ||
3848                     looking_at(buf, &i, "Not a legal move") ||
3849                     looking_at(buf, &i, "Your king is in check") ||
3850                     looking_at(buf, &i, "It isn't your turn") ||
3851                     looking_at(buf, &i, "It is not your move")) {
3852                     /* Illegal move */
3853                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3854                         currentMove = forwardMostMove-1;
3855                         DisplayMove(currentMove - 1); /* before DMError */
3856                         DrawPosition(FALSE, boards[currentMove]);
3857                         SwitchClocks(forwardMostMove-1); // [HGM] race
3858                         DisplayBothClocks();
3859                     }
3860                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3861                     ics_user_moved = 0;
3862                     continue;
3863                 }
3864             }
3865
3866             if (looking_at(buf, &i, "still have time") ||
3867                 looking_at(buf, &i, "not out of time") ||
3868                 looking_at(buf, &i, "either player is out of time") ||
3869                 looking_at(buf, &i, "has timeseal; checking")) {
3870                 /* We must have called his flag a little too soon */
3871                 whiteFlag = blackFlag = FALSE;
3872                 continue;
3873             }
3874
3875             if (looking_at(buf, &i, "added * seconds to") ||
3876                 looking_at(buf, &i, "seconds were added to")) {
3877                 /* Update the clocks */
3878                 SendToICS(ics_prefix);
3879                 SendToICS("refresh\n");
3880                 continue;
3881             }
3882
3883             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3884                 ics_clock_paused = TRUE;
3885                 StopClocks();
3886                 continue;
3887             }
3888
3889             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3890                 ics_clock_paused = FALSE;
3891                 StartClocks();
3892                 continue;
3893             }
3894
3895             /* Grab player ratings from the Creating: message.
3896                Note we have to check for the special case when
3897                the ICS inserts things like [white] or [black]. */
3898             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3899                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3900                 /* star_matches:
3901                    0    player 1 name (not necessarily white)
3902                    1    player 1 rating
3903                    2    empty, white, or black (IGNORED)
3904                    3    player 2 name (not necessarily black)
3905                    4    player 2 rating
3906
3907                    The names/ratings are sorted out when the game
3908                    actually starts (below).
3909                 */
3910                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3911                 player1Rating = string_to_rating(star_match[1]);
3912                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3913                 player2Rating = string_to_rating(star_match[4]);
3914
3915                 if (appData.debugMode)
3916                   fprintf(debugFP,
3917                           "Ratings from 'Creating:' %s %d, %s %d\n",
3918                           player1Name, player1Rating,
3919                           player2Name, player2Rating);
3920
3921                 continue;
3922             }
3923
3924             /* Improved generic start/end-of-game messages */
3925             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3926                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3927                 /* If tkind == 0: */
3928                 /* star_match[0] is the game number */
3929                 /*           [1] is the white player's name */
3930                 /*           [2] is the black player's name */
3931                 /* For end-of-game: */
3932                 /*           [3] is the reason for the game end */
3933                 /*           [4] is a PGN end game-token, preceded by " " */
3934                 /* For start-of-game: */
3935                 /*           [3] begins with "Creating" or "Continuing" */
3936                 /*           [4] is " *" or empty (don't care). */
3937                 int gamenum = atoi(star_match[0]);
3938                 char *whitename, *blackname, *why, *endtoken;
3939                 ChessMove endtype = EndOfFile;
3940
3941                 if (tkind == 0) {
3942                   whitename = star_match[1];
3943                   blackname = star_match[2];
3944                   why = star_match[3];
3945                   endtoken = star_match[4];
3946                 } else {
3947                   whitename = star_match[1];
3948                   blackname = star_match[3];
3949                   why = star_match[5];
3950                   endtoken = star_match[6];
3951                 }
3952
3953                 /* Game start messages */
3954                 if (strncmp(why, "Creating ", 9) == 0 ||
3955                     strncmp(why, "Continuing ", 11) == 0) {
3956                     gs_gamenum = gamenum;
3957                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3958                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3959                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3960 #if ZIPPY
3961                     if (appData.zippyPlay) {
3962                         ZippyGameStart(whitename, blackname);
3963                     }
3964 #endif /*ZIPPY*/
3965                     partnerBoardValid = FALSE; // [HGM] bughouse
3966                     continue;
3967                 }
3968
3969                 /* Game end messages */
3970                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3971                     ics_gamenum != gamenum) {
3972                     continue;
3973                 }
3974                 while (endtoken[0] == ' ') endtoken++;
3975                 switch (endtoken[0]) {
3976                   case '*':
3977                   default:
3978                     endtype = GameUnfinished;
3979                     break;
3980                   case '0':
3981                     endtype = BlackWins;
3982                     break;
3983                   case '1':
3984                     if (endtoken[1] == '/')
3985                       endtype = GameIsDrawn;
3986                     else
3987                       endtype = WhiteWins;
3988                     break;
3989                 }
3990                 GameEnds(endtype, why, GE_ICS);
3991 #if ZIPPY
3992                 if (appData.zippyPlay && first.initDone) {
3993                     ZippyGameEnd(endtype, why);
3994                     if (first.pr == NoProc) {
3995                       /* Start the next process early so that we'll
3996                          be ready for the next challenge */
3997                       StartChessProgram(&first);
3998                     }
3999                     /* Send "new" early, in case this command takes
4000                        a long time to finish, so that we'll be ready
4001                        for the next challenge. */
4002                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4003                     Reset(TRUE, TRUE);
4004                 }
4005 #endif /*ZIPPY*/
4006                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4007                 continue;
4008             }
4009
4010             if (looking_at(buf, &i, "Removing game * from observation") ||
4011                 looking_at(buf, &i, "no longer observing game *") ||
4012                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4013                 if (gameMode == IcsObserving &&
4014                     atoi(star_match[0]) == ics_gamenum)
4015                   {
4016                       /* icsEngineAnalyze */
4017                       if (appData.icsEngineAnalyze) {
4018                             ExitAnalyzeMode();
4019                             ModeHighlight();
4020                       }
4021                       StopClocks();
4022                       gameMode = IcsIdle;
4023                       ics_gamenum = -1;
4024                       ics_user_moved = FALSE;
4025                   }
4026                 continue;
4027             }
4028
4029             if (looking_at(buf, &i, "no longer examining game *")) {
4030                 if (gameMode == IcsExamining &&
4031                     atoi(star_match[0]) == ics_gamenum)
4032                   {
4033                       gameMode = IcsIdle;
4034                       ics_gamenum = -1;
4035                       ics_user_moved = FALSE;
4036                   }
4037                 continue;
4038             }
4039
4040             /* Advance leftover_start past any newlines we find,
4041                so only partial lines can get reparsed */
4042             if (looking_at(buf, &i, "\n")) {
4043                 prevColor = curColor;
4044                 if (curColor != ColorNormal) {
4045                     if (oldi > next_out) {
4046                         SendToPlayer(&buf[next_out], oldi - next_out);
4047                         next_out = oldi;
4048                     }
4049                     Colorize(ColorNormal, FALSE);
4050                     curColor = ColorNormal;
4051                 }
4052                 if (started == STARTED_BOARD) {
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     ParseBoard12(parse);
4056                     ics_user_moved = 0;
4057
4058                     /* Send premove here */
4059                     if (appData.premove) {
4060                       char str[MSG_SIZ];
4061                       if (currentMove == 0 &&
4062                           gameMode == IcsPlayingWhite &&
4063                           appData.premoveWhite) {
4064                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4065                         if (appData.debugMode)
4066                           fprintf(debugFP, "Sending premove:\n");
4067                         SendToICS(str);
4068                       } else if (currentMove == 1 &&
4069                                  gameMode == IcsPlayingBlack &&
4070                                  appData.premoveBlack) {
4071                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4072                         if (appData.debugMode)
4073                           fprintf(debugFP, "Sending premove:\n");
4074                         SendToICS(str);
4075                       } else if (gotPremove) {
4076                         gotPremove = 0;
4077                         ClearPremoveHighlights();
4078                         if (appData.debugMode)
4079                           fprintf(debugFP, "Sending premove:\n");
4080                           UserMoveEvent(premoveFromX, premoveFromY,
4081                                         premoveToX, premoveToY,
4082                                         premovePromoChar);
4083                       }
4084                     }
4085
4086                     /* Usually suppress following prompt */
4087                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4088                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4089                         if (looking_at(buf, &i, "*% ")) {
4090                             savingComment = FALSE;
4091                             suppressKibitz = 0;
4092                         }
4093                     }
4094                     next_out = i;
4095                 } else if (started == STARTED_HOLDINGS) {
4096                     int gamenum;
4097                     char new_piece[MSG_SIZ];
4098                     started = STARTED_NONE;
4099                     parse[parse_pos] = NULLCHAR;
4100                     if (appData.debugMode)
4101                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4102                                                         parse, currentMove);
4103                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4104                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4105                         if (gameInfo.variant == VariantNormal) {
4106                           /* [HGM] We seem to switch variant during a game!
4107                            * Presumably no holdings were displayed, so we have
4108                            * to move the position two files to the right to
4109                            * create room for them!
4110                            */
4111                           VariantClass newVariant;
4112                           switch(gameInfo.boardWidth) { // base guess on board width
4113                                 case 9:  newVariant = VariantShogi; break;
4114                                 case 10: newVariant = VariantGreat; break;
4115                                 default: newVariant = VariantCrazyhouse; break;
4116                           }
4117                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4118                           /* Get a move list just to see the header, which
4119                              will tell us whether this is really bug or zh */
4120                           if (ics_getting_history == H_FALSE) {
4121                             ics_getting_history = H_REQUESTED;
4122                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4123                             SendToICS(str);
4124                           }
4125                         }
4126                         new_piece[0] = NULLCHAR;
4127                         sscanf(parse, "game %d white [%s black [%s <- %s",
4128                                &gamenum, white_holding, black_holding,
4129                                new_piece);
4130                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4131                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4132                         /* [HGM] copy holdings to board holdings area */
4133                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4134                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4135                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4136 #if ZIPPY
4137                         if (appData.zippyPlay && first.initDone) {
4138                             ZippyHoldings(white_holding, black_holding,
4139                                           new_piece);
4140                         }
4141 #endif /*ZIPPY*/
4142                         if (tinyLayout || smallLayout) {
4143                             char wh[16], bh[16];
4144                             PackHolding(wh, white_holding);
4145                             PackHolding(bh, black_holding);
4146                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4147                                     gameInfo.white, gameInfo.black);
4148                         } else {
4149                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4150                                     gameInfo.white, white_holding, _("vs."),
4151                                     gameInfo.black, black_holding);
4152                         }
4153                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4154                         DrawPosition(FALSE, boards[currentMove]);
4155                         DisplayTitle(str);
4156                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4157                         sscanf(parse, "game %d white [%s black [%s <- %s",
4158                                &gamenum, white_holding, black_holding,
4159                                new_piece);
4160                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4161                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4162                         /* [HGM] copy holdings to partner-board holdings area */
4163                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4164                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4165                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4166                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4167                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4168                       }
4169                     }
4170                     /* Suppress following prompt */
4171                     if (looking_at(buf, &i, "*% ")) {
4172                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4173                         savingComment = FALSE;
4174                         suppressKibitz = 0;
4175                     }
4176                     next_out = i;
4177                 }
4178                 continue;
4179             }
4180
4181             i++;                /* skip unparsed character and loop back */
4182         }
4183
4184         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4185 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4186 //          SendToPlayer(&buf[next_out], i - next_out);
4187             started != STARTED_HOLDINGS && leftover_start > next_out) {
4188             SendToPlayer(&buf[next_out], leftover_start - next_out);
4189             next_out = i;
4190         }
4191
4192         leftover_len = buf_len - leftover_start;
4193         /* if buffer ends with something we couldn't parse,
4194            reparse it after appending the next read */
4195
4196     } else if (count == 0) {
4197         RemoveInputSource(isr);
4198         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4199     } else {
4200         DisplayFatalError(_("Error reading from ICS"), error, 1);
4201     }
4202 }
4203
4204
4205 /* Board style 12 looks like this:
4206
4207    <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
4208
4209  * The "<12> " is stripped before it gets to this routine.  The two
4210  * trailing 0's (flip state and clock ticking) are later addition, and
4211  * some chess servers may not have them, or may have only the first.
4212  * Additional trailing fields may be added in the future.
4213  */
4214
4215 #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"
4216
4217 #define RELATION_OBSERVING_PLAYED    0
4218 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4219 #define RELATION_PLAYING_MYMOVE      1
4220 #define RELATION_PLAYING_NOTMYMOVE  -1
4221 #define RELATION_EXAMINING           2
4222 #define RELATION_ISOLATED_BOARD     -3
4223 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4224
4225 void
4226 ParseBoard12 (char *string)
4227 {
4228 #if ZIPPY
4229     int i, takeback;
4230     char *bookHit = NULL; // [HGM] book
4231 #endif
4232     GameMode newGameMode;
4233     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4234     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4235     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4236     char to_play, board_chars[200];
4237     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4238     char black[32], white[32];
4239     Board board;
4240     int prevMove = currentMove;
4241     int ticking = 2;
4242     ChessMove moveType;
4243     int fromX, fromY, toX, toY;
4244     char promoChar;
4245     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4246     Boolean weird = FALSE, reqFlag = FALSE;
4247
4248     fromX = fromY = toX = toY = -1;
4249
4250     newGame = FALSE;
4251
4252     if (appData.debugMode)
4253       fprintf(debugFP, "Parsing board: %s\n", string);
4254
4255     move_str[0] = NULLCHAR;
4256     elapsed_time[0] = NULLCHAR;
4257     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4258         int  i = 0, j;
4259         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4260             if(string[i] == ' ') { ranks++; files = 0; }
4261             else files++;
4262             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4263             i++;
4264         }
4265         for(j = 0; j <i; j++) board_chars[j] = string[j];
4266         board_chars[i] = '\0';
4267         string += i + 1;
4268     }
4269     n = sscanf(string, PATTERN, &to_play, &double_push,
4270                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4271                &gamenum, white, black, &relation, &basetime, &increment,
4272                &white_stren, &black_stren, &white_time, &black_time,
4273                &moveNum, str, elapsed_time, move_str, &ics_flip,
4274                &ticking);
4275
4276     if (n < 21) {
4277         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4278         DisplayError(str, 0);
4279         return;
4280     }
4281
4282     /* Convert the move number to internal form */
4283     moveNum = (moveNum - 1) * 2;
4284     if (to_play == 'B') moveNum++;
4285     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4286       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4287                         0, 1);
4288       return;
4289     }
4290
4291     switch (relation) {
4292       case RELATION_OBSERVING_PLAYED:
4293       case RELATION_OBSERVING_STATIC:
4294         if (gamenum == -1) {
4295             /* Old ICC buglet */
4296             relation = RELATION_OBSERVING_STATIC;
4297         }
4298         newGameMode = IcsObserving;
4299         break;
4300       case RELATION_PLAYING_MYMOVE:
4301       case RELATION_PLAYING_NOTMYMOVE:
4302         newGameMode =
4303           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4304             IcsPlayingWhite : IcsPlayingBlack;
4305         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4306         break;
4307       case RELATION_EXAMINING:
4308         newGameMode = IcsExamining;
4309         break;
4310       case RELATION_ISOLATED_BOARD:
4311       default:
4312         /* Just display this board.  If user was doing something else,
4313            we will forget about it until the next board comes. */
4314         newGameMode = IcsIdle;
4315         break;
4316       case RELATION_STARTING_POSITION:
4317         newGameMode = gameMode;
4318         break;
4319     }
4320
4321     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4322         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4323          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4324       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4325       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4326       static int lastBgGame = -1;
4327       char *toSqr;
4328       for (k = 0; k < ranks; k++) {
4329         for (j = 0; j < files; j++)
4330           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4331         if(gameInfo.holdingsWidth > 1) {
4332              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4333              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4334         }
4335       }
4336       CopyBoard(partnerBoard, board);
4337       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4338         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4339         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4340       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4341       if(toSqr = strchr(str, '-')) {
4342         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4343         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4344       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4345       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4346       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4347       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4348       if(twoBoards) {
4349           DisplayWhiteClock(white_time*fac, to_play == 'W');
4350           DisplayBlackClock(black_time*fac, to_play != 'W');
4351           activePartner = to_play;
4352           if(gamenum != lastBgGame) {
4353               char buf[MSG_SIZ];
4354               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4355               DisplayTitle(buf);
4356           }
4357           lastBgGame = gamenum;
4358           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4359                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4360       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4361                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4362       if(!twoBoards) DisplayMessage(partnerStatus, "");
4363         partnerBoardValid = TRUE;
4364       return;
4365     }
4366
4367     if(appData.dualBoard && appData.bgObserve) {
4368         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4369             SendToICS(ics_prefix), SendToICS("pobserve\n");
4370         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4371             char buf[MSG_SIZ];
4372             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4373             SendToICS(buf);
4374         }
4375     }
4376
4377     /* Modify behavior for initial board display on move listing
4378        of wild games.
4379        */
4380     switch (ics_getting_history) {
4381       case H_FALSE:
4382       case H_REQUESTED:
4383         break;
4384       case H_GOT_REQ_HEADER:
4385       case H_GOT_UNREQ_HEADER:
4386         /* This is the initial position of the current game */
4387         gamenum = ics_gamenum;
4388         moveNum = 0;            /* old ICS bug workaround */
4389         if (to_play == 'B') {
4390           startedFromSetupPosition = TRUE;
4391           blackPlaysFirst = TRUE;
4392           moveNum = 1;
4393           if (forwardMostMove == 0) forwardMostMove = 1;
4394           if (backwardMostMove == 0) backwardMostMove = 1;
4395           if (currentMove == 0) currentMove = 1;
4396         }
4397         newGameMode = gameMode;
4398         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4399         break;
4400       case H_GOT_UNWANTED_HEADER:
4401         /* This is an initial board that we don't want */
4402         return;
4403       case H_GETTING_MOVES:
4404         /* Should not happen */
4405         DisplayError(_("Error gathering move list: extra board"), 0);
4406         ics_getting_history = H_FALSE;
4407         return;
4408     }
4409
4410    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4411                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4412                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4413      /* [HGM] We seem to have switched variant unexpectedly
4414       * Try to guess new variant from board size
4415       */
4416           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4417           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4418           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4419           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4420           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4421           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4422           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4423           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4424           /* Get a move list just to see the header, which
4425              will tell us whether this is really bug or zh */
4426           if (ics_getting_history == H_FALSE) {
4427             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4428             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4429             SendToICS(str);
4430           }
4431     }
4432
4433     /* Take action if this is the first board of a new game, or of a
4434        different game than is currently being displayed.  */
4435     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4436         relation == RELATION_ISOLATED_BOARD) {
4437
4438         /* Forget the old game and get the history (if any) of the new one */
4439         if (gameMode != BeginningOfGame) {
4440           Reset(TRUE, TRUE);
4441         }
4442         newGame = TRUE;
4443         if (appData.autoRaiseBoard) BoardToTop();
4444         prevMove = -3;
4445         if (gamenum == -1) {
4446             newGameMode = IcsIdle;
4447         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4448                    appData.getMoveList && !reqFlag) {
4449             /* Need to get game history */
4450             ics_getting_history = H_REQUESTED;
4451             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4452             SendToICS(str);
4453         }
4454
4455         /* Initially flip the board to have black on the bottom if playing
4456            black or if the ICS flip flag is set, but let the user change
4457            it with the Flip View button. */
4458         flipView = appData.autoFlipView ?
4459           (newGameMode == IcsPlayingBlack) || ics_flip :
4460           appData.flipView;
4461
4462         /* Done with values from previous mode; copy in new ones */
4463         gameMode = newGameMode;
4464         ModeHighlight();
4465         ics_gamenum = gamenum;
4466         if (gamenum == gs_gamenum) {
4467             int klen = strlen(gs_kind);
4468             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4469             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4470             gameInfo.event = StrSave(str);
4471         } else {
4472             gameInfo.event = StrSave("ICS game");
4473         }
4474         gameInfo.site = StrSave(appData.icsHost);
4475         gameInfo.date = PGNDate();
4476         gameInfo.round = StrSave("-");
4477         gameInfo.white = StrSave(white);
4478         gameInfo.black = StrSave(black);
4479         timeControl = basetime * 60 * 1000;
4480         timeControl_2 = 0;
4481         timeIncrement = increment * 1000;
4482         movesPerSession = 0;
4483         gameInfo.timeControl = TimeControlTagValue();
4484         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4487     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4488     setbuf(debugFP, NULL);
4489   }
4490
4491         gameInfo.outOfBook = NULL;
4492
4493         /* Do we have the ratings? */
4494         if (strcmp(player1Name, white) == 0 &&
4495             strcmp(player2Name, black) == 0) {
4496             if (appData.debugMode)
4497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4498                       player1Rating, player2Rating);
4499             gameInfo.whiteRating = player1Rating;
4500             gameInfo.blackRating = player2Rating;
4501         } else if (strcmp(player2Name, white) == 0 &&
4502                    strcmp(player1Name, black) == 0) {
4503             if (appData.debugMode)
4504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4505                       player2Rating, player1Rating);
4506             gameInfo.whiteRating = player2Rating;
4507             gameInfo.blackRating = player1Rating;
4508         }
4509         player1Name[0] = player2Name[0] = NULLCHAR;
4510
4511         /* Silence shouts if requested */
4512         if (appData.quietPlay &&
4513             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4514             SendToICS(ics_prefix);
4515             SendToICS("set shout 0\n");
4516         }
4517     }
4518
4519     /* Deal with midgame name changes */
4520     if (!newGame) {
4521         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4522             if (gameInfo.white) free(gameInfo.white);
4523             gameInfo.white = StrSave(white);
4524         }
4525         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4526             if (gameInfo.black) free(gameInfo.black);
4527             gameInfo.black = StrSave(black);
4528         }
4529     }
4530
4531     /* Throw away game result if anything actually changes in examine mode */
4532     if (gameMode == IcsExamining && !newGame) {
4533         gameInfo.result = GameUnfinished;
4534         if (gameInfo.resultDetails != NULL) {
4535             free(gameInfo.resultDetails);
4536             gameInfo.resultDetails = NULL;
4537         }
4538     }
4539
4540     /* In pausing && IcsExamining mode, we ignore boards coming
4541        in if they are in a different variation than we are. */
4542     if (pauseExamInvalid) return;
4543     if (pausing && gameMode == IcsExamining) {
4544         if (moveNum <= pauseExamForwardMostMove) {
4545             pauseExamInvalid = TRUE;
4546             forwardMostMove = pauseExamForwardMostMove;
4547             return;
4548         }
4549     }
4550
4551   if (appData.debugMode) {
4552     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4553   }
4554     /* Parse the board */
4555     for (k = 0; k < ranks; k++) {
4556       for (j = 0; j < files; j++)
4557         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4558       if(gameInfo.holdingsWidth > 1) {
4559            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4560            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4561       }
4562     }
4563     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4564       board[5][BOARD_RGHT+1] = WhiteAngel;
4565       board[6][BOARD_RGHT+1] = WhiteMarshall;
4566       board[1][0] = BlackMarshall;
4567       board[2][0] = BlackAngel;
4568       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4569     }
4570     CopyBoard(boards[moveNum], board);
4571     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4572     if (moveNum == 0) {
4573         startedFromSetupPosition =
4574           !CompareBoards(board, initialPosition);
4575         if(startedFromSetupPosition)
4576             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4577     }
4578
4579     /* [HGM] Set castling rights. Take the outermost Rooks,
4580        to make it also work for FRC opening positions. Note that board12
4581        is really defective for later FRC positions, as it has no way to
4582        indicate which Rook can castle if they are on the same side of King.
4583        For the initial position we grant rights to the outermost Rooks,
4584        and remember thos rights, and we then copy them on positions
4585        later in an FRC game. This means WB might not recognize castlings with
4586        Rooks that have moved back to their original position as illegal,
4587        but in ICS mode that is not its job anyway.
4588     */
4589     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4590     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4591
4592         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4593             if(board[0][i] == WhiteRook) j = i;
4594         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4595         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4596             if(board[0][i] == WhiteRook) j = i;
4597         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4598         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4599             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4600         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4601         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4602             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4603         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4604
4605         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4606         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4607         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4608             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4609         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4610             if(board[BOARD_HEIGHT-1][k] == bKing)
4611                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4612         if(gameInfo.variant == VariantTwoKings) {
4613             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4614             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4615             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4616         }
4617     } else { int r;
4618         r = boards[moveNum][CASTLING][0] = initialRights[0];
4619         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4620         r = boards[moveNum][CASTLING][1] = initialRights[1];
4621         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4622         r = boards[moveNum][CASTLING][3] = initialRights[3];
4623         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4624         r = boards[moveNum][CASTLING][4] = initialRights[4];
4625         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4626         /* wildcastle kludge: always assume King has rights */
4627         r = boards[moveNum][CASTLING][2] = initialRights[2];
4628         r = boards[moveNum][CASTLING][5] = initialRights[5];
4629     }
4630     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4631     boards[moveNum][EP_STATUS] = EP_NONE;
4632     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4633     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4634     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4635
4636
4637     if (ics_getting_history == H_GOT_REQ_HEADER ||
4638         ics_getting_history == H_GOT_UNREQ_HEADER) {
4639         /* This was an initial position from a move list, not
4640            the current position */
4641         return;
4642     }
4643
4644     /* Update currentMove and known move number limits */
4645     newMove = newGame || moveNum > forwardMostMove;
4646
4647     if (newGame) {
4648         forwardMostMove = backwardMostMove = currentMove = moveNum;
4649         if (gameMode == IcsExamining && moveNum == 0) {
4650           /* Workaround for ICS limitation: we are not told the wild
4651              type when starting to examine a game.  But if we ask for
4652              the move list, the move list header will tell us */
4653             ics_getting_history = H_REQUESTED;
4654             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655             SendToICS(str);
4656         }
4657     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4658                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4659 #if ZIPPY
4660         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4661         /* [HGM] applied this also to an engine that is silently watching        */
4662         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4663             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4664             gameInfo.variant == currentlyInitializedVariant) {
4665           takeback = forwardMostMove - moveNum;
4666           for (i = 0; i < takeback; i++) {
4667             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4668             SendToProgram("undo\n", &first);
4669           }
4670         }
4671 #endif
4672
4673         forwardMostMove = moveNum;
4674         if (!pausing || currentMove > forwardMostMove)
4675           currentMove = forwardMostMove;
4676     } else {
4677         /* New part of history that is not contiguous with old part */
4678         if (pausing && gameMode == IcsExamining) {
4679             pauseExamInvalid = TRUE;
4680             forwardMostMove = pauseExamForwardMostMove;
4681             return;
4682         }
4683         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4684 #if ZIPPY
4685             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4686                 // [HGM] when we will receive the move list we now request, it will be
4687                 // fed to the engine from the first move on. So if the engine is not
4688                 // in the initial position now, bring it there.
4689                 InitChessProgram(&first, 0);
4690             }
4691 #endif
4692             ics_getting_history = H_REQUESTED;
4693             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4694             SendToICS(str);
4695         }
4696         forwardMostMove = backwardMostMove = currentMove = moveNum;
4697     }
4698
4699     /* Update the clocks */
4700     if (strchr(elapsed_time, '.')) {
4701       /* Time is in ms */
4702       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4703       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4704     } else {
4705       /* Time is in seconds */
4706       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4707       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4708     }
4709
4710
4711 #if ZIPPY
4712     if (appData.zippyPlay && newGame &&
4713         gameMode != IcsObserving && gameMode != IcsIdle &&
4714         gameMode != IcsExamining)
4715       ZippyFirstBoard(moveNum, basetime, increment);
4716 #endif
4717
4718     /* Put the move on the move list, first converting
4719        to canonical algebraic form. */
4720     if (moveNum > 0) {
4721   if (appData.debugMode) {
4722     int f = forwardMostMove;
4723     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4724             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4725             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4726     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4727     fprintf(debugFP, "moveNum = %d\n", moveNum);
4728     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4729     setbuf(debugFP, NULL);
4730   }
4731         if (moveNum <= backwardMostMove) {
4732             /* We don't know what the board looked like before
4733                this move.  Punt. */
4734           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4735             strcat(parseList[moveNum - 1], " ");
4736             strcat(parseList[moveNum - 1], elapsed_time);
4737             moveList[moveNum - 1][0] = NULLCHAR;
4738         } else if (strcmp(move_str, "none") == 0) {
4739             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4740             /* Again, we don't know what the board looked like;
4741                this is really the start of the game. */
4742             parseList[moveNum - 1][0] = NULLCHAR;
4743             moveList[moveNum - 1][0] = NULLCHAR;
4744             backwardMostMove = moveNum;
4745             startedFromSetupPosition = TRUE;
4746             fromX = fromY = toX = toY = -1;
4747         } else {
4748           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4749           //                 So we parse the long-algebraic move string in stead of the SAN move
4750           int valid; char buf[MSG_SIZ], *prom;
4751
4752           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4753                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4754           // str looks something like "Q/a1-a2"; kill the slash
4755           if(str[1] == '/')
4756             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4757           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4758           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4759                 strcat(buf, prom); // long move lacks promo specification!
4760           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4761                 if(appData.debugMode)
4762                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4763                 safeStrCpy(move_str, buf, MSG_SIZ);
4764           }
4765           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4766                                 &fromX, &fromY, &toX, &toY, &promoChar)
4767                || ParseOneMove(buf, moveNum - 1, &moveType,
4768                                 &fromX, &fromY, &toX, &toY, &promoChar);
4769           // end of long SAN patch
4770           if (valid) {
4771             (void) CoordsToAlgebraic(boards[moveNum - 1],
4772                                      PosFlags(moveNum - 1),
4773                                      fromY, fromX, toY, toX, promoChar,
4774                                      parseList[moveNum-1]);
4775             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4776               case MT_NONE:
4777               case MT_STALEMATE:
4778               default:
4779                 break;
4780               case MT_CHECK:
4781                 if(gameInfo.variant != VariantShogi)
4782                     strcat(parseList[moveNum - 1], "+");
4783                 break;
4784               case MT_CHECKMATE:
4785               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4786                 strcat(parseList[moveNum - 1], "#");
4787                 break;
4788             }
4789             strcat(parseList[moveNum - 1], " ");
4790             strcat(parseList[moveNum - 1], elapsed_time);
4791             /* currentMoveString is set as a side-effect of ParseOneMove */
4792             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4793             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4794             strcat(moveList[moveNum - 1], "\n");
4795
4796             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4797                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4798               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4799                 ChessSquare old, new = boards[moveNum][k][j];
4800                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4801                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4802                   if(old == new) continue;
4803                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4804                   else if(new == WhiteWazir || new == BlackWazir) {
4805                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4806                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4807                       else boards[moveNum][k][j] = old; // preserve type of Gold
4808                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4809                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4810               }
4811           } else {
4812             /* Move from ICS was illegal!?  Punt. */
4813             if (appData.debugMode) {
4814               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4815               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4816             }
4817             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4818             strcat(parseList[moveNum - 1], " ");
4819             strcat(parseList[moveNum - 1], elapsed_time);
4820             moveList[moveNum - 1][0] = NULLCHAR;
4821             fromX = fromY = toX = toY = -1;
4822           }
4823         }
4824   if (appData.debugMode) {
4825     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4826     setbuf(debugFP, NULL);
4827   }
4828
4829 #if ZIPPY
4830         /* Send move to chess program (BEFORE animating it). */
4831         if (appData.zippyPlay && !newGame && newMove &&
4832            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4833
4834             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4835                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4836                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4838                             move_str);
4839                     DisplayError(str, 0);
4840                 } else {
4841                     if (first.sendTime) {
4842                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4843                     }
4844                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4845                     if (firstMove && !bookHit) {
4846                         firstMove = FALSE;
4847                         if (first.useColors) {
4848                           SendToProgram(gameMode == IcsPlayingWhite ?
4849                                         "white\ngo\n" :
4850                                         "black\ngo\n", &first);
4851                         } else {
4852                           SendToProgram("go\n", &first);
4853                         }
4854                         first.maybeThinking = TRUE;
4855                     }
4856                 }
4857             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4858               if (moveList[moveNum - 1][0] == NULLCHAR) {
4859                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4860                 DisplayError(str, 0);
4861               } else {
4862                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4863                 SendMoveToProgram(moveNum - 1, &first);
4864               }
4865             }
4866         }
4867 #endif
4868     }
4869
4870     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4871         /* If move comes from a remote source, animate it.  If it
4872            isn't remote, it will have already been animated. */
4873         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4874             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4875         }
4876         if (!pausing && appData.highlightLastMove) {
4877             SetHighlights(fromX, fromY, toX, toY);
4878         }
4879     }
4880
4881     /* Start the clocks */
4882     whiteFlag = blackFlag = FALSE;
4883     appData.clockMode = !(basetime == 0 && increment == 0);
4884     if (ticking == 0) {
4885       ics_clock_paused = TRUE;
4886       StopClocks();
4887     } else if (ticking == 1) {
4888       ics_clock_paused = FALSE;
4889     }
4890     if (gameMode == IcsIdle ||
4891         relation == RELATION_OBSERVING_STATIC ||
4892         relation == RELATION_EXAMINING ||
4893         ics_clock_paused)
4894       DisplayBothClocks();
4895     else
4896       StartClocks();
4897
4898     /* Display opponents and material strengths */
4899     if (gameInfo.variant != VariantBughouse &&
4900         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4901         if (tinyLayout || smallLayout) {
4902             if(gameInfo.variant == VariantNormal)
4903               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4904                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4905                     basetime, increment);
4906             else
4907               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4908                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4909                     basetime, increment, (int) gameInfo.variant);
4910         } else {
4911             if(gameInfo.variant == VariantNormal)
4912               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4913                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4914                     basetime, increment);
4915             else
4916               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4917                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4918                     basetime, increment, VariantName(gameInfo.variant));
4919         }
4920         DisplayTitle(str);
4921   if (appData.debugMode) {
4922     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4923   }
4924     }
4925
4926
4927     /* Display the board */
4928     if (!pausing && !appData.noGUI) {
4929
4930       if (appData.premove)
4931           if (!gotPremove ||
4932              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4933              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4934               ClearPremoveHighlights();
4935
4936       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4937         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4938       DrawPosition(j, boards[currentMove]);
4939
4940       DisplayMove(moveNum - 1);
4941       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4942             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4943               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4944         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4945       }
4946     }
4947
4948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4949 #if ZIPPY
4950     if(bookHit) { // [HGM] book: simulate book reply
4951         static char bookMove[MSG_SIZ]; // a bit generous?
4952
4953         programStats.nodes = programStats.depth = programStats.time =
4954         programStats.score = programStats.got_only_move = 0;
4955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4956
4957         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4958         strcat(bookMove, bookHit);
4959         HandleMachineMove(bookMove, &first);
4960     }
4961 #endif
4962 }
4963
4964 void
4965 GetMoveListEvent ()
4966 {
4967     char buf[MSG_SIZ];
4968     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4969         ics_getting_history = H_REQUESTED;
4970         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4971         SendToICS(buf);
4972     }
4973 }
4974
4975 void
4976 SendToBoth (char *msg)
4977 {   // to make it easy to keep two engines in step in dual analysis
4978     SendToProgram(msg, &first);
4979     if(second.analyzing) SendToProgram(msg, &second);
4980 }
4981
4982 void
4983 AnalysisPeriodicEvent (int force)
4984 {
4985     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4986          && !force) || !appData.periodicUpdates)
4987       return;
4988
4989     /* Send . command to Crafty to collect stats */
4990     SendToBoth(".\n");
4991
4992     /* Don't send another until we get a response (this makes
4993        us stop sending to old Crafty's which don't understand
4994        the "." command (sending illegal cmds resets node count & time,
4995        which looks bad)) */
4996     programStats.ok_to_send = 0;
4997 }
4998
4999 void
5000 ics_update_width (int new_width)
5001 {
5002         ics_printf("set width %d\n", new_width);
5003 }
5004
5005 void
5006 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5007 {
5008     char buf[MSG_SIZ];
5009
5010     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5011         // null move in variant where engine does not understand it (for analysis purposes)
5012         SendBoard(cps, moveNum + 1); // send position after move in stead.
5013         return;
5014     }
5015     if (cps->useUsermove) {
5016       SendToProgram("usermove ", cps);
5017     }
5018     if (cps->useSAN) {
5019       char *space;
5020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5021         int len = space - parseList[moveNum];
5022         memcpy(buf, parseList[moveNum], len);
5023         buf[len++] = '\n';
5024         buf[len] = NULLCHAR;
5025       } else {
5026         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5027       }
5028       SendToProgram(buf, cps);
5029     } else {
5030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5031         AlphaRank(moveList[moveNum], 4);
5032         SendToProgram(moveList[moveNum], cps);
5033         AlphaRank(moveList[moveNum], 4); // and back
5034       } else
5035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5036        * the engine. It would be nice to have a better way to identify castle
5037        * moves here. */
5038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5039                                                                          && cps->useOOCastle) {
5040         int fromX = moveList[moveNum][0] - AAA;
5041         int fromY = moveList[moveNum][1] - ONE;
5042         int toX = moveList[moveNum][2] - AAA;
5043         int toY = moveList[moveNum][3] - ONE;
5044         if((boards[moveNum][fromY][fromX] == WhiteKing
5045             && boards[moveNum][toY][toX] == WhiteRook)
5046            || (boards[moveNum][fromY][fromX] == BlackKing
5047                && boards[moveNum][toY][toX] == BlackRook)) {
5048           if(toX > fromX) SendToProgram("O-O\n", cps);
5049           else SendToProgram("O-O-O\n", cps);
5050         }
5051         else SendToProgram(moveList[moveNum], cps);
5052       } else
5053       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5054         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5055           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5056           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5057                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5058         } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5059           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5060                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5061                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5062                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5063         } else
5064           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5065                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066         SendToProgram(buf, cps);
5067       }
5068       else SendToProgram(moveList[moveNum], cps);
5069       /* End of additions by Tord */
5070     }
5071
5072     /* [HGM] setting up the opening has brought engine in force mode! */
5073     /*       Send 'go' if we are in a mode where machine should play. */
5074     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5075         (gameMode == TwoMachinesPlay   ||
5076 #if ZIPPY
5077          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5078 #endif
5079          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5080         SendToProgram("go\n", cps);
5081   if (appData.debugMode) {
5082     fprintf(debugFP, "(extra)\n");
5083   }
5084     }
5085     setboardSpoiledMachineBlack = 0;
5086 }
5087
5088 void
5089 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5090 {
5091     char user_move[MSG_SIZ];
5092     char suffix[4];
5093
5094     if(gameInfo.variant == VariantSChess && promoChar) {
5095         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5096         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5097     } else suffix[0] = NULLCHAR;
5098
5099     switch (moveType) {
5100       default:
5101         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5102                 (int)moveType, fromX, fromY, toX, toY);
5103         DisplayError(user_move + strlen("say "), 0);
5104         break;
5105       case WhiteKingSideCastle:
5106       case BlackKingSideCastle:
5107       case WhiteQueenSideCastleWild:
5108       case BlackQueenSideCastleWild:
5109       /* PUSH Fabien */
5110       case WhiteHSideCastleFR:
5111       case BlackHSideCastleFR:
5112       /* POP Fabien */
5113         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5114         break;
5115       case WhiteQueenSideCastle:
5116       case BlackQueenSideCastle:
5117       case WhiteKingSideCastleWild:
5118       case BlackKingSideCastleWild:
5119       /* PUSH Fabien */
5120       case WhiteASideCastleFR:
5121       case BlackASideCastleFR:
5122       /* POP Fabien */
5123         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5124         break;
5125       case WhiteNonPromotion:
5126       case BlackNonPromotion:
5127         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5128         break;
5129       case WhitePromotion:
5130       case BlackPromotion:
5131         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5132            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5133           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5134                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5135                 PieceToChar(WhiteFerz));
5136         else if(gameInfo.variant == VariantGreat)
5137           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5138                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5139                 PieceToChar(WhiteMan));
5140         else
5141           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143                 promoChar);
5144         break;
5145       case WhiteDrop:
5146       case BlackDrop:
5147       drop:
5148         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5149                  ToUpper(PieceToChar((ChessSquare) fromX)),
5150                  AAA + toX, ONE + toY);
5151         break;
5152       case IllegalMove:  /* could be a variant we don't quite understand */
5153         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5154       case NormalMove:
5155       case WhiteCapturesEnPassant:
5156       case BlackCapturesEnPassant:
5157         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5158                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5159         break;
5160     }
5161     SendToICS(user_move);
5162     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5163         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5164 }
5165
5166 void
5167 UploadGameEvent ()
5168 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5169     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5170     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5171     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5172       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5173       return;
5174     }
5175     if(gameMode != IcsExamining) { // is this ever not the case?
5176         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5177
5178         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5179           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5180         } else { // on FICS we must first go to general examine mode
5181           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5182         }
5183         if(gameInfo.variant != VariantNormal) {
5184             // try figure out wild number, as xboard names are not always valid on ICS
5185             for(i=1; i<=36; i++) {
5186               snprintf(buf, MSG_SIZ, "wild/%d", i);
5187                 if(StringToVariant(buf) == gameInfo.variant) break;
5188             }
5189             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5190             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5191             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5192         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5193         SendToICS(ics_prefix);
5194         SendToICS(buf);
5195         if(startedFromSetupPosition || backwardMostMove != 0) {
5196           fen = PositionToFEN(backwardMostMove, NULL, 1);
5197           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5198             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5199             SendToICS(buf);
5200           } else { // FICS: everything has to set by separate bsetup commands
5201             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5202             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5203             SendToICS(buf);
5204             if(!WhiteOnMove(backwardMostMove)) {
5205                 SendToICS("bsetup tomove black\n");
5206             }
5207             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5208             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5209             SendToICS(buf);
5210             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5211             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5212             SendToICS(buf);
5213             i = boards[backwardMostMove][EP_STATUS];
5214             if(i >= 0) { // set e.p.
5215               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5216                 SendToICS(buf);
5217             }
5218             bsetup++;
5219           }
5220         }
5221       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5222             SendToICS("bsetup done\n"); // switch to normal examining.
5223     }
5224     for(i = backwardMostMove; i<last; i++) {
5225         char buf[20];
5226         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5227         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5228             int len = strlen(moveList[i]);
5229             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5230             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5231         }
5232         SendToICS(buf);
5233     }
5234     SendToICS(ics_prefix);
5235     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5236 }
5237
5238 static int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5239
5240 void
5241 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5242 {
5243     if (rf == DROP_RANK) {
5244       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5245       sprintf(move, "%c@%c%c\n",
5246                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5247     } else {
5248         if (promoChar == 'x' || promoChar == NULLCHAR) {
5249           sprintf(move, "%c%c%c%c\n",
5250                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5251           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5252         } else {
5253             sprintf(move, "%c%c%c%c%c\n",
5254                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5255         }
5256     }
5257 }
5258
5259 void
5260 ProcessICSInitScript (FILE *f)
5261 {
5262     char buf[MSG_SIZ];
5263
5264     while (fgets(buf, MSG_SIZ, f)) {
5265         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5266     }
5267
5268     fclose(f);
5269 }
5270
5271
5272 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5273 static ClickType lastClickType;
5274
5275 void
5276 Sweep (int step)
5277 {
5278     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5279     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5280     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5281     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5282     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5283     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5284     do {
5285         promoSweep -= step;
5286         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5287         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5288         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5289         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5290         if(!step) step = -1;
5291     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5292             appData.testLegality && (promoSweep == king ||
5293             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep));
5294     if(toX >= 0) {
5295         int victim = boards[currentMove][toY][toX];
5296         boards[currentMove][toY][toX] = promoSweep;
5297         DrawPosition(FALSE, boards[currentMove]);
5298         boards[currentMove][toY][toX] = victim;
5299     } else
5300     ChangeDragPiece(promoSweep);
5301 }
5302
5303 int
5304 PromoScroll (int x, int y)
5305 {
5306   int step = 0;
5307
5308   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5309   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5310   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5311   if(!step) return FALSE;
5312   lastX = x; lastY = y;
5313   if((promoSweep < BlackPawn) == flipView) step = -step;
5314   if(step > 0) selectFlag = 1;
5315   if(!selectFlag) Sweep(step);
5316   return FALSE;
5317 }
5318
5319 void
5320 NextPiece (int step)
5321 {
5322     ChessSquare piece = boards[currentMove][toY][toX];
5323     do {
5324         pieceSweep -= step;
5325         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5326         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5327         if(!step) step = -1;
5328     } while(PieceToChar(pieceSweep) == '.');
5329     boards[currentMove][toY][toX] = pieceSweep;
5330     DrawPosition(FALSE, boards[currentMove]);
5331     boards[currentMove][toY][toX] = piece;
5332 }
5333 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5334 void
5335 AlphaRank (char *move, int n)
5336 {
5337 //    char *p = move, c; int x, y;
5338
5339     if (appData.debugMode) {
5340         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5341     }
5342
5343     if(move[1]=='*' &&
5344        move[2]>='0' && move[2]<='9' &&
5345        move[3]>='a' && move[3]<='x'    ) {
5346         move[1] = '@';
5347         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5348         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5349     } else
5350     if(move[0]>='0' && move[0]<='9' &&
5351        move[1]>='a' && move[1]<='x' &&
5352        move[2]>='0' && move[2]<='9' &&
5353        move[3]>='a' && move[3]<='x'    ) {
5354         /* input move, Shogi -> normal */
5355         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5356         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5357         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5358         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5359     } else
5360     if(move[1]=='@' &&
5361        move[3]>='0' && move[3]<='9' &&
5362        move[2]>='a' && move[2]<='x'    ) {
5363         move[1] = '*';
5364         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5365         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5366     } else
5367     if(
5368        move[0]>='a' && move[0]<='x' &&
5369        move[3]>='0' && move[3]<='9' &&
5370        move[2]>='a' && move[2]<='x'    ) {
5371          /* output move, normal -> Shogi */
5372         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5373         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5374         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5375         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5376         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5377     }
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "   out = '%s'\n", move);
5380     }
5381 }
5382
5383 char yy_textstr[8000];
5384
5385 /* Parser for moves from gnuchess, ICS, or user typein box */
5386 Boolean
5387 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5388 {
5389     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5390
5391     switch (*moveType) {
5392       case WhitePromotion:
5393       case BlackPromotion:
5394       case WhiteNonPromotion:
5395       case BlackNonPromotion:
5396       case NormalMove:
5397       case WhiteCapturesEnPassant:
5398       case BlackCapturesEnPassant:
5399       case WhiteKingSideCastle:
5400       case WhiteQueenSideCastle:
5401       case BlackKingSideCastle:
5402       case BlackQueenSideCastle:
5403       case WhiteKingSideCastleWild:
5404       case WhiteQueenSideCastleWild:
5405       case BlackKingSideCastleWild:
5406       case BlackQueenSideCastleWild:
5407       /* Code added by Tord: */
5408       case WhiteHSideCastleFR:
5409       case WhiteASideCastleFR:
5410       case BlackHSideCastleFR:
5411       case BlackASideCastleFR:
5412       /* End of code added by Tord */
5413       case IllegalMove:         /* bug or odd chess variant */
5414         *fromX = currentMoveString[0] - AAA;
5415         *fromY = currentMoveString[1] - ONE;
5416         *toX = currentMoveString[2] - AAA;
5417         *toY = currentMoveString[3] - ONE;
5418         *promoChar = currentMoveString[4];
5419         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5420             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5421     if (appData.debugMode) {
5422         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5423     }
5424             *fromX = *fromY = *toX = *toY = 0;
5425             return FALSE;
5426         }
5427         if (appData.testLegality) {
5428           return (*moveType != IllegalMove);
5429         } else {
5430           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5431                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5432         }
5433
5434       case WhiteDrop:
5435       case BlackDrop:
5436         *fromX = *moveType == WhiteDrop ?
5437           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5438           (int) CharToPiece(ToLower(currentMoveString[0]));
5439         *fromY = DROP_RANK;
5440         *toX = currentMoveString[2] - AAA;
5441         *toY = currentMoveString[3] - ONE;
5442         *promoChar = NULLCHAR;
5443         return TRUE;
5444
5445       case AmbiguousMove:
5446       case ImpossibleMove:
5447       case EndOfFile:
5448       case ElapsedTime:
5449       case Comment:
5450       case PGNTag:
5451       case NAG:
5452       case WhiteWins:
5453       case BlackWins:
5454       case GameIsDrawn:
5455       default:
5456     if (appData.debugMode) {
5457         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5458     }
5459         /* bug? */
5460         *fromX = *fromY = *toX = *toY = 0;
5461         *promoChar = NULLCHAR;
5462         return FALSE;
5463     }
5464 }
5465
5466 Boolean pushed = FALSE;
5467 char *lastParseAttempt;
5468
5469 void
5470 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5471 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5472   int fromX, fromY, toX, toY; char promoChar;
5473   ChessMove moveType;
5474   Boolean valid;
5475   int nr = 0;
5476
5477   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5478   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5479     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5480     pushed = TRUE;
5481   }
5482   endPV = forwardMostMove;
5483   do {
5484     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5485     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5486     lastParseAttempt = pv;
5487     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5488     if(!valid && nr == 0 &&
5489        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5490         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5491         // Hande case where played move is different from leading PV move
5492         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5493         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5494         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5495         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5496           endPV += 2; // if position different, keep this
5497           moveList[endPV-1][0] = fromX + AAA;
5498           moveList[endPV-1][1] = fromY + ONE;
5499           moveList[endPV-1][2] = toX + AAA;
5500           moveList[endPV-1][3] = toY + ONE;
5501           parseList[endPV-1][0] = NULLCHAR;
5502           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5503         }
5504       }
5505     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5506     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5507     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5508     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5509         valid++; // allow comments in PV
5510         continue;
5511     }
5512     nr++;
5513     if(endPV+1 > framePtr) break; // no space, truncate
5514     if(!valid) break;
5515     endPV++;
5516     CopyBoard(boards[endPV], boards[endPV-1]);
5517     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5518     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5519     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5520     CoordsToAlgebraic(boards[endPV - 1],
5521                              PosFlags(endPV - 1),
5522                              fromY, fromX, toY, toX, promoChar,
5523                              parseList[endPV - 1]);
5524   } while(valid);
5525   if(atEnd == 2) return; // used hidden, for PV conversion
5526   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5527   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5528   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5529                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5530   DrawPosition(TRUE, boards[currentMove]);
5531 }
5532
5533 int
5534 MultiPV (ChessProgramState *cps)
5535 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5536         int i;
5537         for(i=0; i<cps->nrOptions; i++)
5538             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5539                 return i;
5540         return -1;
5541 }
5542
5543 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5544
5545 Boolean
5546 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5547 {
5548         int startPV, multi, lineStart, origIndex = index;
5549         char *p, buf2[MSG_SIZ];
5550         ChessProgramState *cps = (pane ? &second : &first);
5551
5552         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5553         lastX = x; lastY = y;
5554         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5555         lineStart = startPV = index;
5556         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5557         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5558         index = startPV;
5559         do{ while(buf[index] && buf[index] != '\n') index++;
5560         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5561         buf[index] = 0;
5562         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5563                 int n = cps->option[multi].value;
5564                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5565                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5566                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5567                 cps->option[multi].value = n;
5568                 *start = *end = 0;
5569                 return FALSE;
5570         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5571                 ExcludeClick(origIndex - lineStart);
5572                 return FALSE;
5573         }
5574         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5575         *start = startPV; *end = index-1;
5576         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5577         return TRUE;
5578 }
5579
5580 char *
5581 PvToSAN (char *pv)
5582 {
5583         static char buf[10*MSG_SIZ];
5584         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5585         *buf = NULLCHAR;
5586         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5587         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5588         for(i = forwardMostMove; i<endPV; i++){
5589             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5590             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5591             k += strlen(buf+k);
5592         }
5593         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5594         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5595         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5596         endPV = savedEnd;
5597         return buf;
5598 }
5599
5600 Boolean
5601 LoadPV (int x, int y)
5602 { // called on right mouse click to load PV
5603   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5604   lastX = x; lastY = y;
5605   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5606   extendGame = FALSE;
5607   return TRUE;
5608 }
5609
5610 void
5611 UnLoadPV ()
5612 {
5613   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5614   if(endPV < 0) return;
5615   if(appData.autoCopyPV) CopyFENToClipboard();
5616   endPV = -1;
5617   if(extendGame && currentMove > forwardMostMove) {
5618         Boolean saveAnimate = appData.animate;
5619         if(pushed) {
5620             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5621                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5622             } else storedGames--; // abandon shelved tail of original game
5623         }
5624         pushed = FALSE;
5625         forwardMostMove = currentMove;
5626         currentMove = oldFMM;
5627         appData.animate = FALSE;
5628         ToNrEvent(forwardMostMove);
5629         appData.animate = saveAnimate;
5630   }
5631   currentMove = forwardMostMove;
5632   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5633   ClearPremoveHighlights();
5634   DrawPosition(TRUE, boards[currentMove]);
5635 }
5636
5637 void
5638 MovePV (int x, int y, int h)
5639 { // step through PV based on mouse coordinates (called on mouse move)
5640   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5641
5642   // we must somehow check if right button is still down (might be released off board!)
5643   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5644   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5645   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5646   if(!step) return;
5647   lastX = x; lastY = y;
5648
5649   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5650   if(endPV < 0) return;
5651   if(y < margin) step = 1; else
5652   if(y > h - margin) step = -1;
5653   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5654   currentMove += step;
5655   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5656   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5657                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5658   DrawPosition(FALSE, boards[currentMove]);
5659 }
5660
5661
5662 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5663 // All positions will have equal probability, but the current method will not provide a unique
5664 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5665 #define DARK 1
5666 #define LITE 2
5667 #define ANY 3
5668
5669 int squaresLeft[4];
5670 int piecesLeft[(int)BlackPawn];
5671 int seed, nrOfShuffles;
5672
5673 void
5674 GetPositionNumber ()
5675 {       // sets global variable seed
5676         int i;
5677
5678         seed = appData.defaultFrcPosition;
5679         if(seed < 0) { // randomize based on time for negative FRC position numbers
5680                 for(i=0; i<50; i++) seed += random();
5681                 seed = random() ^ random() >> 8 ^ random() << 8;
5682                 if(seed<0) seed = -seed;
5683         }
5684 }
5685
5686 int
5687 put (Board board, int pieceType, int rank, int n, int shade)
5688 // put the piece on the (n-1)-th empty squares of the given shade
5689 {
5690         int i;
5691
5692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5693                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5694                         board[rank][i] = (ChessSquare) pieceType;
5695                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5696                         squaresLeft[ANY]--;
5697                         piecesLeft[pieceType]--;
5698                         return i;
5699                 }
5700         }
5701         return -1;
5702 }
5703
5704
5705 void
5706 AddOnePiece (Board board, int pieceType, int rank, int shade)
5707 // calculate where the next piece goes, (any empty square), and put it there
5708 {
5709         int i;
5710
5711         i = seed % squaresLeft[shade];
5712         nrOfShuffles *= squaresLeft[shade];
5713         seed /= squaresLeft[shade];
5714         put(board, pieceType, rank, i, shade);
5715 }
5716
5717 void
5718 AddTwoPieces (Board board, int pieceType, int rank)
5719 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5720 {
5721         int i, n=squaresLeft[ANY], j=n-1, k;
5722
5723         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5724         i = seed % k;  // pick one
5725         nrOfShuffles *= k;
5726         seed /= k;
5727         while(i >= j) i -= j--;
5728         j = n - 1 - j; i += j;
5729         put(board, pieceType, rank, j, ANY);
5730         put(board, pieceType, rank, i, ANY);
5731 }
5732
5733 void
5734 SetUpShuffle (Board board, int number)
5735 {
5736         int i, p, first=1;
5737
5738         GetPositionNumber(); nrOfShuffles = 1;
5739
5740         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5741         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5742         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5743
5744         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5745
5746         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5747             p = (int) board[0][i];
5748             if(p < (int) BlackPawn) piecesLeft[p] ++;
5749             board[0][i] = EmptySquare;
5750         }
5751
5752         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5753             // shuffles restricted to allow normal castling put KRR first
5754             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5755                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5756             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5757                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5758             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5759                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5760             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5761                 put(board, WhiteRook, 0, 0, ANY);
5762             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5763         }
5764
5765         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5766             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5767             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5768                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5769                 while(piecesLeft[p] >= 2) {
5770                     AddOnePiece(board, p, 0, LITE);
5771                     AddOnePiece(board, p, 0, DARK);
5772                 }
5773                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5774             }
5775
5776         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5777             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5778             // but we leave King and Rooks for last, to possibly obey FRC restriction
5779             if(p == (int)WhiteRook) continue;
5780             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5781             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5782         }
5783
5784         // now everything is placed, except perhaps King (Unicorn) and Rooks
5785
5786         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5787             // Last King gets castling rights
5788             while(piecesLeft[(int)WhiteUnicorn]) {
5789                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5790                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5791             }
5792
5793             while(piecesLeft[(int)WhiteKing]) {
5794                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5795                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5796             }
5797
5798
5799         } else {
5800             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5801             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5802         }
5803
5804         // Only Rooks can be left; simply place them all
5805         while(piecesLeft[(int)WhiteRook]) {
5806                 i = put(board, WhiteRook, 0, 0, ANY);
5807                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5808                         if(first) {
5809                                 first=0;
5810                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5811                         }
5812                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5813                 }
5814         }
5815         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5816             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5817         }
5818
5819         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5820 }
5821
5822 int
5823 SetCharTable (char *table, const char * map)
5824 /* [HGM] moved here from winboard.c because of its general usefulness */
5825 /*       Basically a safe strcpy that uses the last character as King */
5826 {
5827     int result = FALSE; int NrPieces;
5828
5829     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5830                     && NrPieces >= 12 && !(NrPieces&1)) {
5831         int i; /* [HGM] Accept even length from 12 to 34 */
5832
5833         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5834         for( i=0; i<NrPieces/2-1; i++ ) {
5835             table[i] = map[i];
5836             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5837         }
5838         table[(int) WhiteKing]  = map[NrPieces/2-1];
5839         table[(int) BlackKing]  = map[NrPieces-1];
5840
5841         result = TRUE;
5842     }
5843
5844     return result;
5845 }
5846
5847 void
5848 Prelude (Board board)
5849 {       // [HGM] superchess: random selection of exo-pieces
5850         int i, j, k; ChessSquare p;
5851         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5852
5853         GetPositionNumber(); // use FRC position number
5854
5855         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5856             SetCharTable(pieceToChar, appData.pieceToCharTable);
5857             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5858                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5859         }
5860
5861         j = seed%4;                 seed /= 4;
5862         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5863         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5864         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5865         j = seed%3 + (seed%3 >= j); seed /= 3;
5866         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5867         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5868         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5869         j = seed%3;                 seed /= 3;
5870         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
5874         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5878         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5879         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5880         put(board, exoPieces[0],    0, 0, ANY);
5881         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5882 }
5883
5884 void
5885 InitPosition (int redraw)
5886 {
5887     ChessSquare (* pieces)[BOARD_FILES];
5888     int i, j, pawnRow=1, pieceRows=1, overrule,
5889     oldx = gameInfo.boardWidth,
5890     oldy = gameInfo.boardHeight,
5891     oldh = gameInfo.holdingsWidth;
5892     static int oldv;
5893
5894     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5895
5896     /* [AS] Initialize pv info list [HGM] and game status */
5897     {
5898         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5899             pvInfoList[i].depth = 0;
5900             boards[i][EP_STATUS] = EP_NONE;
5901             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5902         }
5903
5904         initialRulePlies = 0; /* 50-move counter start */
5905
5906         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5907         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5908     }
5909
5910
5911     /* [HGM] logic here is completely changed. In stead of full positions */
5912     /* the initialized data only consist of the two backranks. The switch */
5913     /* selects which one we will use, which is than copied to the Board   */
5914     /* initialPosition, which for the rest is initialized by Pawns and    */
5915     /* empty squares. This initial position is then copied to boards[0],  */
5916     /* possibly after shuffling, so that it remains available.            */
5917
5918     gameInfo.holdingsWidth = 0; /* default board sizes */
5919     gameInfo.boardWidth    = 8;
5920     gameInfo.boardHeight   = 8;
5921     gameInfo.holdingsSize  = 0;
5922     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5923     for(i=0; i<BOARD_FILES-2; i++)
5924       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5925     initialPosition[EP_STATUS] = EP_NONE;
5926     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5927     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5928          SetCharTable(pieceNickName, appData.pieceNickNames);
5929     else SetCharTable(pieceNickName, "............");
5930     pieces = FIDEArray;
5931
5932     switch (gameInfo.variant) {
5933     case VariantFischeRandom:
5934       shuffleOpenings = TRUE;
5935     default:
5936       break;
5937     case VariantShatranj:
5938       pieces = ShatranjArray;
5939       nrCastlingRights = 0;
5940       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5941       break;
5942     case VariantMakruk:
5943       pieces = makrukArray;
5944       nrCastlingRights = 0;
5945       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5946       break;
5947     case VariantASEAN:
5948       pieces = aseanArray;
5949       nrCastlingRights = 0;
5950       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5951       break;
5952     case VariantTwoKings:
5953       pieces = twoKingsArray;
5954       break;
5955     case VariantGrand:
5956       pieces = GrandArray;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5959       gameInfo.boardWidth = 10;
5960       gameInfo.boardHeight = 10;
5961       gameInfo.holdingsSize = 7;
5962       break;
5963     case VariantCapaRandom:
5964       shuffleOpenings = TRUE;
5965     case VariantCapablanca:
5966       pieces = CapablancaArray;
5967       gameInfo.boardWidth = 10;
5968       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5969       break;
5970     case VariantGothic:
5971       pieces = GothicArray;
5972       gameInfo.boardWidth = 10;
5973       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5974       break;
5975     case VariantSChess:
5976       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5977       gameInfo.holdingsSize = 7;
5978       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5979       break;
5980     case VariantJanus:
5981       pieces = JanusArray;
5982       gameInfo.boardWidth = 10;
5983       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5984       nrCastlingRights = 6;
5985         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5986         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5987         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5988         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5989         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5990         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5991       break;
5992     case VariantFalcon:
5993       pieces = FalconArray;
5994       gameInfo.boardWidth = 10;
5995       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5996       break;
5997     case VariantXiangqi:
5998       pieces = XiangqiArray;
5999       gameInfo.boardWidth  = 9;
6000       gameInfo.boardHeight = 10;
6001       nrCastlingRights = 0;
6002       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6003       break;
6004     case VariantShogi:
6005       pieces = ShogiArray;
6006       gameInfo.boardWidth  = 9;
6007       gameInfo.boardHeight = 9;
6008       gameInfo.holdingsSize = 7;
6009       nrCastlingRights = 0;
6010       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6011       break;
6012     case VariantChu:
6013       pieces = ChuArray; pieceRows = 3;
6014       gameInfo.boardWidth  = 12;
6015       gameInfo.boardHeight = 12;
6016       nrCastlingRights = 0;
6017       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6018                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6019       break;
6020     case VariantCourier:
6021       pieces = CourierArray;
6022       gameInfo.boardWidth  = 12;
6023       nrCastlingRights = 0;
6024       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6025       break;
6026     case VariantKnightmate:
6027       pieces = KnightmateArray;
6028       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6029       break;
6030     case VariantSpartan:
6031       pieces = SpartanArray;
6032       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6033       break;
6034     case VariantFairy:
6035       pieces = fairyArray;
6036       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6037       break;
6038     case VariantGreat:
6039       pieces = GreatArray;
6040       gameInfo.boardWidth = 10;
6041       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6042       gameInfo.holdingsSize = 8;
6043       break;
6044     case VariantSuper:
6045       pieces = FIDEArray;
6046       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6047       gameInfo.holdingsSize = 8;
6048       startedFromSetupPosition = TRUE;
6049       break;
6050     case VariantCrazyhouse:
6051     case VariantBughouse:
6052       pieces = FIDEArray;
6053       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6054       gameInfo.holdingsSize = 5;
6055       break;
6056     case VariantWildCastle:
6057       pieces = FIDEArray;
6058       /* !!?shuffle with kings guaranteed to be on d or e file */
6059       shuffleOpenings = 1;
6060       break;
6061     case VariantNoCastle:
6062       pieces = FIDEArray;
6063       nrCastlingRights = 0;
6064       /* !!?unconstrained back-rank shuffle */
6065       shuffleOpenings = 1;
6066       break;
6067     }
6068
6069     overrule = 0;
6070     if(appData.NrFiles >= 0) {
6071         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6072         gameInfo.boardWidth = appData.NrFiles;
6073     }
6074     if(appData.NrRanks >= 0) {
6075         gameInfo.boardHeight = appData.NrRanks;
6076     }
6077     if(appData.holdingsSize >= 0) {
6078         i = appData.holdingsSize;
6079         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6080         gameInfo.holdingsSize = i;
6081     }
6082     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6083     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6084         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6085
6086     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6087     if(pawnRow < 1) pawnRow = 1;
6088     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6089     if(gameInfo.variant == VariantChu) pawnRow = 3;
6090
6091     /* User pieceToChar list overrules defaults */
6092     if(appData.pieceToCharTable != NULL)
6093         SetCharTable(pieceToChar, appData.pieceToCharTable);
6094
6095     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6096
6097         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6098             s = (ChessSquare) 0; /* account holding counts in guard band */
6099         for( i=0; i<BOARD_HEIGHT; i++ )
6100             initialPosition[i][j] = s;
6101
6102         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6103         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6104         initialPosition[pawnRow][j] = WhitePawn;
6105         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6106         if(gameInfo.variant == VariantXiangqi) {
6107             if(j&1) {
6108                 initialPosition[pawnRow][j] =
6109                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6110                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6111                    initialPosition[2][j] = WhiteCannon;
6112                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6113                 }
6114             }
6115         }
6116         if(gameInfo.variant == VariantChu) {
6117              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6118                initialPosition[pawnRow+1][j] = WhiteCobra,
6119                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6120              for(i=1; i<pieceRows; i++) {
6121                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6122                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6123              }
6124         }
6125         if(gameInfo.variant == VariantGrand) {
6126             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6127                initialPosition[0][j] = WhiteRook;
6128                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6129             }
6130         }
6131         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6132     }
6133     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6134
6135             j=BOARD_LEFT+1;
6136             initialPosition[1][j] = WhiteBishop;
6137             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6138             j=BOARD_RGHT-2;
6139             initialPosition[1][j] = WhiteRook;
6140             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6141     }
6142
6143     if( nrCastlingRights == -1) {
6144         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6145         /*       This sets default castling rights from none to normal corners   */
6146         /* Variants with other castling rights must set them themselves above    */
6147         nrCastlingRights = 6;
6148
6149         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6150         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6151         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6152         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6153         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6154         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6155      }
6156
6157      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6158      if(gameInfo.variant == VariantGreat) { // promotion commoners
6159         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6160         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6161         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6162         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6163      }
6164      if( gameInfo.variant == VariantSChess ) {
6165       initialPosition[1][0] = BlackMarshall;
6166       initialPosition[2][0] = BlackAngel;
6167       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6168       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6169       initialPosition[1][1] = initialPosition[2][1] =
6170       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6171      }
6172   if (appData.debugMode) {
6173     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6174   }
6175     if(shuffleOpenings) {
6176         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6177         startedFromSetupPosition = TRUE;
6178     }
6179     if(startedFromPositionFile) {
6180       /* [HGM] loadPos: use PositionFile for every new game */
6181       CopyBoard(initialPosition, filePosition);
6182       for(i=0; i<nrCastlingRights; i++)
6183           initialRights[i] = filePosition[CASTLING][i];
6184       startedFromSetupPosition = TRUE;
6185     }
6186
6187     CopyBoard(boards[0], initialPosition);
6188
6189     if(oldx != gameInfo.boardWidth ||
6190        oldy != gameInfo.boardHeight ||
6191        oldv != gameInfo.variant ||
6192        oldh != gameInfo.holdingsWidth
6193                                          )
6194             InitDrawingSizes(-2 ,0);
6195
6196     oldv = gameInfo.variant;
6197     if (redraw)
6198       DrawPosition(TRUE, boards[currentMove]);
6199 }
6200
6201 void
6202 SendBoard (ChessProgramState *cps, int moveNum)
6203 {
6204     char message[MSG_SIZ];
6205
6206     if (cps->useSetboard) {
6207       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6208       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6209       SendToProgram(message, cps);
6210       free(fen);
6211
6212     } else {
6213       ChessSquare *bp;
6214       int i, j, left=0, right=BOARD_WIDTH;
6215       /* Kludge to set black to move, avoiding the troublesome and now
6216        * deprecated "black" command.
6217        */
6218       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6219         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6220
6221       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6222
6223       SendToProgram("edit\n", cps);
6224       SendToProgram("#\n", cps);
6225       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6226         bp = &boards[moveNum][i][left];
6227         for (j = left; j < right; j++, bp++) {
6228           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6229           if ((int) *bp < (int) BlackPawn) {
6230             if(j == BOARD_RGHT+1)
6231                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6232             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6233             if(message[0] == '+' || message[0] == '~') {
6234               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6235                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6236                         AAA + j, ONE + i);
6237             }
6238             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6239                 message[1] = BOARD_RGHT   - 1 - j + '1';
6240                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6241             }
6242             SendToProgram(message, cps);
6243           }
6244         }
6245       }
6246
6247       SendToProgram("c\n", cps);
6248       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6249         bp = &boards[moveNum][i][left];
6250         for (j = left; j < right; j++, bp++) {
6251           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6252           if (((int) *bp != (int) EmptySquare)
6253               && ((int) *bp >= (int) BlackPawn)) {
6254             if(j == BOARD_LEFT-2)
6255                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6256             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6257                     AAA + j, ONE + i);
6258             if(message[0] == '+' || message[0] == '~') {
6259               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6260                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6261                         AAA + j, ONE + i);
6262             }
6263             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6264                 message[1] = BOARD_RGHT   - 1 - j + '1';
6265                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6266             }
6267             SendToProgram(message, cps);
6268           }
6269         }
6270       }
6271
6272       SendToProgram(".\n", cps);
6273     }
6274     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6275 }
6276
6277 char exclusionHeader[MSG_SIZ];
6278 int exCnt, excludePtr;
6279 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6280 static Exclusion excluTab[200];
6281 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6282
6283 static void
6284 WriteMap (int s)
6285 {
6286     int j;
6287     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6288     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6289 }
6290
6291 static void
6292 ClearMap ()
6293 {
6294     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6295     excludePtr = 24; exCnt = 0;
6296     WriteMap(0);
6297 }
6298
6299 static void
6300 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6301 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6302     char buf[2*MOVE_LEN], *p;
6303     Exclusion *e = excluTab;
6304     int i;
6305     for(i=0; i<exCnt; i++)
6306         if(e[i].ff == fromX && e[i].fr == fromY &&
6307            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6308     if(i == exCnt) { // was not in exclude list; add it
6309         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6310         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6311             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6312             return; // abort
6313         }
6314         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6315         excludePtr++; e[i].mark = excludePtr++;
6316         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6317         exCnt++;
6318     }
6319     exclusionHeader[e[i].mark] = state;
6320 }
6321
6322 static int
6323 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6324 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6325     char buf[MSG_SIZ];
6326     int j, k;
6327     ChessMove moveType;
6328     if((signed char)promoChar == -1) { // kludge to indicate best move
6329         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6330             return 1; // if unparsable, abort
6331     }
6332     // update exclusion map (resolving toggle by consulting existing state)
6333     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6334     j = k%8; k >>= 3;
6335     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6336     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6337          excludeMap[k] |=   1<<j;
6338     else excludeMap[k] &= ~(1<<j);
6339     // update header
6340     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6341     // inform engine
6342     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6343     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6344     SendToBoth(buf);
6345     return (state == '+');
6346 }
6347
6348 static void
6349 ExcludeClick (int index)
6350 {
6351     int i, j;
6352     Exclusion *e = excluTab;
6353     if(index < 25) { // none, best or tail clicked
6354         if(index < 13) { // none: include all
6355             WriteMap(0); // clear map
6356             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6357             SendToBoth("include all\n"); // and inform engine
6358         } else if(index > 18) { // tail
6359             if(exclusionHeader[19] == '-') { // tail was excluded
6360                 SendToBoth("include all\n");
6361                 WriteMap(0); // clear map completely
6362                 // now re-exclude selected moves
6363                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6364                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6365             } else { // tail was included or in mixed state
6366                 SendToBoth("exclude all\n");
6367                 WriteMap(0xFF); // fill map completely
6368                 // now re-include selected moves
6369                 j = 0; // count them
6370                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6371                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6372                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6373             }
6374         } else { // best
6375             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6376         }
6377     } else {
6378         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6379             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6380             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6381             break;
6382         }
6383     }
6384 }
6385
6386 ChessSquare
6387 DefaultPromoChoice (int white)
6388 {
6389     ChessSquare result;
6390     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6391        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6392         result = WhiteFerz; // no choice
6393     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6394         result= WhiteKing; // in Suicide Q is the last thing we want
6395     else if(gameInfo.variant == VariantSpartan)
6396         result = white ? WhiteQueen : WhiteAngel;
6397     else result = WhiteQueen;
6398     if(!white) result = WHITE_TO_BLACK result;
6399     return result;
6400 }
6401
6402 static int autoQueen; // [HGM] oneclick
6403
6404 int
6405 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6406 {
6407     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6408     /* [HGM] add Shogi promotions */
6409     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6410     ChessSquare piece;
6411     ChessMove moveType;
6412     Boolean premove;
6413
6414     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6415     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6416
6417     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6418       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6419         return FALSE;
6420
6421     piece = boards[currentMove][fromY][fromX];
6422     if(gameInfo.variant == VariantChu) {
6423         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6424         promotionZoneSize = BOARD_HEIGHT/3;
6425         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteKing;
6426     } else if(gameInfo.variant == VariantShogi) {
6427         promotionZoneSize = BOARD_HEIGHT/3;
6428         highestPromotingPiece = (int)WhiteAlfil;
6429     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6430         promotionZoneSize = 3;
6431     }
6432
6433     // Treat Lance as Pawn when it is not representing Amazon
6434     if(gameInfo.variant != VariantSuper) {
6435         if(piece == WhiteLance) piece = WhitePawn; else
6436         if(piece == BlackLance) piece = BlackPawn;
6437     }
6438
6439     // next weed out all moves that do not touch the promotion zone at all
6440     if((int)piece >= BlackPawn) {
6441         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6442              return FALSE;
6443         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6444     } else {
6445         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6446            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6447     }
6448
6449     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6450
6451     // weed out mandatory Shogi promotions
6452     if(gameInfo.variant == VariantShogi) {
6453         if(piece >= BlackPawn) {
6454             if(toY == 0 && piece == BlackPawn ||
6455                toY == 0 && piece == BlackQueen ||
6456                toY <= 1 && piece == BlackKnight) {
6457                 *promoChoice = '+';
6458                 return FALSE;
6459             }
6460         } else {
6461             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6462                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6463                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6464                 *promoChoice = '+';
6465                 return FALSE;
6466             }
6467         }
6468     }
6469
6470     // weed out obviously illegal Pawn moves
6471     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6472         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6473         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6474         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6475         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6476         // note we are not allowed to test for valid (non-)capture, due to premove
6477     }
6478
6479     // we either have a choice what to promote to, or (in Shogi) whether to promote
6480     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6481        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6482         *promoChoice = PieceToChar(BlackFerz);  // no choice
6483         return FALSE;
6484     }
6485     // no sense asking what we must promote to if it is going to explode...
6486     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6487         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6488         return FALSE;
6489     }
6490     // give caller the default choice even if we will not make it
6491     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6492     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6493     if(        sweepSelect && gameInfo.variant != VariantGreat
6494                            && gameInfo.variant != VariantGrand
6495                            && gameInfo.variant != VariantSuper) return FALSE;
6496     if(autoQueen) return FALSE; // predetermined
6497
6498     // suppress promotion popup on illegal moves that are not premoves
6499     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6500               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6501     if(appData.testLegality && !premove) {
6502         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6503                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6504         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6505             return FALSE;
6506     }
6507
6508     return TRUE;
6509 }
6510
6511 int
6512 InPalace (int row, int column)
6513 {   /* [HGM] for Xiangqi */
6514     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6515          column < (BOARD_WIDTH + 4)/2 &&
6516          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6517     return FALSE;
6518 }
6519
6520 int
6521 PieceForSquare (int x, int y)
6522 {
6523   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6524      return -1;
6525   else
6526      return boards[currentMove][y][x];
6527 }
6528
6529 int
6530 OKToStartUserMove (int x, int y)
6531 {
6532     ChessSquare from_piece;
6533     int white_piece;
6534
6535     if (matchMode) return FALSE;
6536     if (gameMode == EditPosition) return TRUE;
6537
6538     if (x >= 0 && y >= 0)
6539       from_piece = boards[currentMove][y][x];
6540     else
6541       from_piece = EmptySquare;
6542
6543     if (from_piece == EmptySquare) return FALSE;
6544
6545     white_piece = (int)from_piece >= (int)WhitePawn &&
6546       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6547
6548     switch (gameMode) {
6549       case AnalyzeFile:
6550       case TwoMachinesPlay:
6551       case EndOfGame:
6552         return FALSE;
6553
6554       case IcsObserving:
6555       case IcsIdle:
6556         return FALSE;
6557
6558       case MachinePlaysWhite:
6559       case IcsPlayingBlack:
6560         if (appData.zippyPlay) return FALSE;
6561         if (white_piece) {
6562             DisplayMoveError(_("You are playing Black"));
6563             return FALSE;
6564         }
6565         break;
6566
6567       case MachinePlaysBlack:
6568       case IcsPlayingWhite:
6569         if (appData.zippyPlay) return FALSE;
6570         if (!white_piece) {
6571             DisplayMoveError(_("You are playing White"));
6572             return FALSE;
6573         }
6574         break;
6575
6576       case PlayFromGameFile:
6577             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6578       case EditGame:
6579         if (!white_piece && WhiteOnMove(currentMove)) {
6580             DisplayMoveError(_("It is White's turn"));
6581             return FALSE;
6582         }
6583         if (white_piece && !WhiteOnMove(currentMove)) {
6584             DisplayMoveError(_("It is Black's turn"));
6585             return FALSE;
6586         }
6587         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6588             /* Editing correspondence game history */
6589             /* Could disallow this or prompt for confirmation */
6590             cmailOldMove = -1;
6591         }
6592         break;
6593
6594       case BeginningOfGame:
6595         if (appData.icsActive) return FALSE;
6596         if (!appData.noChessProgram) {
6597             if (!white_piece) {
6598                 DisplayMoveError(_("You are playing White"));
6599                 return FALSE;
6600             }
6601         }
6602         break;
6603
6604       case Training:
6605         if (!white_piece && WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is White's turn"));
6607             return FALSE;
6608         }
6609         if (white_piece && !WhiteOnMove(currentMove)) {
6610             DisplayMoveError(_("It is Black's turn"));
6611             return FALSE;
6612         }
6613         break;
6614
6615       default:
6616       case IcsExamining:
6617         break;
6618     }
6619     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6620         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6621         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6622         && gameMode != AnalyzeFile && gameMode != Training) {
6623         DisplayMoveError(_("Displayed position is not current"));
6624         return FALSE;
6625     }
6626     return TRUE;
6627 }
6628
6629 Boolean
6630 OnlyMove (int *x, int *y, Boolean captures)
6631 {
6632     DisambiguateClosure cl;
6633     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6634     switch(gameMode) {
6635       case MachinePlaysBlack:
6636       case IcsPlayingWhite:
6637       case BeginningOfGame:
6638         if(!WhiteOnMove(currentMove)) return FALSE;
6639         break;
6640       case MachinePlaysWhite:
6641       case IcsPlayingBlack:
6642         if(WhiteOnMove(currentMove)) return FALSE;
6643         break;
6644       case EditGame:
6645         break;
6646       default:
6647         return FALSE;
6648     }
6649     cl.pieceIn = EmptySquare;
6650     cl.rfIn = *y;
6651     cl.ffIn = *x;
6652     cl.rtIn = -1;
6653     cl.ftIn = -1;
6654     cl.promoCharIn = NULLCHAR;
6655     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6656     if( cl.kind == NormalMove ||
6657         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6658         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6659         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6660       fromX = cl.ff;
6661       fromY = cl.rf;
6662       *x = cl.ft;
6663       *y = cl.rt;
6664       return TRUE;
6665     }
6666     if(cl.kind != ImpossibleMove) return FALSE;
6667     cl.pieceIn = EmptySquare;
6668     cl.rfIn = -1;
6669     cl.ffIn = -1;
6670     cl.rtIn = *y;
6671     cl.ftIn = *x;
6672     cl.promoCharIn = NULLCHAR;
6673     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6674     if( cl.kind == NormalMove ||
6675         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6676         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6677         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6678       fromX = cl.ff;
6679       fromY = cl.rf;
6680       *x = cl.ft;
6681       *y = cl.rt;
6682       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6683       return TRUE;
6684     }
6685     return FALSE;
6686 }
6687
6688 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6689 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6690 int lastLoadGameUseList = FALSE;
6691 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6692 ChessMove lastLoadGameStart = EndOfFile;
6693 int doubleClick;
6694
6695 void
6696 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6697 {
6698     ChessMove moveType;
6699     ChessSquare pup;
6700     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6701
6702     /* Check if the user is playing in turn.  This is complicated because we
6703        let the user "pick up" a piece before it is his turn.  So the piece he
6704        tried to pick up may have been captured by the time he puts it down!
6705        Therefore we use the color the user is supposed to be playing in this
6706        test, not the color of the piece that is currently on the starting
6707        square---except in EditGame mode, where the user is playing both
6708        sides; fortunately there the capture race can't happen.  (It can
6709        now happen in IcsExamining mode, but that's just too bad.  The user
6710        will get a somewhat confusing message in that case.)
6711        */
6712
6713     switch (gameMode) {
6714       case AnalyzeFile:
6715       case TwoMachinesPlay:
6716       case EndOfGame:
6717       case IcsObserving:
6718       case IcsIdle:
6719         /* We switched into a game mode where moves are not accepted,
6720            perhaps while the mouse button was down. */
6721         return;
6722
6723       case MachinePlaysWhite:
6724         /* User is moving for Black */
6725         if (WhiteOnMove(currentMove)) {
6726             DisplayMoveError(_("It is White's turn"));
6727             return;
6728         }
6729         break;
6730
6731       case MachinePlaysBlack:
6732         /* User is moving for White */
6733         if (!WhiteOnMove(currentMove)) {
6734             DisplayMoveError(_("It is Black's turn"));
6735             return;
6736         }
6737         break;
6738
6739       case PlayFromGameFile:
6740             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6741       case EditGame:
6742       case IcsExamining:
6743       case BeginningOfGame:
6744       case AnalyzeMode:
6745       case Training:
6746         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6747         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6748             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6749             /* User is moving for Black */
6750             if (WhiteOnMove(currentMove)) {
6751                 DisplayMoveError(_("It is White's turn"));
6752                 return;
6753             }
6754         } else {
6755             /* User is moving for White */
6756             if (!WhiteOnMove(currentMove)) {
6757                 DisplayMoveError(_("It is Black's turn"));
6758                 return;
6759             }
6760         }
6761         break;
6762
6763       case IcsPlayingBlack:
6764         /* User is moving for Black */
6765         if (WhiteOnMove(currentMove)) {
6766             if (!appData.premove) {
6767                 DisplayMoveError(_("It is White's turn"));
6768             } else if (toX >= 0 && toY >= 0) {
6769                 premoveToX = toX;
6770                 premoveToY = toY;
6771                 premoveFromX = fromX;
6772                 premoveFromY = fromY;
6773                 premovePromoChar = promoChar;
6774                 gotPremove = 1;
6775                 if (appData.debugMode)
6776                     fprintf(debugFP, "Got premove: fromX %d,"
6777                             "fromY %d, toX %d, toY %d\n",
6778                             fromX, fromY, toX, toY);
6779             }
6780             return;
6781         }
6782         break;
6783
6784       case IcsPlayingWhite:
6785         /* User is moving for White */
6786         if (!WhiteOnMove(currentMove)) {
6787             if (!appData.premove) {
6788                 DisplayMoveError(_("It is Black's turn"));
6789             } else if (toX >= 0 && toY >= 0) {
6790                 premoveToX = toX;
6791                 premoveToY = toY;
6792                 premoveFromX = fromX;
6793                 premoveFromY = fromY;
6794                 premovePromoChar = promoChar;
6795                 gotPremove = 1;
6796                 if (appData.debugMode)
6797                     fprintf(debugFP, "Got premove: fromX %d,"
6798                             "fromY %d, toX %d, toY %d\n",
6799                             fromX, fromY, toX, toY);
6800             }
6801             return;
6802         }
6803         break;
6804
6805       default:
6806         break;
6807
6808       case EditPosition:
6809         /* EditPosition, empty square, or different color piece;
6810            click-click move is possible */
6811         if (toX == -2 || toY == -2) {
6812             boards[0][fromY][fromX] = EmptySquare;
6813             DrawPosition(FALSE, boards[currentMove]);
6814             return;
6815         } else if (toX >= 0 && toY >= 0) {
6816             boards[0][toY][toX] = boards[0][fromY][fromX];
6817             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6818                 if(boards[0][fromY][0] != EmptySquare) {
6819                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6820                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6821                 }
6822             } else
6823             if(fromX == BOARD_RGHT+1) {
6824                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6825                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6826                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6827                 }
6828             } else
6829             boards[0][fromY][fromX] = gatingPiece;
6830             DrawPosition(FALSE, boards[currentMove]);
6831             return;
6832         }
6833         return;
6834     }
6835
6836     if(toX < 0 || toY < 0) return;
6837     pup = boards[currentMove][toY][toX];
6838
6839     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6840     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6841          if( pup != EmptySquare ) return;
6842          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6843            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6844                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6845            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6846            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6847            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6848            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6849          fromY = DROP_RANK;
6850     }
6851
6852     /* [HGM] always test for legality, to get promotion info */
6853     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6854                                          fromY, fromX, toY, toX, promoChar);
6855
6856     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6857
6858     /* [HGM] but possibly ignore an IllegalMove result */
6859     if (appData.testLegality) {
6860         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6861             DisplayMoveError(_("Illegal move"));
6862             return;
6863         }
6864     }
6865
6866     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6867         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6868              ClearPremoveHighlights(); // was included
6869         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6870         return;
6871     }
6872
6873     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6874 }
6875
6876 /* Common tail of UserMoveEvent and DropMenuEvent */
6877 int
6878 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6879 {
6880     char *bookHit = 0;
6881
6882     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6883         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6884         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6885         if(WhiteOnMove(currentMove)) {
6886             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6887         } else {
6888             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6889         }
6890     }
6891
6892     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6893        move type in caller when we know the move is a legal promotion */
6894     if(moveType == NormalMove && promoChar)
6895         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6896
6897     /* [HGM] <popupFix> The following if has been moved here from
6898        UserMoveEvent(). Because it seemed to belong here (why not allow
6899        piece drops in training games?), and because it can only be
6900        performed after it is known to what we promote. */
6901     if (gameMode == Training) {
6902       /* compare the move played on the board to the next move in the
6903        * game. If they match, display the move and the opponent's response.
6904        * If they don't match, display an error message.
6905        */
6906       int saveAnimate;
6907       Board testBoard;
6908       CopyBoard(testBoard, boards[currentMove]);
6909       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6910
6911       if (CompareBoards(testBoard, boards[currentMove+1])) {
6912         ForwardInner(currentMove+1);
6913
6914         /* Autoplay the opponent's response.
6915          * if appData.animate was TRUE when Training mode was entered,
6916          * the response will be animated.
6917          */
6918         saveAnimate = appData.animate;
6919         appData.animate = animateTraining;
6920         ForwardInner(currentMove+1);
6921         appData.animate = saveAnimate;
6922
6923         /* check for the end of the game */
6924         if (currentMove >= forwardMostMove) {
6925           gameMode = PlayFromGameFile;
6926           ModeHighlight();
6927           SetTrainingModeOff();
6928           DisplayInformation(_("End of game"));
6929         }
6930       } else {
6931         DisplayError(_("Incorrect move"), 0);
6932       }
6933       return 1;
6934     }
6935
6936   /* Ok, now we know that the move is good, so we can kill
6937      the previous line in Analysis Mode */
6938   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6939                                 && currentMove < forwardMostMove) {
6940     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6941     else forwardMostMove = currentMove;
6942   }
6943
6944   ClearMap();
6945
6946   /* If we need the chess program but it's dead, restart it */
6947   ResurrectChessProgram();
6948
6949   /* A user move restarts a paused game*/
6950   if (pausing)
6951     PauseEvent();
6952
6953   thinkOutput[0] = NULLCHAR;
6954
6955   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6956
6957   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6958     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6959     return 1;
6960   }
6961
6962   if (gameMode == BeginningOfGame) {
6963     if (appData.noChessProgram) {
6964       gameMode = EditGame;
6965       SetGameInfo();
6966     } else {
6967       char buf[MSG_SIZ];
6968       gameMode = MachinePlaysBlack;
6969       StartClocks();
6970       SetGameInfo();
6971       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6972       DisplayTitle(buf);
6973       if (first.sendName) {
6974         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6975         SendToProgram(buf, &first);
6976       }
6977       StartClocks();
6978     }
6979     ModeHighlight();
6980   }
6981
6982   /* Relay move to ICS or chess engine */
6983   if (appData.icsActive) {
6984     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6985         gameMode == IcsExamining) {
6986       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6987         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6988         SendToICS("draw ");
6989         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6990       }
6991       // also send plain move, in case ICS does not understand atomic claims
6992       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6993       ics_user_moved = 1;
6994     }
6995   } else {
6996     if (first.sendTime && (gameMode == BeginningOfGame ||
6997                            gameMode == MachinePlaysWhite ||
6998                            gameMode == MachinePlaysBlack)) {
6999       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7000     }
7001     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7002          // [HGM] book: if program might be playing, let it use book
7003         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7004         first.maybeThinking = TRUE;
7005     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7006         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7007         SendBoard(&first, currentMove+1);
7008         if(second.analyzing) {
7009             if(!second.useSetboard) SendToProgram("undo\n", &second);
7010             SendBoard(&second, currentMove+1);
7011         }
7012     } else {
7013         SendMoveToProgram(forwardMostMove-1, &first);
7014         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7015     }
7016     if (currentMove == cmailOldMove + 1) {
7017       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7018     }
7019   }
7020
7021   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7022
7023   switch (gameMode) {
7024   case EditGame:
7025     if(appData.testLegality)
7026     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7027     case MT_NONE:
7028     case MT_CHECK:
7029       break;
7030     case MT_CHECKMATE:
7031     case MT_STAINMATE:
7032       if (WhiteOnMove(currentMove)) {
7033         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7034       } else {
7035         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7036       }
7037       break;
7038     case MT_STALEMATE:
7039       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7040       break;
7041     }
7042     break;
7043
7044   case MachinePlaysBlack:
7045   case MachinePlaysWhite:
7046     /* disable certain menu options while machine is thinking */
7047     SetMachineThinkingEnables();
7048     break;
7049
7050   default:
7051     break;
7052   }
7053
7054   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7055   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7056
7057   if(bookHit) { // [HGM] book: simulate book reply
7058         static char bookMove[MSG_SIZ]; // a bit generous?
7059
7060         programStats.nodes = programStats.depth = programStats.time =
7061         programStats.score = programStats.got_only_move = 0;
7062         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7063
7064         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7065         strcat(bookMove, bookHit);
7066         HandleMachineMove(bookMove, &first);
7067   }
7068   return 1;
7069 }
7070
7071 void
7072 MarkByFEN(char *fen)
7073 {
7074         int r, f;
7075         if(!appData.markers || !appData.highlightDragging) return;
7076         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7077         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7078         while(*fen) {
7079             int s = 0;
7080             marker[r][f] = 0;
7081             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7082             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7083             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7084             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7085             if(*fen == 'T') marker[r][f++] = 0; else
7086             if(*fen == 'Y') marker[r][f++] = 1; else
7087             if(*fen == 'G') marker[r][f++] = 3; else
7088             if(*fen == 'B') marker[r][f++] = 4; else
7089             if(*fen == 'C') marker[r][f++] = 5; else
7090             if(*fen == 'M') marker[r][f++] = 6; else
7091             if(*fen == 'W') marker[r][f++] = 7; else
7092             if(*fen == 'D') marker[r][f++] = 8; else
7093             if(*fen == 'R') marker[r][f++] = 2; else {
7094                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7095               f += s; fen -= s>0;
7096             }
7097             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7098             if(r < 0) break;
7099             fen++;
7100         }
7101         DrawPosition(TRUE, NULL);
7102 }
7103
7104 void
7105 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7106 {
7107     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7108     Markers *m = (Markers *) closure;
7109     if(rf == fromY && ff == fromX)
7110         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7111                          || kind == WhiteCapturesEnPassant
7112                          || kind == BlackCapturesEnPassant);
7113     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7114 }
7115
7116 void
7117 MarkTargetSquares (int clear)
7118 {
7119   int x, y, sum=0;
7120   if(clear) { // no reason to ever suppress clearing
7121     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7122     if(!sum) return; // nothing was cleared,no redraw needed
7123   } else {
7124     int capt = 0;
7125     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7126        !appData.testLegality || gameMode == EditPosition) return;
7127     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7128     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7129       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7130       if(capt)
7131       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7132     }
7133   }
7134   DrawPosition(FALSE, NULL);
7135 }
7136
7137 int
7138 Explode (Board board, int fromX, int fromY, int toX, int toY)
7139 {
7140     if(gameInfo.variant == VariantAtomic &&
7141        (board[toY][toX] != EmptySquare ||                     // capture?
7142         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7143                          board[fromY][fromX] == BlackPawn   )
7144       )) {
7145         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7146         return TRUE;
7147     }
7148     return FALSE;
7149 }
7150
7151 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7152
7153 int
7154 CanPromote (ChessSquare piece, int y)
7155 {
7156         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7157         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7158         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7159            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7160            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7161          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7162         return (piece == BlackPawn && y == 1 ||
7163                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7164                 piece == BlackLance && y == 1 ||
7165                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7166 }
7167
7168 void
7169 HoverEvent (int xPix, int yPix, int x, int y)
7170 {
7171         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7172         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7173         int r, f;
7174         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7175         if(!first.highlight) return;
7176         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7177         if(x == oldX && y == oldY) return; // only do something if we enter new square
7178         oldFromX = fromX; oldFromY = fromY;
7179         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7180           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7181             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7182         else if(oldX != x || oldY != y) {
7183           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7184           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7185             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7186           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7187             char buf[MSG_SIZ];
7188             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7189             SendToProgram(buf, &first);
7190           }
7191           oldX = x; oldY = y;
7192 //        SetHighlights(fromX, fromY, x, y);
7193         }
7194 }
7195
7196 void ReportClick(char *action, int x, int y)
7197 {
7198         char buf[MSG_SIZ]; // Inform engine of what user does
7199         int r, f;
7200         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7201           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7202         if(!first.highlight || gameMode == EditPosition) return;
7203         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7204         SendToProgram(buf, &first);
7205 }
7206
7207 void
7208 LeftClick (ClickType clickType, int xPix, int yPix)
7209 {
7210     int x, y;
7211     Boolean saveAnimate;
7212     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7213     char promoChoice = NULLCHAR;
7214     ChessSquare piece;
7215     static TimeMark lastClickTime, prevClickTime;
7216
7217     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7218
7219     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7220
7221     if (clickType == Press) ErrorPopDown();
7222     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7223
7224     x = EventToSquare(xPix, BOARD_WIDTH);
7225     y = EventToSquare(yPix, BOARD_HEIGHT);
7226     if (!flipView && y >= 0) {
7227         y = BOARD_HEIGHT - 1 - y;
7228     }
7229     if (flipView && x >= 0) {
7230         x = BOARD_WIDTH - 1 - x;
7231     }
7232
7233     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7234         defaultPromoChoice = promoSweep;
7235         promoSweep = EmptySquare;   // terminate sweep
7236         promoDefaultAltered = TRUE;
7237         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7238     }
7239
7240     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7241         if(clickType == Release) return; // ignore upclick of click-click destination
7242         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7243         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7244         if(gameInfo.holdingsWidth &&
7245                 (WhiteOnMove(currentMove)
7246                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7247                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7248             // click in right holdings, for determining promotion piece
7249             ChessSquare p = boards[currentMove][y][x];
7250             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7251             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7252             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7253                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7254                 fromX = fromY = -1;
7255                 return;
7256             }
7257         }
7258         DrawPosition(FALSE, boards[currentMove]);
7259         return;
7260     }
7261
7262     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7263     if(clickType == Press
7264             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7265               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7266               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7267         return;
7268
7269     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7270         // could be static click on premove from-square: abort premove
7271         gotPremove = 0;
7272         ClearPremoveHighlights();
7273     }
7274
7275     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7276         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7277
7278     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7279         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7280                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7281         defaultPromoChoice = DefaultPromoChoice(side);
7282     }
7283
7284     autoQueen = appData.alwaysPromoteToQueen;
7285
7286     if (fromX == -1) {
7287       int originalY = y;
7288       gatingPiece = EmptySquare;
7289       if (clickType != Press) {
7290         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7291             DragPieceEnd(xPix, yPix); dragging = 0;
7292             DrawPosition(FALSE, NULL);
7293         }
7294         return;
7295       }
7296       doubleClick = FALSE;
7297       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7298         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7299       }
7300       fromX = x; fromY = y; toX = toY = -1;
7301       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7302          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7303          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7304             /* First square */
7305             if (OKToStartUserMove(fromX, fromY)) {
7306                 second = 0;
7307                 ReportClick("lift", x, y);
7308                 MarkTargetSquares(0);
7309                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7310                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7311                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7312                     promoSweep = defaultPromoChoice;
7313                     selectFlag = 0; lastX = xPix; lastY = yPix;
7314                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7315                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7316                 }
7317                 if (appData.highlightDragging) {
7318                     SetHighlights(fromX, fromY, -1, -1);
7319                 } else {
7320                     ClearHighlights();
7321                 }
7322             } else fromX = fromY = -1;
7323             return;
7324         }
7325     }
7326
7327     /* fromX != -1 */
7328     if (clickType == Press && gameMode != EditPosition && killX < 0) {
7329         ChessSquare fromP;
7330         ChessSquare toP;
7331         int frc;
7332
7333         // ignore off-board to clicks
7334         if(y < 0 || x < 0) return;
7335
7336         /* Check if clicking again on the same color piece */
7337         fromP = boards[currentMove][fromY][fromX];
7338         toP = boards[currentMove][y][x];
7339         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7340         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7341              WhitePawn <= toP && toP <= WhiteKing &&
7342              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7343              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7344             (BlackPawn <= fromP && fromP <= BlackKing &&
7345              BlackPawn <= toP && toP <= BlackKing &&
7346              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7347              !(fromP == BlackKing && toP == BlackRook && frc))) {
7348             /* Clicked again on same color piece -- changed his mind */
7349             second = (x == fromX && y == fromY);
7350             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7351                 second = FALSE; // first double-click rather than scond click
7352                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7353             }
7354             promoDefaultAltered = FALSE;
7355             MarkTargetSquares(1);
7356            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7357             if (appData.highlightDragging) {
7358                 SetHighlights(x, y, -1, -1);
7359             } else {
7360                 ClearHighlights();
7361             }
7362             if (OKToStartUserMove(x, y)) {
7363                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7364                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7365                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7366                  gatingPiece = boards[currentMove][fromY][fromX];
7367                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7368                 fromX = x;
7369                 fromY = y; dragging = 1;
7370                 ReportClick("lift", x, y);
7371                 MarkTargetSquares(0);
7372                 DragPieceBegin(xPix, yPix, FALSE);
7373                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7374                     promoSweep = defaultPromoChoice;
7375                     selectFlag = 0; lastX = xPix; lastY = yPix;
7376                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7377                 }
7378             }
7379            }
7380            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7381            second = FALSE;
7382         }
7383         // ignore clicks on holdings
7384         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7385     }
7386
7387     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7388         DragPieceEnd(xPix, yPix); dragging = 0;
7389         if(clearFlag) {
7390             // a deferred attempt to click-click move an empty square on top of a piece
7391             boards[currentMove][y][x] = EmptySquare;
7392             ClearHighlights();
7393             DrawPosition(FALSE, boards[currentMove]);
7394             fromX = fromY = -1; clearFlag = 0;
7395             return;
7396         }
7397         if (appData.animateDragging) {
7398             /* Undo animation damage if any */
7399             DrawPosition(FALSE, NULL);
7400         }
7401         if (second || sweepSelecting) {
7402             /* Second up/down in same square; just abort move */
7403             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7404             second = sweepSelecting = 0;
7405             fromX = fromY = -1;
7406             gatingPiece = EmptySquare;
7407             MarkTargetSquares(1);
7408             ClearHighlights();
7409             gotPremove = 0;
7410             ClearPremoveHighlights();
7411         } else {
7412             /* First upclick in same square; start click-click mode */
7413             SetHighlights(x, y, -1, -1);
7414         }
7415         return;
7416     }
7417
7418     clearFlag = 0;
7419
7420     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY)) {
7421         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7422         DisplayMessage(_("only marked squares are legal"),"");
7423         DrawPosition(TRUE, NULL);
7424         return; // ignore to-click
7425     }
7426
7427     /* we now have a different from- and (possibly off-board) to-square */
7428     /* Completed move */
7429     if(!sweepSelecting) {
7430         toX = x;
7431         toY = y;
7432     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7433
7434     saveAnimate = appData.animate;
7435     if (clickType == Press) {
7436         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7437             // must be Edit Position mode with empty-square selected
7438             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7439             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7440             return;
7441         }
7442         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7443             dragging = 1;
7444             return;
7445         }
7446         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7447             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7448         } else
7449         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7450         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7451           if(appData.sweepSelect) {
7452             ChessSquare piece = boards[currentMove][fromY][fromX];
7453             promoSweep = defaultPromoChoice;
7454             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7455             selectFlag = 0; lastX = xPix; lastY = yPix;
7456             Sweep(0); // Pawn that is going to promote: preview promotion piece
7457             sweepSelecting = 1;
7458             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7459             MarkTargetSquares(1);
7460           }
7461           return; // promo popup appears on up-click
7462         }
7463         /* Finish clickclick move */
7464         if (appData.animate || appData.highlightLastMove) {
7465             SetHighlights(fromX, fromY, toX, toY);
7466         } else {
7467             ClearHighlights();
7468         }
7469     } else {
7470 #if 0
7471 // [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
7472         /* Finish drag move */
7473         if (appData.highlightLastMove) {
7474             SetHighlights(fromX, fromY, toX, toY);
7475         } else {
7476             ClearHighlights();
7477         }
7478 #endif
7479         if(!dragging || marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7480           dragging *= 2;            // flag button-less dragging if we are dragging
7481           MarkTargetSquares(1);
7482           if(x == killX && y == killY) killX = killY = -1; else {
7483             killX = x; killY = y;     //remeber this square as intermediate
7484             ReportClick("put", x, y); // and inform engine
7485             ReportClick("lift", x, y);
7486             return;
7487           }
7488         }
7489         DragPieceEnd(xPix, yPix); dragging = 0;
7490         /* Don't animate move and drag both */
7491         appData.animate = FALSE;
7492     }
7493
7494     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7495     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7496         ChessSquare piece = boards[currentMove][fromY][fromX];
7497         if(gameMode == EditPosition && piece != EmptySquare &&
7498            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7499             int n;
7500
7501             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7502                 n = PieceToNumber(piece - (int)BlackPawn);
7503                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7504                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7505                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7506             } else
7507             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7508                 n = PieceToNumber(piece);
7509                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7510                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7511                 boards[currentMove][n][BOARD_WIDTH-2]++;
7512             }
7513             boards[currentMove][fromY][fromX] = EmptySquare;
7514         }
7515         ClearHighlights();
7516         fromX = fromY = -1;
7517         MarkTargetSquares(1);
7518         DrawPosition(TRUE, boards[currentMove]);
7519         return;
7520     }
7521
7522     // off-board moves should not be highlighted
7523     if(x < 0 || y < 0) ClearHighlights();
7524     else ReportClick("put", x, y);
7525
7526     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7527
7528     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7529         SetHighlights(fromX, fromY, toX, toY);
7530         MarkTargetSquares(1);
7531         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7532             // [HGM] super: promotion to captured piece selected from holdings
7533             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7534             promotionChoice = TRUE;
7535             // kludge follows to temporarily execute move on display, without promoting yet
7536             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7537             boards[currentMove][toY][toX] = p;
7538             DrawPosition(FALSE, boards[currentMove]);
7539             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7540             boards[currentMove][toY][toX] = q;
7541             DisplayMessage("Click in holdings to choose piece", "");
7542             return;
7543         }
7544         PromotionPopUp();
7545     } else {
7546         int oldMove = currentMove;
7547         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7548         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7549         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7550         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7551            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7552             DrawPosition(TRUE, boards[currentMove]);
7553         MarkTargetSquares(1);
7554         fromX = fromY = -1;
7555     }
7556     appData.animate = saveAnimate;
7557     if (appData.animate || appData.animateDragging) {
7558         /* Undo animation damage if needed */
7559         DrawPosition(FALSE, NULL);
7560     }
7561 }
7562
7563 int
7564 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7565 {   // front-end-free part taken out of PieceMenuPopup
7566     int whichMenu; int xSqr, ySqr;
7567
7568     if(seekGraphUp) { // [HGM] seekgraph
7569         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7570         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7571         return -2;
7572     }
7573
7574     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7575          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7576         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7577         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7578         if(action == Press)   {
7579             originalFlip = flipView;
7580             flipView = !flipView; // temporarily flip board to see game from partners perspective
7581             DrawPosition(TRUE, partnerBoard);
7582             DisplayMessage(partnerStatus, "");
7583             partnerUp = TRUE;
7584         } else if(action == Release) {
7585             flipView = originalFlip;
7586             DrawPosition(TRUE, boards[currentMove]);
7587             partnerUp = FALSE;
7588         }
7589         return -2;
7590     }
7591
7592     xSqr = EventToSquare(x, BOARD_WIDTH);
7593     ySqr = EventToSquare(y, BOARD_HEIGHT);
7594     if (action == Release) {
7595         if(pieceSweep != EmptySquare) {
7596             EditPositionMenuEvent(pieceSweep, toX, toY);
7597             pieceSweep = EmptySquare;
7598         } else UnLoadPV(); // [HGM] pv
7599     }
7600     if (action != Press) return -2; // return code to be ignored
7601     switch (gameMode) {
7602       case IcsExamining:
7603         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7604       case EditPosition:
7605         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7606         if (xSqr < 0 || ySqr < 0) return -1;
7607         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7608         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7609         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7610         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7611         NextPiece(0);
7612         return 2; // grab
7613       case IcsObserving:
7614         if(!appData.icsEngineAnalyze) return -1;
7615       case IcsPlayingWhite:
7616       case IcsPlayingBlack:
7617         if(!appData.zippyPlay) goto noZip;
7618       case AnalyzeMode:
7619       case AnalyzeFile:
7620       case MachinePlaysWhite:
7621       case MachinePlaysBlack:
7622       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7623         if (!appData.dropMenu) {
7624           LoadPV(x, y);
7625           return 2; // flag front-end to grab mouse events
7626         }
7627         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7628            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7629       case EditGame:
7630       noZip:
7631         if (xSqr < 0 || ySqr < 0) return -1;
7632         if (!appData.dropMenu || appData.testLegality &&
7633             gameInfo.variant != VariantBughouse &&
7634             gameInfo.variant != VariantCrazyhouse) return -1;
7635         whichMenu = 1; // drop menu
7636         break;
7637       default:
7638         return -1;
7639     }
7640
7641     if (((*fromX = xSqr) < 0) ||
7642         ((*fromY = ySqr) < 0)) {
7643         *fromX = *fromY = -1;
7644         return -1;
7645     }
7646     if (flipView)
7647       *fromX = BOARD_WIDTH - 1 - *fromX;
7648     else
7649       *fromY = BOARD_HEIGHT - 1 - *fromY;
7650
7651     return whichMenu;
7652 }
7653
7654 void
7655 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7656 {
7657 //    char * hint = lastHint;
7658     FrontEndProgramStats stats;
7659
7660     stats.which = cps == &first ? 0 : 1;
7661     stats.depth = cpstats->depth;
7662     stats.nodes = cpstats->nodes;
7663     stats.score = cpstats->score;
7664     stats.time = cpstats->time;
7665     stats.pv = cpstats->movelist;
7666     stats.hint = lastHint;
7667     stats.an_move_index = 0;
7668     stats.an_move_count = 0;
7669
7670     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7671         stats.hint = cpstats->move_name;
7672         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7673         stats.an_move_count = cpstats->nr_moves;
7674     }
7675
7676     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
7677
7678     SetProgramStats( &stats );
7679 }
7680
7681 void
7682 ClearEngineOutputPane (int which)
7683 {
7684     static FrontEndProgramStats dummyStats;
7685     dummyStats.which = which;
7686     dummyStats.pv = "#";
7687     SetProgramStats( &dummyStats );
7688 }
7689
7690 #define MAXPLAYERS 500
7691
7692 char *
7693 TourneyStandings (int display)
7694 {
7695     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7696     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7697     char result, *p, *names[MAXPLAYERS];
7698
7699     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7700         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7701     names[0] = p = strdup(appData.participants);
7702     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7703
7704     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7705
7706     while(result = appData.results[nr]) {
7707         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7708         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7709         wScore = bScore = 0;
7710         switch(result) {
7711           case '+': wScore = 2; break;
7712           case '-': bScore = 2; break;
7713           case '=': wScore = bScore = 1; break;
7714           case ' ':
7715           case '*': return strdup("busy"); // tourney not finished
7716         }
7717         score[w] += wScore;
7718         score[b] += bScore;
7719         games[w]++;
7720         games[b]++;
7721         nr++;
7722     }
7723     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7724     for(w=0; w<nPlayers; w++) {
7725         bScore = -1;
7726         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7727         ranking[w] = b; points[w] = bScore; score[b] = -2;
7728     }
7729     p = malloc(nPlayers*34+1);
7730     for(w=0; w<nPlayers && w<display; w++)
7731         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7732     free(names[0]);
7733     return p;
7734 }
7735
7736 void
7737 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7738 {       // count all piece types
7739         int p, f, r;
7740         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7741         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7742         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7743                 p = board[r][f];
7744                 pCnt[p]++;
7745                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7746                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7747                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7748                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7749                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7750                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7751         }
7752 }
7753
7754 int
7755 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7756 {
7757         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7758         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7759
7760         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7761         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7762         if(myPawns == 2 && nMine == 3) // KPP
7763             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7764         if(myPawns == 1 && nMine == 2) // KP
7765             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7766         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7767             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7768         if(myPawns) return FALSE;
7769         if(pCnt[WhiteRook+side])
7770             return pCnt[BlackRook-side] ||
7771                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7772                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7773                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7774         if(pCnt[WhiteCannon+side]) {
7775             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7776             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7777         }
7778         if(pCnt[WhiteKnight+side])
7779             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7780         return FALSE;
7781 }
7782
7783 int
7784 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7785 {
7786         VariantClass v = gameInfo.variant;
7787
7788         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7789         if(v == VariantShatranj) return TRUE; // always winnable through baring
7790         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7791         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7792
7793         if(v == VariantXiangqi) {
7794                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7795
7796                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7797                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7798                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7799                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7800                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7801                 if(stale) // we have at least one last-rank P plus perhaps C
7802                     return majors // KPKX
7803                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7804                 else // KCA*E*
7805                     return pCnt[WhiteFerz+side] // KCAK
7806                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7807                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7808                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7809
7810         } else if(v == VariantKnightmate) {
7811                 if(nMine == 1) return FALSE;
7812                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7813         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7814                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7815
7816                 if(nMine == 1) return FALSE; // bare King
7817                 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
7818                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7819                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7820                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7821                 if(pCnt[WhiteKnight+side])
7822                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7823                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7824                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7825                 if(nBishops)
7826                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7827                 if(pCnt[WhiteAlfil+side])
7828                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7829                 if(pCnt[WhiteWazir+side])
7830                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7831         }
7832
7833         return TRUE;
7834 }
7835
7836 int
7837 CompareWithRights (Board b1, Board b2)
7838 {
7839     int rights = 0;
7840     if(!CompareBoards(b1, b2)) return FALSE;
7841     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7842     /* compare castling rights */
7843     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7844            rights++; /* King lost rights, while rook still had them */
7845     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7846         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7847            rights++; /* but at least one rook lost them */
7848     }
7849     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7850            rights++;
7851     if( b1[CASTLING][5] != NoRights ) {
7852         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7853            rights++;
7854     }
7855     return rights == 0;
7856 }
7857
7858 int
7859 Adjudicate (ChessProgramState *cps)
7860 {       // [HGM] some adjudications useful with buggy engines
7861         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7862         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7863         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7864         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7865         int k, drop, count = 0; static int bare = 1;
7866         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7867         Boolean canAdjudicate = !appData.icsActive;
7868
7869         // most tests only when we understand the game, i.e. legality-checking on
7870             if( appData.testLegality )
7871             {   /* [HGM] Some more adjudications for obstinate engines */
7872                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7873                 static int moveCount = 6;
7874                 ChessMove result;
7875                 char *reason = NULL;
7876
7877                 /* Count what is on board. */
7878                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7879
7880                 /* Some material-based adjudications that have to be made before stalemate test */
7881                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7882                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7883                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7884                      if(canAdjudicate && appData.checkMates) {
7885                          if(engineOpponent)
7886                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7887                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7888                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7889                          return 1;
7890                      }
7891                 }
7892
7893                 /* Bare King in Shatranj (loses) or Losers (wins) */
7894                 if( nrW == 1 || nrB == 1) {
7895                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7896                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7897                      if(canAdjudicate && appData.checkMates) {
7898                          if(engineOpponent)
7899                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7900                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7901                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7902                          return 1;
7903                      }
7904                   } else
7905                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7906                   {    /* bare King */
7907                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7908                         if(canAdjudicate && appData.checkMates) {
7909                             /* but only adjudicate if adjudication enabled */
7910                             if(engineOpponent)
7911                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7912                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7913                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7914                             return 1;
7915                         }
7916                   }
7917                 } else bare = 1;
7918
7919
7920             // don't wait for engine to announce game end if we can judge ourselves
7921             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7922               case MT_CHECK:
7923                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7924                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7925                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7926                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7927                             checkCnt++;
7928                         if(checkCnt >= 2) {
7929                             reason = "Xboard adjudication: 3rd check";
7930                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7931                             break;
7932                         }
7933                     }
7934                 }
7935               case MT_NONE:
7936               default:
7937                 break;
7938               case MT_STALEMATE:
7939               case MT_STAINMATE:
7940                 reason = "Xboard adjudication: Stalemate";
7941                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7942                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7943                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7944                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7945                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7946                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7947                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7948                                                                         EP_CHECKMATE : EP_WINS);
7949                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7950                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7951                 }
7952                 break;
7953               case MT_CHECKMATE:
7954                 reason = "Xboard adjudication: Checkmate";
7955                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7956                 if(gameInfo.variant == VariantShogi) {
7957                     if(forwardMostMove > backwardMostMove
7958                        && moveList[forwardMostMove-1][1] == '@'
7959                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7960                         reason = "XBoard adjudication: pawn-drop mate";
7961                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7962                     }
7963                 }
7964                 break;
7965             }
7966
7967                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7968                     case EP_STALEMATE:
7969                         result = GameIsDrawn; break;
7970                     case EP_CHECKMATE:
7971                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7972                     case EP_WINS:
7973                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7974                     default:
7975                         result = EndOfFile;
7976                 }
7977                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7978                     if(engineOpponent)
7979                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980                     GameEnds( result, reason, GE_XBOARD );
7981                     return 1;
7982                 }
7983
7984                 /* Next absolutely insufficient mating material. */
7985                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7986                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7987                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7988
7989                      /* always flag draws, for judging claims */
7990                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7991
7992                      if(canAdjudicate && appData.materialDraws) {
7993                          /* but only adjudicate them if adjudication enabled */
7994                          if(engineOpponent) {
7995                            SendToProgram("force\n", engineOpponent); // suppress reply
7996                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7997                          }
7998                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7999                          return 1;
8000                      }
8001                 }
8002
8003                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8004                 if(gameInfo.variant == VariantXiangqi ?
8005                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8006                  : nrW + nrB == 4 &&
8007                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8008                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8009                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8010                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8011                    ) ) {
8012                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8013                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8014                           if(engineOpponent) {
8015                             SendToProgram("force\n", engineOpponent); // suppress reply
8016                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8017                           }
8018                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8019                           return 1;
8020                      }
8021                 } else moveCount = 6;
8022             }
8023
8024         // Repetition draws and 50-move rule can be applied independently of legality testing
8025
8026                 /* Check for rep-draws */
8027                 count = 0;
8028                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8029                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8030                 for(k = forwardMostMove-2;
8031                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8032                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8033                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8034                     k-=2)
8035                 {   int rights=0;
8036                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8037                         /* compare castling rights */
8038                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8039                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8040                                 rights++; /* King lost rights, while rook still had them */
8041                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8042                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8043                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8044                                    rights++; /* but at least one rook lost them */
8045                         }
8046                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8047                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8048                                 rights++;
8049                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8050                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8051                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8052                                    rights++;
8053                         }
8054                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8055                             && appData.drawRepeats > 1) {
8056                              /* adjudicate after user-specified nr of repeats */
8057                              int result = GameIsDrawn;
8058                              char *details = "XBoard adjudication: repetition draw";
8059                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8060                                 // [HGM] xiangqi: check for forbidden perpetuals
8061                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8062                                 for(m=forwardMostMove; m>k; m-=2) {
8063                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8064                                         ourPerpetual = 0; // the current mover did not always check
8065                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8066                                         hisPerpetual = 0; // the opponent did not always check
8067                                 }
8068                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8069                                                                         ourPerpetual, hisPerpetual);
8070                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8071                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8072                                     details = "Xboard adjudication: perpetual checking";
8073                                 } else
8074                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8075                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8076                                 } else
8077                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8078                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8079                                         result = BlackWins;
8080                                         details = "Xboard adjudication: repetition";
8081                                     }
8082                                 } else // it must be XQ
8083                                 // Now check for perpetual chases
8084                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8085                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8086                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8087                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8088                                         static char resdet[MSG_SIZ];
8089                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8090                                         details = resdet;
8091                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8092                                     } else
8093                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8094                                         break; // Abort repetition-checking loop.
8095                                 }
8096                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8097                              }
8098                              if(engineOpponent) {
8099                                SendToProgram("force\n", engineOpponent); // suppress reply
8100                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8101                              }
8102                              GameEnds( result, details, GE_XBOARD );
8103                              return 1;
8104                         }
8105                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8106                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8107                     }
8108                 }
8109
8110                 /* Now we test for 50-move draws. Determine ply count */
8111                 count = forwardMostMove;
8112                 /* look for last irreversble move */
8113                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8114                     count--;
8115                 /* if we hit starting position, add initial plies */
8116                 if( count == backwardMostMove )
8117                     count -= initialRulePlies;
8118                 count = forwardMostMove - count;
8119                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8120                         // adjust reversible move counter for checks in Xiangqi
8121                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8122                         if(i < backwardMostMove) i = backwardMostMove;
8123                         while(i <= forwardMostMove) {
8124                                 lastCheck = inCheck; // check evasion does not count
8125                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8126                                 if(inCheck || lastCheck) count--; // check does not count
8127                                 i++;
8128                         }
8129                 }
8130                 if( count >= 100)
8131                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8132                          /* this is used to judge if draw claims are legal */
8133                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8134                          if(engineOpponent) {
8135                            SendToProgram("force\n", engineOpponent); // suppress reply
8136                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8137                          }
8138                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8139                          return 1;
8140                 }
8141
8142                 /* if draw offer is pending, treat it as a draw claim
8143                  * when draw condition present, to allow engines a way to
8144                  * claim draws before making their move to avoid a race
8145                  * condition occurring after their move
8146                  */
8147                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8148                          char *p = NULL;
8149                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8150                              p = "Draw claim: 50-move rule";
8151                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8152                              p = "Draw claim: 3-fold repetition";
8153                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8154                              p = "Draw claim: insufficient mating material";
8155                          if( p != NULL && canAdjudicate) {
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, p, GE_XBOARD );
8161                              return 1;
8162                          }
8163                 }
8164
8165                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8166                     if(engineOpponent) {
8167                       SendToProgram("force\n", engineOpponent); // suppress reply
8168                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8169                     }
8170                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8171                     return 1;
8172                 }
8173         return 0;
8174 }
8175
8176 char *
8177 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8178 {   // [HGM] book: this routine intercepts moves to simulate book replies
8179     char *bookHit = NULL;
8180
8181     //first determine if the incoming move brings opponent into his book
8182     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8183         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8184     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8185     if(bookHit != NULL && !cps->bookSuspend) {
8186         // make sure opponent is not going to reply after receiving move to book position
8187         SendToProgram("force\n", cps);
8188         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8189     }
8190     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8191     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8192     // now arrange restart after book miss
8193     if(bookHit) {
8194         // after a book hit we never send 'go', and the code after the call to this routine
8195         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8196         char buf[MSG_SIZ], *move = bookHit;
8197         if(cps->useSAN) {
8198             int fromX, fromY, toX, toY;
8199             char promoChar;
8200             ChessMove moveType;
8201             move = buf + 30;
8202             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8203                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8204                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8205                                     PosFlags(forwardMostMove),
8206                                     fromY, fromX, toY, toX, promoChar, move);
8207             } else {
8208                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8209                 bookHit = NULL;
8210             }
8211         }
8212         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8213         SendToProgram(buf, cps);
8214         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8215     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8216         SendToProgram("go\n", cps);
8217         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8218     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8219         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8220             SendToProgram("go\n", cps);
8221         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8222     }
8223     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8224 }
8225
8226 int
8227 LoadError (char *errmess, ChessProgramState *cps)
8228 {   // unloads engine and switches back to -ncp mode if it was first
8229     if(cps->initDone) return FALSE;
8230     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8231     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8232     cps->pr = NoProc;
8233     if(cps == &first) {
8234         appData.noChessProgram = TRUE;
8235         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8236         gameMode = BeginningOfGame; ModeHighlight();
8237         SetNCPMode();
8238     }
8239     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8240     DisplayMessage("", ""); // erase waiting message
8241     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8242     return TRUE;
8243 }
8244
8245 char *savedMessage;
8246 ChessProgramState *savedState;
8247 void
8248 DeferredBookMove (void)
8249 {
8250         if(savedState->lastPing != savedState->lastPong)
8251                     ScheduleDelayedEvent(DeferredBookMove, 10);
8252         else
8253         HandleMachineMove(savedMessage, savedState);
8254 }
8255
8256 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8257 static ChessProgramState *stalledEngine;
8258 static char stashedInputMove[MSG_SIZ];
8259
8260 void
8261 HandleMachineMove (char *message, ChessProgramState *cps)
8262 {
8263     static char firstLeg[20];
8264     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8265     char realname[MSG_SIZ];
8266     int fromX, fromY, toX, toY;
8267     ChessMove moveType;
8268     char promoChar;
8269     char *p, *pv=buf1;
8270     int machineWhite, oldError;
8271     char *bookHit;
8272
8273     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8274         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8275         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8276             DisplayError(_("Invalid pairing from pairing engine"), 0);
8277             return;
8278         }
8279         pairingReceived = 1;
8280         NextMatchGame();
8281         return; // Skim the pairing messages here.
8282     }
8283
8284     oldError = cps->userError; cps->userError = 0;
8285
8286 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8287     /*
8288      * Kludge to ignore BEL characters
8289      */
8290     while (*message == '\007') message++;
8291
8292     /*
8293      * [HGM] engine debug message: ignore lines starting with '#' character
8294      */
8295     if(cps->debug && *message == '#') return;
8296
8297     /*
8298      * Look for book output
8299      */
8300     if (cps == &first && bookRequested) {
8301         if (message[0] == '\t' || message[0] == ' ') {
8302             /* Part of the book output is here; append it */
8303             strcat(bookOutput, message);
8304             strcat(bookOutput, "  \n");
8305             return;
8306         } else if (bookOutput[0] != NULLCHAR) {
8307             /* All of book output has arrived; display it */
8308             char *p = bookOutput;
8309             while (*p != NULLCHAR) {
8310                 if (*p == '\t') *p = ' ';
8311                 p++;
8312             }
8313             DisplayInformation(bookOutput);
8314             bookRequested = FALSE;
8315             /* Fall through to parse the current output */
8316         }
8317     }
8318
8319     /*
8320      * Look for machine move.
8321      */
8322     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8323         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8324     {
8325         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8326             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8327             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8328             stalledEngine = cps;
8329             if(appData.ponderNextMove) { // bring opponent out of ponder
8330                 if(gameMode == TwoMachinesPlay) {
8331                     if(cps->other->pause)
8332                         PauseEngine(cps->other);
8333                     else
8334                         SendToProgram("easy\n", cps->other);
8335                 }
8336             }
8337             StopClocks();
8338             return;
8339         }
8340
8341         /* This method is only useful on engines that support ping */
8342         if (cps->lastPing != cps->lastPong) {
8343           if (gameMode == BeginningOfGame) {
8344             /* Extra move from before last new; ignore */
8345             if (appData.debugMode) {
8346                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8347             }
8348           } else {
8349             if (appData.debugMode) {
8350                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8351                         cps->which, gameMode);
8352             }
8353
8354             SendToProgram("undo\n", cps);
8355           }
8356           return;
8357         }
8358
8359         switch (gameMode) {
8360           case BeginningOfGame:
8361             /* Extra move from before last reset; ignore */
8362             if (appData.debugMode) {
8363                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8364             }
8365             return;
8366
8367           case EndOfGame:
8368           case IcsIdle:
8369           default:
8370             /* Extra move after we tried to stop.  The mode test is
8371                not a reliable way of detecting this problem, but it's
8372                the best we can do on engines that don't support ping.
8373             */
8374             if (appData.debugMode) {
8375                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8376                         cps->which, gameMode);
8377             }
8378             SendToProgram("undo\n", cps);
8379             return;
8380
8381           case MachinePlaysWhite:
8382           case IcsPlayingWhite:
8383             machineWhite = TRUE;
8384             break;
8385
8386           case MachinePlaysBlack:
8387           case IcsPlayingBlack:
8388             machineWhite = FALSE;
8389             break;
8390
8391           case TwoMachinesPlay:
8392             machineWhite = (cps->twoMachinesColor[0] == 'w');
8393             break;
8394         }
8395         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8396             if (appData.debugMode) {
8397                 fprintf(debugFP,
8398                         "Ignoring move out of turn by %s, gameMode %d"
8399                         ", forwardMost %d\n",
8400                         cps->which, gameMode, forwardMostMove);
8401             }
8402             return;
8403         }
8404
8405         if(cps->alphaRank) AlphaRank(machineMove, 4);
8406
8407         // [HGM] lion: (some very limited) support for Alien protocol
8408         killX = killY = -1;
8409         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8410             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8411             return;
8412         } else if(firstLeg[0]) { // there was a previous leg;
8413             // only support case where same piece makes two step (and don't even test that!)
8414             char buf[20], *p = machineMove+1, *q = buf+1, f;
8415             safeStrCpy(buf, machineMove, 20);
8416             while(isdigit(*q)) q++; // find start of to-square
8417             safeStrCpy(machineMove, firstLeg, 20);
8418             while(isdigit(*p)) p++;
8419             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8420             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8421             firstLeg[0] = NULLCHAR;
8422         }
8423
8424         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8425                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8426             /* Machine move could not be parsed; ignore it. */
8427           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8428                     machineMove, _(cps->which));
8429             DisplayMoveError(buf1);
8430             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8431                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8432             if (gameMode == TwoMachinesPlay) {
8433               GameEnds(machineWhite ? BlackWins : WhiteWins,
8434                        buf1, GE_XBOARD);
8435             }
8436             return;
8437         }
8438
8439         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8440         /* So we have to redo legality test with true e.p. status here,  */
8441         /* to make sure an illegal e.p. capture does not slip through,   */
8442         /* to cause a forfeit on a justified illegal-move complaint      */
8443         /* of the opponent.                                              */
8444         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8445            ChessMove moveType;
8446            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8447                              fromY, fromX, toY, toX, promoChar);
8448             if(moveType == IllegalMove) {
8449               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8450                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8451                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8452                            buf1, GE_XBOARD);
8453                 return;
8454            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8455            /* [HGM] Kludge to handle engines that send FRC-style castling
8456               when they shouldn't (like TSCP-Gothic) */
8457            switch(moveType) {
8458              case WhiteASideCastleFR:
8459              case BlackASideCastleFR:
8460                toX+=2;
8461                currentMoveString[2]++;
8462                break;
8463              case WhiteHSideCastleFR:
8464              case BlackHSideCastleFR:
8465                toX--;
8466                currentMoveString[2]--;
8467                break;
8468              default: ; // nothing to do, but suppresses warning of pedantic compilers
8469            }
8470         }
8471         hintRequested = FALSE;
8472         lastHint[0] = NULLCHAR;
8473         bookRequested = FALSE;
8474         /* Program may be pondering now */
8475         cps->maybeThinking = TRUE;
8476         if (cps->sendTime == 2) cps->sendTime = 1;
8477         if (cps->offeredDraw) cps->offeredDraw--;
8478
8479         /* [AS] Save move info*/
8480         pvInfoList[ forwardMostMove ].score = programStats.score;
8481         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8482         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8483
8484         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8485
8486         /* Test suites abort the 'game' after one move */
8487         if(*appData.finger) {
8488            static FILE *f;
8489            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8490            if(!f) f = fopen(appData.finger, "w");
8491            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8492            else { DisplayFatalError("Bad output file", errno, 0); return; }
8493            free(fen);
8494            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8495         }
8496
8497         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8498         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8499             int count = 0;
8500
8501             while( count < adjudicateLossPlies ) {
8502                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8503
8504                 if( count & 1 ) {
8505                     score = -score; /* Flip score for winning side */
8506                 }
8507
8508                 if( score > adjudicateLossThreshold ) {
8509                     break;
8510                 }
8511
8512                 count++;
8513             }
8514
8515             if( count >= adjudicateLossPlies ) {
8516                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8517
8518                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8519                     "Xboard adjudication",
8520                     GE_XBOARD );
8521
8522                 return;
8523             }
8524         }
8525
8526         if(Adjudicate(cps)) {
8527             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8528             return; // [HGM] adjudicate: for all automatic game ends
8529         }
8530
8531 #if ZIPPY
8532         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8533             first.initDone) {
8534           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8535                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8536                 SendToICS("draw ");
8537                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8538           }
8539           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8540           ics_user_moved = 1;
8541           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8542                 char buf[3*MSG_SIZ];
8543
8544                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8545                         programStats.score / 100.,
8546                         programStats.depth,
8547                         programStats.time / 100.,
8548                         (unsigned int)programStats.nodes,
8549                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8550                         programStats.movelist);
8551                 SendToICS(buf);
8552 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8553           }
8554         }
8555 #endif
8556
8557         /* [AS] Clear stats for next move */
8558         ClearProgramStats();
8559         thinkOutput[0] = NULLCHAR;
8560         hiddenThinkOutputState = 0;
8561
8562         bookHit = NULL;
8563         if (gameMode == TwoMachinesPlay) {
8564             /* [HGM] relaying draw offers moved to after reception of move */
8565             /* and interpreting offer as claim if it brings draw condition */
8566             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8567                 SendToProgram("draw\n", cps->other);
8568             }
8569             if (cps->other->sendTime) {
8570                 SendTimeRemaining(cps->other,
8571                                   cps->other->twoMachinesColor[0] == 'w');
8572             }
8573             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8574             if (firstMove && !bookHit) {
8575                 firstMove = FALSE;
8576                 if (cps->other->useColors) {
8577                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8578                 }
8579                 SendToProgram("go\n", cps->other);
8580             }
8581             cps->other->maybeThinking = TRUE;
8582         }
8583
8584         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8585
8586         if (!pausing && appData.ringBellAfterMoves) {
8587             RingBell();
8588         }
8589
8590         /*
8591          * Reenable menu items that were disabled while
8592          * machine was thinking
8593          */
8594         if (gameMode != TwoMachinesPlay)
8595             SetUserThinkingEnables();
8596
8597         // [HGM] book: after book hit opponent has received move and is now in force mode
8598         // force the book reply into it, and then fake that it outputted this move by jumping
8599         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8600         if(bookHit) {
8601                 static char bookMove[MSG_SIZ]; // a bit generous?
8602
8603                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8604                 strcat(bookMove, bookHit);
8605                 message = bookMove;
8606                 cps = cps->other;
8607                 programStats.nodes = programStats.depth = programStats.time =
8608                 programStats.score = programStats.got_only_move = 0;
8609                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8610
8611                 if(cps->lastPing != cps->lastPong) {
8612                     savedMessage = message; // args for deferred call
8613                     savedState = cps;
8614                     ScheduleDelayedEvent(DeferredBookMove, 10);
8615                     return;
8616                 }
8617                 goto FakeBookMove;
8618         }
8619
8620         return;
8621     }
8622
8623     /* Set special modes for chess engines.  Later something general
8624      *  could be added here; for now there is just one kludge feature,
8625      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8626      *  when "xboard" is given as an interactive command.
8627      */
8628     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8629         cps->useSigint = FALSE;
8630         cps->useSigterm = FALSE;
8631     }
8632     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8633       ParseFeatures(message+8, cps);
8634       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8635     }
8636
8637     if (!strncmp(message, "setup ", 6) && 
8638         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8639                                         ) { // [HGM] allow first engine to define opening position
8640       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8641       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8642       *buf = NULLCHAR;
8643       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8644       if(startedFromSetupPosition) return;
8645       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8646       if(dummy >= 3) {
8647         while(message[s] && message[s++] != ' ');
8648         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8649            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8650             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8651             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8652           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8653           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8654         }
8655       }
8656       ParseFEN(boards[0], &dummy, message+s, FALSE);
8657       DrawPosition(TRUE, boards[0]);
8658       startedFromSetupPosition = TRUE;
8659       return;
8660     }
8661     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8662      * want this, I was asked to put it in, and obliged.
8663      */
8664     if (!strncmp(message, "setboard ", 9)) {
8665         Board initial_position;
8666
8667         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8668
8669         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8670             DisplayError(_("Bad FEN received from engine"), 0);
8671             return ;
8672         } else {
8673            Reset(TRUE, FALSE);
8674            CopyBoard(boards[0], initial_position);
8675            initialRulePlies = FENrulePlies;
8676            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8677            else gameMode = MachinePlaysBlack;
8678            DrawPosition(FALSE, boards[currentMove]);
8679         }
8680         return;
8681     }
8682
8683     /*
8684      * Look for communication commands
8685      */
8686     if (!strncmp(message, "telluser ", 9)) {
8687         if(message[9] == '\\' && message[10] == '\\')
8688             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8689         PlayTellSound();
8690         DisplayNote(message + 9);
8691         return;
8692     }
8693     if (!strncmp(message, "tellusererror ", 14)) {
8694         cps->userError = 1;
8695         if(message[14] == '\\' && message[15] == '\\')
8696             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8697         PlayTellSound();
8698         DisplayError(message + 14, 0);
8699         return;
8700     }
8701     if (!strncmp(message, "tellopponent ", 13)) {
8702       if (appData.icsActive) {
8703         if (loggedOn) {
8704           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8705           SendToICS(buf1);
8706         }
8707       } else {
8708         DisplayNote(message + 13);
8709       }
8710       return;
8711     }
8712     if (!strncmp(message, "tellothers ", 11)) {
8713       if (appData.icsActive) {
8714         if (loggedOn) {
8715           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8716           SendToICS(buf1);
8717         }
8718       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8719       return;
8720     }
8721     if (!strncmp(message, "tellall ", 8)) {
8722       if (appData.icsActive) {
8723         if (loggedOn) {
8724           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8725           SendToICS(buf1);
8726         }
8727       } else {
8728         DisplayNote(message + 8);
8729       }
8730       return;
8731     }
8732     if (strncmp(message, "warning", 7) == 0) {
8733         /* Undocumented feature, use tellusererror in new code */
8734         DisplayError(message, 0);
8735         return;
8736     }
8737     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8738         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8739         strcat(realname, " query");
8740         AskQuestion(realname, buf2, buf1, cps->pr);
8741         return;
8742     }
8743     /* Commands from the engine directly to ICS.  We don't allow these to be
8744      *  sent until we are logged on. Crafty kibitzes have been known to
8745      *  interfere with the login process.
8746      */
8747     if (loggedOn) {
8748         if (!strncmp(message, "tellics ", 8)) {
8749             SendToICS(message + 8);
8750             SendToICS("\n");
8751             return;
8752         }
8753         if (!strncmp(message, "tellicsnoalias ", 15)) {
8754             SendToICS(ics_prefix);
8755             SendToICS(message + 15);
8756             SendToICS("\n");
8757             return;
8758         }
8759         /* The following are for backward compatibility only */
8760         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8761             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8762             SendToICS(ics_prefix);
8763             SendToICS(message);
8764             SendToICS("\n");
8765             return;
8766         }
8767     }
8768     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8769         return;
8770     }
8771     if(!strncmp(message, "highlight ", 10)) {
8772         if(appData.testLegality && appData.markers) return;
8773         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8774         return;
8775     }
8776     if(!strncmp(message, "click ", 6)) {
8777         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8778         if(appData.testLegality || !appData.oneClick) return;
8779         sscanf(message+6, "%c%d%c", &f, &y, &c);
8780         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8781         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8782         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8783         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8784         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8785         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8786             LeftClick(Release, lastLeftX, lastLeftY);
8787         controlKey  = (c == ',');
8788         LeftClick(Press, x, y);
8789         LeftClick(Release, x, y);
8790         first.highlight = f;
8791         return;
8792     }
8793     /*
8794      * If the move is illegal, cancel it and redraw the board.
8795      * Also deal with other error cases.  Matching is rather loose
8796      * here to accommodate engines written before the spec.
8797      */
8798     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8799         strncmp(message, "Error", 5) == 0) {
8800         if (StrStr(message, "name") ||
8801             StrStr(message, "rating") || StrStr(message, "?") ||
8802             StrStr(message, "result") || StrStr(message, "board") ||
8803             StrStr(message, "bk") || StrStr(message, "computer") ||
8804             StrStr(message, "variant") || StrStr(message, "hint") ||
8805             StrStr(message, "random") || StrStr(message, "depth") ||
8806             StrStr(message, "accepted")) {
8807             return;
8808         }
8809         if (StrStr(message, "protover")) {
8810           /* Program is responding to input, so it's apparently done
8811              initializing, and this error message indicates it is
8812              protocol version 1.  So we don't need to wait any longer
8813              for it to initialize and send feature commands. */
8814           FeatureDone(cps, 1);
8815           cps->protocolVersion = 1;
8816           return;
8817         }
8818         cps->maybeThinking = FALSE;
8819
8820         if (StrStr(message, "draw")) {
8821             /* Program doesn't have "draw" command */
8822             cps->sendDrawOffers = 0;
8823             return;
8824         }
8825         if (cps->sendTime != 1 &&
8826             (StrStr(message, "time") || StrStr(message, "otim"))) {
8827           /* Program apparently doesn't have "time" or "otim" command */
8828           cps->sendTime = 0;
8829           return;
8830         }
8831         if (StrStr(message, "analyze")) {
8832             cps->analysisSupport = FALSE;
8833             cps->analyzing = FALSE;
8834 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8835             EditGameEvent(); // [HGM] try to preserve loaded game
8836             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8837             DisplayError(buf2, 0);
8838             return;
8839         }
8840         if (StrStr(message, "(no matching move)st")) {
8841           /* Special kludge for GNU Chess 4 only */
8842           cps->stKludge = TRUE;
8843           SendTimeControl(cps, movesPerSession, timeControl,
8844                           timeIncrement, appData.searchDepth,
8845                           searchTime);
8846           return;
8847         }
8848         if (StrStr(message, "(no matching move)sd")) {
8849           /* Special kludge for GNU Chess 4 only */
8850           cps->sdKludge = TRUE;
8851           SendTimeControl(cps, movesPerSession, timeControl,
8852                           timeIncrement, appData.searchDepth,
8853                           searchTime);
8854           return;
8855         }
8856         if (!StrStr(message, "llegal")) {
8857             return;
8858         }
8859         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8860             gameMode == IcsIdle) return;
8861         if (forwardMostMove <= backwardMostMove) return;
8862         if (pausing) PauseEvent();
8863       if(appData.forceIllegal) {
8864             // [HGM] illegal: machine refused move; force position after move into it
8865           SendToProgram("force\n", cps);
8866           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8867                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8868                 // when black is to move, while there might be nothing on a2 or black
8869                 // might already have the move. So send the board as if white has the move.
8870                 // But first we must change the stm of the engine, as it refused the last move
8871                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8872                 if(WhiteOnMove(forwardMostMove)) {
8873                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8874                     SendBoard(cps, forwardMostMove); // kludgeless board
8875                 } else {
8876                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8877                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8878                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8879                 }
8880           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8881             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8882                  gameMode == TwoMachinesPlay)
8883               SendToProgram("go\n", cps);
8884             return;
8885       } else
8886         if (gameMode == PlayFromGameFile) {
8887             /* Stop reading this game file */
8888             gameMode = EditGame;
8889             ModeHighlight();
8890         }
8891         /* [HGM] illegal-move claim should forfeit game when Xboard */
8892         /* only passes fully legal moves                            */
8893         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8894             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8895                                 "False illegal-move claim", GE_XBOARD );
8896             return; // do not take back move we tested as valid
8897         }
8898         currentMove = forwardMostMove-1;
8899         DisplayMove(currentMove-1); /* before DisplayMoveError */
8900         SwitchClocks(forwardMostMove-1); // [HGM] race
8901         DisplayBothClocks();
8902         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8903                 parseList[currentMove], _(cps->which));
8904         DisplayMoveError(buf1);
8905         DrawPosition(FALSE, boards[currentMove]);
8906
8907         SetUserThinkingEnables();
8908         return;
8909     }
8910     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8911         /* Program has a broken "time" command that
8912            outputs a string not ending in newline.
8913            Don't use it. */
8914         cps->sendTime = 0;
8915     }
8916
8917     /*
8918      * If chess program startup fails, exit with an error message.
8919      * Attempts to recover here are futile. [HGM] Well, we try anyway
8920      */
8921     if ((StrStr(message, "unknown host") != NULL)
8922         || (StrStr(message, "No remote directory") != NULL)
8923         || (StrStr(message, "not found") != NULL)
8924         || (StrStr(message, "No such file") != NULL)
8925         || (StrStr(message, "can't alloc") != NULL)
8926         || (StrStr(message, "Permission denied") != NULL)) {
8927
8928         cps->maybeThinking = FALSE;
8929         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8930                 _(cps->which), cps->program, cps->host, message);
8931         RemoveInputSource(cps->isr);
8932         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8933             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8934             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8935         }
8936         return;
8937     }
8938
8939     /*
8940      * Look for hint output
8941      */
8942     if (sscanf(message, "Hint: %s", buf1) == 1) {
8943         if (cps == &first && hintRequested) {
8944             hintRequested = FALSE;
8945             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8946                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8947                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8948                                     PosFlags(forwardMostMove),
8949                                     fromY, fromX, toY, toX, promoChar, buf1);
8950                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8951                 DisplayInformation(buf2);
8952             } else {
8953                 /* Hint move could not be parsed!? */
8954               snprintf(buf2, sizeof(buf2),
8955                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8956                         buf1, _(cps->which));
8957                 DisplayError(buf2, 0);
8958             }
8959         } else {
8960           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8961         }
8962         return;
8963     }
8964
8965     /*
8966      * Ignore other messages if game is not in progress
8967      */
8968     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8969         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8970
8971     /*
8972      * look for win, lose, draw, or draw offer
8973      */
8974     if (strncmp(message, "1-0", 3) == 0) {
8975         char *p, *q, *r = "";
8976         p = strchr(message, '{');
8977         if (p) {
8978             q = strchr(p, '}');
8979             if (q) {
8980                 *q = NULLCHAR;
8981                 r = p + 1;
8982             }
8983         }
8984         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8985         return;
8986     } else if (strncmp(message, "0-1", 3) == 0) {
8987         char *p, *q, *r = "";
8988         p = strchr(message, '{');
8989         if (p) {
8990             q = strchr(p, '}');
8991             if (q) {
8992                 *q = NULLCHAR;
8993                 r = p + 1;
8994             }
8995         }
8996         /* Kludge for Arasan 4.1 bug */
8997         if (strcmp(r, "Black resigns") == 0) {
8998             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8999             return;
9000         }
9001         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9002         return;
9003     } else if (strncmp(message, "1/2", 3) == 0) {
9004         char *p, *q, *r = "";
9005         p = strchr(message, '{');
9006         if (p) {
9007             q = strchr(p, '}');
9008             if (q) {
9009                 *q = NULLCHAR;
9010                 r = p + 1;
9011             }
9012         }
9013
9014         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9015         return;
9016
9017     } else if (strncmp(message, "White resign", 12) == 0) {
9018         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9019         return;
9020     } else if (strncmp(message, "Black resign", 12) == 0) {
9021         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9022         return;
9023     } else if (strncmp(message, "White matches", 13) == 0 ||
9024                strncmp(message, "Black matches", 13) == 0   ) {
9025         /* [HGM] ignore GNUShogi noises */
9026         return;
9027     } else if (strncmp(message, "White", 5) == 0 &&
9028                message[5] != '(' &&
9029                StrStr(message, "Black") == NULL) {
9030         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9031         return;
9032     } else if (strncmp(message, "Black", 5) == 0 &&
9033                message[5] != '(') {
9034         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9035         return;
9036     } else if (strcmp(message, "resign") == 0 ||
9037                strcmp(message, "computer resigns") == 0) {
9038         switch (gameMode) {
9039           case MachinePlaysBlack:
9040           case IcsPlayingBlack:
9041             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9042             break;
9043           case MachinePlaysWhite:
9044           case IcsPlayingWhite:
9045             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9046             break;
9047           case TwoMachinesPlay:
9048             if (cps->twoMachinesColor[0] == 'w')
9049               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9050             else
9051               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9052             break;
9053           default:
9054             /* can't happen */
9055             break;
9056         }
9057         return;
9058     } else if (strncmp(message, "opponent mates", 14) == 0) {
9059         switch (gameMode) {
9060           case MachinePlaysBlack:
9061           case IcsPlayingBlack:
9062             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9063             break;
9064           case MachinePlaysWhite:
9065           case IcsPlayingWhite:
9066             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9067             break;
9068           case TwoMachinesPlay:
9069             if (cps->twoMachinesColor[0] == 'w')
9070               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9071             else
9072               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9073             break;
9074           default:
9075             /* can't happen */
9076             break;
9077         }
9078         return;
9079     } else if (strncmp(message, "computer mates", 14) == 0) {
9080         switch (gameMode) {
9081           case MachinePlaysBlack:
9082           case IcsPlayingBlack:
9083             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9084             break;
9085           case MachinePlaysWhite:
9086           case IcsPlayingWhite:
9087             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9088             break;
9089           case TwoMachinesPlay:
9090             if (cps->twoMachinesColor[0] == 'w')
9091               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9092             else
9093               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9094             break;
9095           default:
9096             /* can't happen */
9097             break;
9098         }
9099         return;
9100     } else if (strncmp(message, "checkmate", 9) == 0) {
9101         if (WhiteOnMove(forwardMostMove)) {
9102             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9103         } else {
9104             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9105         }
9106         return;
9107     } else if (strstr(message, "Draw") != NULL ||
9108                strstr(message, "game is a draw") != NULL) {
9109         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9110         return;
9111     } else if (strstr(message, "offer") != NULL &&
9112                strstr(message, "draw") != NULL) {
9113 #if ZIPPY
9114         if (appData.zippyPlay && first.initDone) {
9115             /* Relay offer to ICS */
9116             SendToICS(ics_prefix);
9117             SendToICS("draw\n");
9118         }
9119 #endif
9120         cps->offeredDraw = 2; /* valid until this engine moves twice */
9121         if (gameMode == TwoMachinesPlay) {
9122             if (cps->other->offeredDraw) {
9123                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9124             /* [HGM] in two-machine mode we delay relaying draw offer      */
9125             /* until after we also have move, to see if it is really claim */
9126             }
9127         } else if (gameMode == MachinePlaysWhite ||
9128                    gameMode == MachinePlaysBlack) {
9129           if (userOfferedDraw) {
9130             DisplayInformation(_("Machine accepts your draw offer"));
9131             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9132           } else {
9133             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9134           }
9135         }
9136     }
9137
9138
9139     /*
9140      * Look for thinking output
9141      */
9142     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9143           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9144                                 ) {
9145         int plylev, mvleft, mvtot, curscore, time;
9146         char mvname[MOVE_LEN];
9147         u64 nodes; // [DM]
9148         char plyext;
9149         int ignore = FALSE;
9150         int prefixHint = FALSE;
9151         mvname[0] = NULLCHAR;
9152
9153         switch (gameMode) {
9154           case MachinePlaysBlack:
9155           case IcsPlayingBlack:
9156             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9157             break;
9158           case MachinePlaysWhite:
9159           case IcsPlayingWhite:
9160             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9161             break;
9162           case AnalyzeMode:
9163           case AnalyzeFile:
9164             break;
9165           case IcsObserving: /* [DM] icsEngineAnalyze */
9166             if (!appData.icsEngineAnalyze) ignore = TRUE;
9167             break;
9168           case TwoMachinesPlay:
9169             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9170                 ignore = TRUE;
9171             }
9172             break;
9173           default:
9174             ignore = TRUE;
9175             break;
9176         }
9177
9178         if (!ignore) {
9179             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9180             buf1[0] = NULLCHAR;
9181             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9182                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9183
9184                 if (plyext != ' ' && plyext != '\t') {
9185                     time *= 100;
9186                 }
9187
9188                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9189                 if( cps->scoreIsAbsolute &&
9190                     ( gameMode == MachinePlaysBlack ||
9191                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9192                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9193                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9194                      !WhiteOnMove(currentMove)
9195                     ) )
9196                 {
9197                     curscore = -curscore;
9198                 }
9199
9200                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9201
9202                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9203                         char buf[MSG_SIZ];
9204                         FILE *f;
9205                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9206                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9207                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9208                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9209                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9210                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9211                                 fclose(f);
9212                         }
9213                         else
9214                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9215                           DisplayError(_("failed writing PV"), 0);
9216                 }
9217
9218                 tempStats.depth = plylev;
9219                 tempStats.nodes = nodes;
9220                 tempStats.time = time;
9221                 tempStats.score = curscore;
9222                 tempStats.got_only_move = 0;
9223
9224                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9225                         int ticklen;
9226
9227                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9228                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9229                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9230                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9231                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9232                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9233                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9234                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9235                 }
9236
9237                 /* Buffer overflow protection */
9238                 if (pv[0] != NULLCHAR) {
9239                     if (strlen(pv) >= sizeof(tempStats.movelist)
9240                         && appData.debugMode) {
9241                         fprintf(debugFP,
9242                                 "PV is too long; using the first %u bytes.\n",
9243                                 (unsigned) sizeof(tempStats.movelist) - 1);
9244                     }
9245
9246                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9247                 } else {
9248                     sprintf(tempStats.movelist, " no PV\n");
9249                 }
9250
9251                 if (tempStats.seen_stat) {
9252                     tempStats.ok_to_send = 1;
9253                 }
9254
9255                 if (strchr(tempStats.movelist, '(') != NULL) {
9256                     tempStats.line_is_book = 1;
9257                     tempStats.nr_moves = 0;
9258                     tempStats.moves_left = 0;
9259                 } else {
9260                     tempStats.line_is_book = 0;
9261                 }
9262
9263                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9264                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9265
9266                 SendProgramStatsToFrontend( cps, &tempStats );
9267
9268                 /*
9269                     [AS] Protect the thinkOutput buffer from overflow... this
9270                     is only useful if buf1 hasn't overflowed first!
9271                 */
9272                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9273                          plylev,
9274                          (gameMode == TwoMachinesPlay ?
9275                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9276                          ((double) curscore) / 100.0,
9277                          prefixHint ? lastHint : "",
9278                          prefixHint ? " " : "" );
9279
9280                 if( buf1[0] != NULLCHAR ) {
9281                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9282
9283                     if( strlen(pv) > max_len ) {
9284                         if( appData.debugMode) {
9285                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9286                         }
9287                         pv[max_len+1] = '\0';
9288                     }
9289
9290                     strcat( thinkOutput, pv);
9291                 }
9292
9293                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9294                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9295                     DisplayMove(currentMove - 1);
9296                 }
9297                 return;
9298
9299             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9300                 /* crafty (9.25+) says "(only move) <move>"
9301                  * if there is only 1 legal move
9302                  */
9303                 sscanf(p, "(only move) %s", buf1);
9304                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9305                 sprintf(programStats.movelist, "%s (only move)", buf1);
9306                 programStats.depth = 1;
9307                 programStats.nr_moves = 1;
9308                 programStats.moves_left = 1;
9309                 programStats.nodes = 1;
9310                 programStats.time = 1;
9311                 programStats.got_only_move = 1;
9312
9313                 /* Not really, but we also use this member to
9314                    mean "line isn't going to change" (Crafty
9315                    isn't searching, so stats won't change) */
9316                 programStats.line_is_book = 1;
9317
9318                 SendProgramStatsToFrontend( cps, &programStats );
9319
9320                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9321                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9322                     DisplayMove(currentMove - 1);
9323                 }
9324                 return;
9325             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9326                               &time, &nodes, &plylev, &mvleft,
9327                               &mvtot, mvname) >= 5) {
9328                 /* The stat01: line is from Crafty (9.29+) in response
9329                    to the "." command */
9330                 programStats.seen_stat = 1;
9331                 cps->maybeThinking = TRUE;
9332
9333                 if (programStats.got_only_move || !appData.periodicUpdates)
9334                   return;
9335
9336                 programStats.depth = plylev;
9337                 programStats.time = time;
9338                 programStats.nodes = nodes;
9339                 programStats.moves_left = mvleft;
9340                 programStats.nr_moves = mvtot;
9341                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9342                 programStats.ok_to_send = 1;
9343                 programStats.movelist[0] = '\0';
9344
9345                 SendProgramStatsToFrontend( cps, &programStats );
9346
9347                 return;
9348
9349             } else if (strncmp(message,"++",2) == 0) {
9350                 /* Crafty 9.29+ outputs this */
9351                 programStats.got_fail = 2;
9352                 return;
9353
9354             } else if (strncmp(message,"--",2) == 0) {
9355                 /* Crafty 9.29+ outputs this */
9356                 programStats.got_fail = 1;
9357                 return;
9358
9359             } else if (thinkOutput[0] != NULLCHAR &&
9360                        strncmp(message, "    ", 4) == 0) {
9361                 unsigned message_len;
9362
9363                 p = message;
9364                 while (*p && *p == ' ') p++;
9365
9366                 message_len = strlen( p );
9367
9368                 /* [AS] Avoid buffer overflow */
9369                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9370                     strcat(thinkOutput, " ");
9371                     strcat(thinkOutput, p);
9372                 }
9373
9374                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9375                     strcat(programStats.movelist, " ");
9376                     strcat(programStats.movelist, p);
9377                 }
9378
9379                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9380                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9381                     DisplayMove(currentMove - 1);
9382                 }
9383                 return;
9384             }
9385         }
9386         else {
9387             buf1[0] = NULLCHAR;
9388
9389             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9390                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9391             {
9392                 ChessProgramStats cpstats;
9393
9394                 if (plyext != ' ' && plyext != '\t') {
9395                     time *= 100;
9396                 }
9397
9398                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9399                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9400                     curscore = -curscore;
9401                 }
9402
9403                 cpstats.depth = plylev;
9404                 cpstats.nodes = nodes;
9405                 cpstats.time = time;
9406                 cpstats.score = curscore;
9407                 cpstats.got_only_move = 0;
9408                 cpstats.movelist[0] = '\0';
9409
9410                 if (buf1[0] != NULLCHAR) {
9411                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9412                 }
9413
9414                 cpstats.ok_to_send = 0;
9415                 cpstats.line_is_book = 0;
9416                 cpstats.nr_moves = 0;
9417                 cpstats.moves_left = 0;
9418
9419                 SendProgramStatsToFrontend( cps, &cpstats );
9420             }
9421         }
9422     }
9423 }
9424
9425
9426 /* Parse a game score from the character string "game", and
9427    record it as the history of the current game.  The game
9428    score is NOT assumed to start from the standard position.
9429    The display is not updated in any way.
9430    */
9431 void
9432 ParseGameHistory (char *game)
9433 {
9434     ChessMove moveType;
9435     int fromX, fromY, toX, toY, boardIndex;
9436     char promoChar;
9437     char *p, *q;
9438     char buf[MSG_SIZ];
9439
9440     if (appData.debugMode)
9441       fprintf(debugFP, "Parsing game history: %s\n", game);
9442
9443     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9444     gameInfo.site = StrSave(appData.icsHost);
9445     gameInfo.date = PGNDate();
9446     gameInfo.round = StrSave("-");
9447
9448     /* Parse out names of players */
9449     while (*game == ' ') game++;
9450     p = buf;
9451     while (*game != ' ') *p++ = *game++;
9452     *p = NULLCHAR;
9453     gameInfo.white = StrSave(buf);
9454     while (*game == ' ') game++;
9455     p = buf;
9456     while (*game != ' ' && *game != '\n') *p++ = *game++;
9457     *p = NULLCHAR;
9458     gameInfo.black = StrSave(buf);
9459
9460     /* Parse moves */
9461     boardIndex = blackPlaysFirst ? 1 : 0;
9462     yynewstr(game);
9463     for (;;) {
9464         yyboardindex = boardIndex;
9465         moveType = (ChessMove) Myylex();
9466         switch (moveType) {
9467           case IllegalMove:             /* maybe suicide chess, etc. */
9468   if (appData.debugMode) {
9469     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9470     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9471     setbuf(debugFP, NULL);
9472   }
9473           case WhitePromotion:
9474           case BlackPromotion:
9475           case WhiteNonPromotion:
9476           case BlackNonPromotion:
9477           case NormalMove:
9478           case WhiteCapturesEnPassant:
9479           case BlackCapturesEnPassant:
9480           case WhiteKingSideCastle:
9481           case WhiteQueenSideCastle:
9482           case BlackKingSideCastle:
9483           case BlackQueenSideCastle:
9484           case WhiteKingSideCastleWild:
9485           case WhiteQueenSideCastleWild:
9486           case BlackKingSideCastleWild:
9487           case BlackQueenSideCastleWild:
9488           /* PUSH Fabien */
9489           case WhiteHSideCastleFR:
9490           case WhiteASideCastleFR:
9491           case BlackHSideCastleFR:
9492           case BlackASideCastleFR:
9493           /* POP Fabien */
9494             fromX = currentMoveString[0] - AAA;
9495             fromY = currentMoveString[1] - ONE;
9496             toX = currentMoveString[2] - AAA;
9497             toY = currentMoveString[3] - ONE;
9498             promoChar = currentMoveString[4];
9499             break;
9500           case WhiteDrop:
9501           case BlackDrop:
9502             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9503             fromX = moveType == WhiteDrop ?
9504               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9505             (int) CharToPiece(ToLower(currentMoveString[0]));
9506             fromY = DROP_RANK;
9507             toX = currentMoveString[2] - AAA;
9508             toY = currentMoveString[3] - ONE;
9509             promoChar = NULLCHAR;
9510             break;
9511           case AmbiguousMove:
9512             /* bug? */
9513             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9514   if (appData.debugMode) {
9515     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9516     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9517     setbuf(debugFP, NULL);
9518   }
9519             DisplayError(buf, 0);
9520             return;
9521           case ImpossibleMove:
9522             /* bug? */
9523             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9524   if (appData.debugMode) {
9525     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9526     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9527     setbuf(debugFP, NULL);
9528   }
9529             DisplayError(buf, 0);
9530             return;
9531           case EndOfFile:
9532             if (boardIndex < backwardMostMove) {
9533                 /* Oops, gap.  How did that happen? */
9534                 DisplayError(_("Gap in move list"), 0);
9535                 return;
9536             }
9537             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9538             if (boardIndex > forwardMostMove) {
9539                 forwardMostMove = boardIndex;
9540             }
9541             return;
9542           case ElapsedTime:
9543             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9544                 strcat(parseList[boardIndex-1], " ");
9545                 strcat(parseList[boardIndex-1], yy_text);
9546             }
9547             continue;
9548           case Comment:
9549           case PGNTag:
9550           case NAG:
9551           default:
9552             /* ignore */
9553             continue;
9554           case WhiteWins:
9555           case BlackWins:
9556           case GameIsDrawn:
9557           case GameUnfinished:
9558             if (gameMode == IcsExamining) {
9559                 if (boardIndex < backwardMostMove) {
9560                     /* Oops, gap.  How did that happen? */
9561                     return;
9562                 }
9563                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9564                 return;
9565             }
9566             gameInfo.result = moveType;
9567             p = strchr(yy_text, '{');
9568             if (p == NULL) p = strchr(yy_text, '(');
9569             if (p == NULL) {
9570                 p = yy_text;
9571                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9572             } else {
9573                 q = strchr(p, *p == '{' ? '}' : ')');
9574                 if (q != NULL) *q = NULLCHAR;
9575                 p++;
9576             }
9577             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9578             gameInfo.resultDetails = StrSave(p);
9579             continue;
9580         }
9581         if (boardIndex >= forwardMostMove &&
9582             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9583             backwardMostMove = blackPlaysFirst ? 1 : 0;
9584             return;
9585         }
9586         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9587                                  fromY, fromX, toY, toX, promoChar,
9588                                  parseList[boardIndex]);
9589         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9590         /* currentMoveString is set as a side-effect of yylex */
9591         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9592         strcat(moveList[boardIndex], "\n");
9593         boardIndex++;
9594         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9595         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9596           case MT_NONE:
9597           case MT_STALEMATE:
9598           default:
9599             break;
9600           case MT_CHECK:
9601             if(gameInfo.variant != VariantShogi)
9602                 strcat(parseList[boardIndex - 1], "+");
9603             break;
9604           case MT_CHECKMATE:
9605           case MT_STAINMATE:
9606             strcat(parseList[boardIndex - 1], "#");
9607             break;
9608         }
9609     }
9610 }
9611
9612
9613 /* Apply a move to the given board  */
9614 void
9615 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9616 {
9617   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9618   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9619
9620     /* [HGM] compute & store e.p. status and castling rights for new position */
9621     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9622
9623       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9624       oldEP = (signed char)board[EP_STATUS];
9625       board[EP_STATUS] = EP_NONE;
9626
9627   if (fromY == DROP_RANK) {
9628         /* must be first */
9629         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9630             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9631             return;
9632         }
9633         piece = board[toY][toX] = (ChessSquare) fromX;
9634   } else {
9635       int i;
9636
9637       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9638            board[killY][killX] = EmptySquare,
9639            board[EP_STATUS] = EP_CAPTURE;
9640
9641       if( board[toY][toX] != EmptySquare )
9642            board[EP_STATUS] = EP_CAPTURE;
9643
9644       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9645            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9646                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9647       } else
9648       if( board[fromY][fromX] == WhitePawn ) {
9649            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9650                board[EP_STATUS] = EP_PAWN_MOVE;
9651            if( toY-fromY==2) {
9652                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9653                         gameInfo.variant != VariantBerolina || toX < fromX)
9654                       board[EP_STATUS] = toX | berolina;
9655                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9656                         gameInfo.variant != VariantBerolina || toX > fromX)
9657                       board[EP_STATUS] = toX;
9658            }
9659       } else
9660       if( board[fromY][fromX] == BlackPawn ) {
9661            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9662                board[EP_STATUS] = EP_PAWN_MOVE;
9663            if( toY-fromY== -2) {
9664                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9665                         gameInfo.variant != VariantBerolina || toX < fromX)
9666                       board[EP_STATUS] = toX | berolina;
9667                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9668                         gameInfo.variant != VariantBerolina || toX > fromX)
9669                       board[EP_STATUS] = toX;
9670            }
9671        }
9672
9673        for(i=0; i<nrCastlingRights; i++) {
9674            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9675               board[CASTLING][i] == toX   && castlingRank[i] == toY
9676              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9677        }
9678
9679        if(gameInfo.variant == VariantSChess) { // update virginity
9680            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9681            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9682            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9683            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9684        }
9685
9686      if (fromX == toX && fromY == toY) return;
9687
9688      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9689      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9690      if(gameInfo.variant == VariantKnightmate)
9691          king += (int) WhiteUnicorn - (int) WhiteKing;
9692
9693     /* Code added by Tord: */
9694     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9695     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9696         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9697       board[fromY][fromX] = EmptySquare;
9698       board[toY][toX] = EmptySquare;
9699       if((toX > fromX) != (piece == WhiteRook)) {
9700         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9701       } else {
9702         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9703       }
9704     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9705                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9706       board[fromY][fromX] = EmptySquare;
9707       board[toY][toX] = EmptySquare;
9708       if((toX > fromX) != (piece == BlackRook)) {
9709         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9710       } else {
9711         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9712       }
9713     /* End of code added by Tord */
9714
9715     } else if (board[fromY][fromX] == king
9716         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9717         && toY == fromY && toX > fromX+1) {
9718         board[fromY][fromX] = EmptySquare;
9719         board[toY][toX] = king;
9720         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9721         board[fromY][BOARD_RGHT-1] = EmptySquare;
9722     } else if (board[fromY][fromX] == king
9723         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9724                && toY == fromY && toX < fromX-1) {
9725         board[fromY][fromX] = EmptySquare;
9726         board[toY][toX] = king;
9727         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9728         board[fromY][BOARD_LEFT] = EmptySquare;
9729     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9730                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9731                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9732                ) {
9733         /* white pawn promotion */
9734         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9735         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9736             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9737         board[fromY][fromX] = EmptySquare;
9738     } else if ((fromY >= BOARD_HEIGHT>>1)
9739                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9740                && (toX != fromX)
9741                && gameInfo.variant != VariantXiangqi
9742                && gameInfo.variant != VariantBerolina
9743                && (board[fromY][fromX] == WhitePawn)
9744                && (board[toY][toX] == EmptySquare)) {
9745         board[fromY][fromX] = EmptySquare;
9746         board[toY][toX] = WhitePawn;
9747         captured = board[toY - 1][toX];
9748         board[toY - 1][toX] = EmptySquare;
9749     } else if ((fromY == BOARD_HEIGHT-4)
9750                && (toX == fromX)
9751                && gameInfo.variant == VariantBerolina
9752                && (board[fromY][fromX] == WhitePawn)
9753                && (board[toY][toX] == EmptySquare)) {
9754         board[fromY][fromX] = EmptySquare;
9755         board[toY][toX] = WhitePawn;
9756         if(oldEP & EP_BEROLIN_A) {
9757                 captured = board[fromY][fromX-1];
9758                 board[fromY][fromX-1] = EmptySquare;
9759         }else{  captured = board[fromY][fromX+1];
9760                 board[fromY][fromX+1] = EmptySquare;
9761         }
9762     } else if (board[fromY][fromX] == king
9763         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9764                && toY == fromY && toX > fromX+1) {
9765         board[fromY][fromX] = EmptySquare;
9766         board[toY][toX] = king;
9767         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9768         board[fromY][BOARD_RGHT-1] = EmptySquare;
9769     } else if (board[fromY][fromX] == king
9770         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9771                && toY == fromY && toX < fromX-1) {
9772         board[fromY][fromX] = EmptySquare;
9773         board[toY][toX] = king;
9774         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9775         board[fromY][BOARD_LEFT] = EmptySquare;
9776     } else if (fromY == 7 && fromX == 3
9777                && board[fromY][fromX] == BlackKing
9778                && toY == 7 && toX == 5) {
9779         board[fromY][fromX] = EmptySquare;
9780         board[toY][toX] = BlackKing;
9781         board[fromY][7] = EmptySquare;
9782         board[toY][4] = BlackRook;
9783     } else if (fromY == 7 && fromX == 3
9784                && board[fromY][fromX] == BlackKing
9785                && toY == 7 && toX == 1) {
9786         board[fromY][fromX] = EmptySquare;
9787         board[toY][toX] = BlackKing;
9788         board[fromY][0] = EmptySquare;
9789         board[toY][2] = BlackRook;
9790     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9791                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9792                && toY < promoRank && promoChar
9793                ) {
9794         /* black pawn promotion */
9795         board[toY][toX] = CharToPiece(ToLower(promoChar));
9796         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9797             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9798         board[fromY][fromX] = EmptySquare;
9799     } else if ((fromY < BOARD_HEIGHT>>1)
9800                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9801                && (toX != fromX)
9802                && gameInfo.variant != VariantXiangqi
9803                && gameInfo.variant != VariantBerolina
9804                && (board[fromY][fromX] == BlackPawn)
9805                && (board[toY][toX] == EmptySquare)) {
9806         board[fromY][fromX] = EmptySquare;
9807         board[toY][toX] = BlackPawn;
9808         captured = board[toY + 1][toX];
9809         board[toY + 1][toX] = EmptySquare;
9810     } else if ((fromY == 3)
9811                && (toX == fromX)
9812                && gameInfo.variant == VariantBerolina
9813                && (board[fromY][fromX] == BlackPawn)
9814                && (board[toY][toX] == EmptySquare)) {
9815         board[fromY][fromX] = EmptySquare;
9816         board[toY][toX] = BlackPawn;
9817         if(oldEP & EP_BEROLIN_A) {
9818                 captured = board[fromY][fromX-1];
9819                 board[fromY][fromX-1] = EmptySquare;
9820         }else{  captured = board[fromY][fromX+1];
9821                 board[fromY][fromX+1] = EmptySquare;
9822         }
9823     } else {
9824         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9825         board[fromY][fromX] = EmptySquare;
9826         board[toY][toX] = piece;
9827     }
9828   }
9829
9830     if (gameInfo.holdingsWidth != 0) {
9831
9832       /* !!A lot more code needs to be written to support holdings  */
9833       /* [HGM] OK, so I have written it. Holdings are stored in the */
9834       /* penultimate board files, so they are automaticlly stored   */
9835       /* in the game history.                                       */
9836       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9837                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9838         /* Delete from holdings, by decreasing count */
9839         /* and erasing image if necessary            */
9840         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9841         if(p < (int) BlackPawn) { /* white drop */
9842              p -= (int)WhitePawn;
9843                  p = PieceToNumber((ChessSquare)p);
9844              if(p >= gameInfo.holdingsSize) p = 0;
9845              if(--board[p][BOARD_WIDTH-2] <= 0)
9846                   board[p][BOARD_WIDTH-1] = EmptySquare;
9847              if((int)board[p][BOARD_WIDTH-2] < 0)
9848                         board[p][BOARD_WIDTH-2] = 0;
9849         } else {                  /* black drop */
9850              p -= (int)BlackPawn;
9851                  p = PieceToNumber((ChessSquare)p);
9852              if(p >= gameInfo.holdingsSize) p = 0;
9853              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9854                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9855              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9856                         board[BOARD_HEIGHT-1-p][1] = 0;
9857         }
9858       }
9859       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9860           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9861         /* [HGM] holdings: Add to holdings, if holdings exist */
9862         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9863                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9864                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9865         }
9866         p = (int) captured;
9867         if (p >= (int) BlackPawn) {
9868           p -= (int)BlackPawn;
9869           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9870                   /* in Shogi restore piece to its original  first */
9871                   captured = (ChessSquare) (DEMOTED captured);
9872                   p = DEMOTED p;
9873           }
9874           p = PieceToNumber((ChessSquare)p);
9875           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9876           board[p][BOARD_WIDTH-2]++;
9877           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9878         } else {
9879           p -= (int)WhitePawn;
9880           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9881                   captured = (ChessSquare) (DEMOTED captured);
9882                   p = DEMOTED p;
9883           }
9884           p = PieceToNumber((ChessSquare)p);
9885           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9886           board[BOARD_HEIGHT-1-p][1]++;
9887           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9888         }
9889       }
9890     } else if (gameInfo.variant == VariantAtomic) {
9891       if (captured != EmptySquare) {
9892         int y, x;
9893         for (y = toY-1; y <= toY+1; y++) {
9894           for (x = toX-1; x <= toX+1; x++) {
9895             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9896                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9897               board[y][x] = EmptySquare;
9898             }
9899           }
9900         }
9901         board[toY][toX] = EmptySquare;
9902       }
9903     }
9904     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9905         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9906     } else
9907     if(promoChar == '+') {
9908         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9909         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9910     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9911         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9912         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9913            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9914         board[toY][toX] = newPiece;
9915     }
9916     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9917                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9918         // [HGM] superchess: take promotion piece out of holdings
9919         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9920         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9921             if(!--board[k][BOARD_WIDTH-2])
9922                 board[k][BOARD_WIDTH-1] = EmptySquare;
9923         } else {
9924             if(!--board[BOARD_HEIGHT-1-k][1])
9925                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9926         }
9927     }
9928
9929 }
9930
9931 /* Updates forwardMostMove */
9932 void
9933 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9934 {
9935     int x = toX, y = toY;
9936     char *s = parseList[forwardMostMove];
9937     ChessSquare p = boards[forwardMostMove][toY][toX];
9938 //    forwardMostMove++; // [HGM] bare: moved downstream
9939
9940     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9941     (void) CoordsToAlgebraic(boards[forwardMostMove],
9942                              PosFlags(forwardMostMove),
9943                              fromY, fromX, y, x, promoChar,
9944                              s);
9945     if(killX >= 0 && killY >= 0)
9946         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9947
9948     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9949         int timeLeft; static int lastLoadFlag=0; int king, piece;
9950         piece = boards[forwardMostMove][fromY][fromX];
9951         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9952         if(gameInfo.variant == VariantKnightmate)
9953             king += (int) WhiteUnicorn - (int) WhiteKing;
9954         if(forwardMostMove == 0) {
9955             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9956                 fprintf(serverMoves, "%s;", UserName());
9957             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9958                 fprintf(serverMoves, "%s;", second.tidy);
9959             fprintf(serverMoves, "%s;", first.tidy);
9960             if(gameMode == MachinePlaysWhite)
9961                 fprintf(serverMoves, "%s;", UserName());
9962             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9963                 fprintf(serverMoves, "%s;", second.tidy);
9964         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9965         lastLoadFlag = loadFlag;
9966         // print base move
9967         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9968         // print castling suffix
9969         if( toY == fromY && piece == king ) {
9970             if(toX-fromX > 1)
9971                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9972             if(fromX-toX >1)
9973                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9974         }
9975         // e.p. suffix
9976         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9977              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9978              boards[forwardMostMove][toY][toX] == EmptySquare
9979              && fromX != toX && fromY != toY)
9980                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9981         // promotion suffix
9982         if(promoChar != NULLCHAR) {
9983             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9984                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9985                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9986             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9987         }
9988         if(!loadFlag) {
9989                 char buf[MOVE_LEN*2], *p; int len;
9990             fprintf(serverMoves, "/%d/%d",
9991                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9992             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9993             else                      timeLeft = blackTimeRemaining/1000;
9994             fprintf(serverMoves, "/%d", timeLeft);
9995                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9996                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9997                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9998                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9999             fprintf(serverMoves, "/%s", buf);
10000         }
10001         fflush(serverMoves);
10002     }
10003
10004     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10005         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10006       return;
10007     }
10008     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10009     if (commentList[forwardMostMove+1] != NULL) {
10010         free(commentList[forwardMostMove+1]);
10011         commentList[forwardMostMove+1] = NULL;
10012     }
10013     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10014     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10015     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10016     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10017     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10018     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10019     adjustedClock = FALSE;
10020     gameInfo.result = GameUnfinished;
10021     if (gameInfo.resultDetails != NULL) {
10022         free(gameInfo.resultDetails);
10023         gameInfo.resultDetails = NULL;
10024     }
10025     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10026                               moveList[forwardMostMove - 1]);
10027     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10028       case MT_NONE:
10029       case MT_STALEMATE:
10030       default:
10031         break;
10032       case MT_CHECK:
10033         if(gameInfo.variant != VariantShogi)
10034             strcat(parseList[forwardMostMove - 1], "+");
10035         break;
10036       case MT_CHECKMATE:
10037       case MT_STAINMATE:
10038         strcat(parseList[forwardMostMove - 1], "#");
10039         break;
10040     }
10041
10042     killX = killY = -1; // [HGM] lion: used up
10043 }
10044
10045 /* Updates currentMove if not pausing */
10046 void
10047 ShowMove (int fromX, int fromY, int toX, int toY)
10048 {
10049     int instant = (gameMode == PlayFromGameFile) ?
10050         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10051     if(appData.noGUI) return;
10052     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10053         if (!instant) {
10054             if (forwardMostMove == currentMove + 1) {
10055                 AnimateMove(boards[forwardMostMove - 1],
10056                             fromX, fromY, toX, toY);
10057             }
10058         }
10059         currentMove = forwardMostMove;
10060     }
10061
10062     if (instant) return;
10063
10064     DisplayMove(currentMove - 1);
10065     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10066             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10067                 SetHighlights(fromX, fromY, toX, toY);
10068             }
10069     }
10070     DrawPosition(FALSE, boards[currentMove]);
10071     DisplayBothClocks();
10072     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10073 }
10074
10075 void
10076 SendEgtPath (ChessProgramState *cps)
10077 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10078         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10079
10080         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10081
10082         while(*p) {
10083             char c, *q = name+1, *r, *s;
10084
10085             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10086             while(*p && *p != ',') *q++ = *p++;
10087             *q++ = ':'; *q = 0;
10088             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10089                 strcmp(name, ",nalimov:") == 0 ) {
10090                 // take nalimov path from the menu-changeable option first, if it is defined
10091               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10092                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10093             } else
10094             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10095                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10096                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10097                 s = r = StrStr(s, ":") + 1; // beginning of path info
10098                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10099                 c = *r; *r = 0;             // temporarily null-terminate path info
10100                     *--q = 0;               // strip of trailig ':' from name
10101                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10102                 *r = c;
10103                 SendToProgram(buf,cps);     // send egtbpath command for this format
10104             }
10105             if(*p == ',') p++; // read away comma to position for next format name
10106         }
10107 }
10108
10109 static int
10110 NonStandardBoardSize ()
10111 {
10112       /* [HGM] Awkward testing. Should really be a table */
10113       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10114       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10115       if( gameInfo.variant == VariantXiangqi )
10116            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10117       if( gameInfo.variant == VariantShogi )
10118            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10119       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10120            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10121       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10122           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10123            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10124       if( gameInfo.variant == VariantCourier )
10125            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10126       if( gameInfo.variant == VariantSuper )
10127            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10128       if( gameInfo.variant == VariantGreat )
10129            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10130       if( gameInfo.variant == VariantSChess )
10131            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10132       if( gameInfo.variant == VariantGrand )
10133            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10134       if( gameInfo.variant == VariantChu )
10135            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10136       return overruled;
10137 }
10138
10139 void
10140 InitChessProgram (ChessProgramState *cps, int setup)
10141 /* setup needed to setup FRC opening position */
10142 {
10143     char buf[MSG_SIZ], b[MSG_SIZ];
10144     if (appData.noChessProgram) return;
10145     hintRequested = FALSE;
10146     bookRequested = FALSE;
10147
10148     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10149     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10150     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10151     if(cps->memSize) { /* [HGM] memory */
10152       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10153         SendToProgram(buf, cps);
10154     }
10155     SendEgtPath(cps); /* [HGM] EGT */
10156     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10157       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10158         SendToProgram(buf, cps);
10159     }
10160
10161     SendToProgram(cps->initString, cps);
10162     if (gameInfo.variant != VariantNormal &&
10163         gameInfo.variant != VariantLoadable
10164         /* [HGM] also send variant if board size non-standard */
10165         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10166                                             ) {
10167       char *v = VariantName(gameInfo.variant);
10168       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10169         /* [HGM] in protocol 1 we have to assume all variants valid */
10170         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10171         DisplayFatalError(buf, 0, 1);
10172         return;
10173       }
10174
10175       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10176         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10177                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10178            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10179            if(StrStr(cps->variants, b) == NULL) {
10180                // specific sized variant not known, check if general sizing allowed
10181                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10182                    if(StrStr(cps->variants, "boardsize") == NULL) {
10183                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10184                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10185                        DisplayFatalError(buf, 0, 1);
10186                        return;
10187                    }
10188                    /* [HGM] here we really should compare with the maximum supported board size */
10189                }
10190            }
10191       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10192       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10193       SendToProgram(buf, cps);
10194     }
10195     currentlyInitializedVariant = gameInfo.variant;
10196
10197     /* [HGM] send opening position in FRC to first engine */
10198     if(setup) {
10199           SendToProgram("force\n", cps);
10200           SendBoard(cps, 0);
10201           /* engine is now in force mode! Set flag to wake it up after first move. */
10202           setboardSpoiledMachineBlack = 1;
10203     }
10204
10205     if (cps->sendICS) {
10206       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10207       SendToProgram(buf, cps);
10208     }
10209     cps->maybeThinking = FALSE;
10210     cps->offeredDraw = 0;
10211     if (!appData.icsActive) {
10212         SendTimeControl(cps, movesPerSession, timeControl,
10213                         timeIncrement, appData.searchDepth,
10214                         searchTime);
10215     }
10216     if (appData.showThinking
10217         // [HGM] thinking: four options require thinking output to be sent
10218         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10219                                 ) {
10220         SendToProgram("post\n", cps);
10221     }
10222     SendToProgram("hard\n", cps);
10223     if (!appData.ponderNextMove) {
10224         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10225            it without being sure what state we are in first.  "hard"
10226            is not a toggle, so that one is OK.
10227          */
10228         SendToProgram("easy\n", cps);
10229     }
10230     if (cps->usePing) {
10231       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10232       SendToProgram(buf, cps);
10233     }
10234     cps->initDone = TRUE;
10235     ClearEngineOutputPane(cps == &second);
10236 }
10237
10238
10239 void
10240 ResendOptions (ChessProgramState *cps)
10241 { // send the stored value of the options
10242   int i;
10243   char buf[MSG_SIZ];
10244   Option *opt = cps->option;
10245   for(i=0; i<cps->nrOptions; i++, opt++) {
10246       switch(opt->type) {
10247         case Spin:
10248         case Slider:
10249         case CheckBox:
10250             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10251           break;
10252         case ComboBox:
10253           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10254           break;
10255         default:
10256             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10257           break;
10258         case Button:
10259         case SaveButton:
10260           continue;
10261       }
10262       SendToProgram(buf, cps);
10263   }
10264 }
10265
10266 void
10267 StartChessProgram (ChessProgramState *cps)
10268 {
10269     char buf[MSG_SIZ];
10270     int err;
10271
10272     if (appData.noChessProgram) return;
10273     cps->initDone = FALSE;
10274
10275     if (strcmp(cps->host, "localhost") == 0) {
10276         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10277     } else if (*appData.remoteShell == NULLCHAR) {
10278         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10279     } else {
10280         if (*appData.remoteUser == NULLCHAR) {
10281           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10282                     cps->program);
10283         } else {
10284           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10285                     cps->host, appData.remoteUser, cps->program);
10286         }
10287         err = StartChildProcess(buf, "", &cps->pr);
10288     }
10289
10290     if (err != 0) {
10291       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10292         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10293         if(cps != &first) return;
10294         appData.noChessProgram = TRUE;
10295         ThawUI();
10296         SetNCPMode();
10297 //      DisplayFatalError(buf, err, 1);
10298 //      cps->pr = NoProc;
10299 //      cps->isr = NULL;
10300         return;
10301     }
10302
10303     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10304     if (cps->protocolVersion > 1) {
10305       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10306       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10307         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10308         cps->comboCnt = 0;  //                and values of combo boxes
10309       }
10310       SendToProgram(buf, cps);
10311       if(cps->reload) ResendOptions(cps);
10312     } else {
10313       SendToProgram("xboard\n", cps);
10314     }
10315 }
10316
10317 void
10318 TwoMachinesEventIfReady P((void))
10319 {
10320   static int curMess = 0;
10321   if (first.lastPing != first.lastPong) {
10322     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10323     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10324     return;
10325   }
10326   if (second.lastPing != second.lastPong) {
10327     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10328     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10329     return;
10330   }
10331   DisplayMessage("", ""); curMess = 0;
10332   TwoMachinesEvent();
10333 }
10334
10335 char *
10336 MakeName (char *template)
10337 {
10338     time_t clock;
10339     struct tm *tm;
10340     static char buf[MSG_SIZ];
10341     char *p = buf;
10342     int i;
10343
10344     clock = time((time_t *)NULL);
10345     tm = localtime(&clock);
10346
10347     while(*p++ = *template++) if(p[-1] == '%') {
10348         switch(*template++) {
10349           case 0:   *p = 0; return buf;
10350           case 'Y': i = tm->tm_year+1900; break;
10351           case 'y': i = tm->tm_year-100; break;
10352           case 'M': i = tm->tm_mon+1; break;
10353           case 'd': i = tm->tm_mday; break;
10354           case 'h': i = tm->tm_hour; break;
10355           case 'm': i = tm->tm_min; break;
10356           case 's': i = tm->tm_sec; break;
10357           default:  i = 0;
10358         }
10359         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10360     }
10361     return buf;
10362 }
10363
10364 int
10365 CountPlayers (char *p)
10366 {
10367     int n = 0;
10368     while(p = strchr(p, '\n')) p++, n++; // count participants
10369     return n;
10370 }
10371
10372 FILE *
10373 WriteTourneyFile (char *results, FILE *f)
10374 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10375     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10376     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10377         // create a file with tournament description
10378         fprintf(f, "-participants {%s}\n", appData.participants);
10379         fprintf(f, "-seedBase %d\n", appData.seedBase);
10380         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10381         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10382         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10383         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10384         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10385         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10386         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10387         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10388         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10389         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10390         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10391         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10392         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10393         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10394         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10395         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10396         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10397         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10398         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10399         fprintf(f, "-smpCores %d\n", appData.smpCores);
10400         if(searchTime > 0)
10401                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10402         else {
10403                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10404                 fprintf(f, "-tc %s\n", appData.timeControl);
10405                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10406         }
10407         fprintf(f, "-results \"%s\"\n", results);
10408     }
10409     return f;
10410 }
10411
10412 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10413
10414 void
10415 Substitute (char *participants, int expunge)
10416 {
10417     int i, changed, changes=0, nPlayers=0;
10418     char *p, *q, *r, buf[MSG_SIZ];
10419     if(participants == NULL) return;
10420     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10421     r = p = participants; q = appData.participants;
10422     while(*p && *p == *q) {
10423         if(*p == '\n') r = p+1, nPlayers++;
10424         p++; q++;
10425     }
10426     if(*p) { // difference
10427         while(*p && *p++ != '\n');
10428         while(*q && *q++ != '\n');
10429       changed = nPlayers;
10430         changes = 1 + (strcmp(p, q) != 0);
10431     }
10432     if(changes == 1) { // a single engine mnemonic was changed
10433         q = r; while(*q) nPlayers += (*q++ == '\n');
10434         p = buf; while(*r && (*p = *r++) != '\n') p++;
10435         *p = NULLCHAR;
10436         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10437         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10438         if(mnemonic[i]) { // The substitute is valid
10439             FILE *f;
10440             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10441                 flock(fileno(f), LOCK_EX);
10442                 ParseArgsFromFile(f);
10443                 fseek(f, 0, SEEK_SET);
10444                 FREE(appData.participants); appData.participants = participants;
10445                 if(expunge) { // erase results of replaced engine
10446                     int len = strlen(appData.results), w, b, dummy;
10447                     for(i=0; i<len; i++) {
10448                         Pairing(i, nPlayers, &w, &b, &dummy);
10449                         if((w == changed || b == changed) && appData.results[i] == '*') {
10450                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10451                             fclose(f);
10452                             return;
10453                         }
10454                     }
10455                     for(i=0; i<len; i++) {
10456                         Pairing(i, nPlayers, &w, &b, &dummy);
10457                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10458                     }
10459                 }
10460                 WriteTourneyFile(appData.results, f);
10461                 fclose(f); // release lock
10462                 return;
10463             }
10464         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10465     }
10466     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10467     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10468     free(participants);
10469     return;
10470 }
10471
10472 int
10473 CheckPlayers (char *participants)
10474 {
10475         int i;
10476         char buf[MSG_SIZ], *p;
10477         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10478         while(p = strchr(participants, '\n')) {
10479             *p = NULLCHAR;
10480             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10481             if(!mnemonic[i]) {
10482                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10483                 *p = '\n';
10484                 DisplayError(buf, 0);
10485                 return 1;
10486             }
10487             *p = '\n';
10488             participants = p + 1;
10489         }
10490         return 0;
10491 }
10492
10493 int
10494 CreateTourney (char *name)
10495 {
10496         FILE *f;
10497         if(matchMode && strcmp(name, appData.tourneyFile)) {
10498              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10499         }
10500         if(name[0] == NULLCHAR) {
10501             if(appData.participants[0])
10502                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10503             return 0;
10504         }
10505         f = fopen(name, "r");
10506         if(f) { // file exists
10507             ASSIGN(appData.tourneyFile, name);
10508             ParseArgsFromFile(f); // parse it
10509         } else {
10510             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10511             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10512                 DisplayError(_("Not enough participants"), 0);
10513                 return 0;
10514             }
10515             if(CheckPlayers(appData.participants)) return 0;
10516             ASSIGN(appData.tourneyFile, name);
10517             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10518             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10519         }
10520         fclose(f);
10521         appData.noChessProgram = FALSE;
10522         appData.clockMode = TRUE;
10523         SetGNUMode();
10524         return 1;
10525 }
10526
10527 int
10528 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10529 {
10530     char buf[MSG_SIZ], *p, *q;
10531     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10532     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10533     skip = !all && group[0]; // if group requested, we start in skip mode
10534     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10535         p = names; q = buf; header = 0;
10536         while(*p && *p != '\n') *q++ = *p++;
10537         *q = 0;
10538         if(*p == '\n') p++;
10539         if(buf[0] == '#') {
10540             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10541             depth++; // we must be entering a new group
10542             if(all) continue; // suppress printing group headers when complete list requested
10543             header = 1;
10544             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10545         }
10546         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10547         if(engineList[i]) free(engineList[i]);
10548         engineList[i] = strdup(buf);
10549         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10550         if(engineMnemonic[i]) free(engineMnemonic[i]);
10551         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10552             strcat(buf, " (");
10553             sscanf(q + 8, "%s", buf + strlen(buf));
10554             strcat(buf, ")");
10555         }
10556         engineMnemonic[i] = strdup(buf);
10557         i++;
10558     }
10559     engineList[i] = engineMnemonic[i] = NULL;
10560     return i;
10561 }
10562
10563 // following implemented as macro to avoid type limitations
10564 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10565
10566 void
10567 SwapEngines (int n)
10568 {   // swap settings for first engine and other engine (so far only some selected options)
10569     int h;
10570     char *p;
10571     if(n == 0) return;
10572     SWAP(directory, p)
10573     SWAP(chessProgram, p)
10574     SWAP(isUCI, h)
10575     SWAP(hasOwnBookUCI, h)
10576     SWAP(protocolVersion, h)
10577     SWAP(reuse, h)
10578     SWAP(scoreIsAbsolute, h)
10579     SWAP(timeOdds, h)
10580     SWAP(logo, p)
10581     SWAP(pgnName, p)
10582     SWAP(pvSAN, h)
10583     SWAP(engOptions, p)
10584     SWAP(engInitString, p)
10585     SWAP(computerString, p)
10586     SWAP(features, p)
10587     SWAP(fenOverride, p)
10588     SWAP(NPS, h)
10589     SWAP(accumulateTC, h)
10590     SWAP(host, p)
10591 }
10592
10593 int
10594 GetEngineLine (char *s, int n)
10595 {
10596     int i;
10597     char buf[MSG_SIZ];
10598     extern char *icsNames;
10599     if(!s || !*s) return 0;
10600     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10601     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10602     if(!mnemonic[i]) return 0;
10603     if(n == 11) return 1; // just testing if there was a match
10604     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10605     if(n == 1) SwapEngines(n);
10606     ParseArgsFromString(buf);
10607     if(n == 1) SwapEngines(n);
10608     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10609         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10610         ParseArgsFromString(buf);
10611     }
10612     return 1;
10613 }
10614
10615 int
10616 SetPlayer (int player, char *p)
10617 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10618     int i;
10619     char buf[MSG_SIZ], *engineName;
10620     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10621     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10622     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10623     if(mnemonic[i]) {
10624         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10625         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10626         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10627         ParseArgsFromString(buf);
10628     } else { // no engine with this nickname is installed!
10629         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10630         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10631         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10632         ModeHighlight();
10633         DisplayError(buf, 0);
10634         return 0;
10635     }
10636     free(engineName);
10637     return i;
10638 }
10639
10640 char *recentEngines;
10641
10642 void
10643 RecentEngineEvent (int nr)
10644 {
10645     int n;
10646 //    SwapEngines(1); // bump first to second
10647 //    ReplaceEngine(&second, 1); // and load it there
10648     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10649     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10650     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10651         ReplaceEngine(&first, 0);
10652         FloatToFront(&appData.recentEngineList, command[n]);
10653     }
10654 }
10655
10656 int
10657 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10658 {   // determine players from game number
10659     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10660
10661     if(appData.tourneyType == 0) {
10662         roundsPerCycle = (nPlayers - 1) | 1;
10663         pairingsPerRound = nPlayers / 2;
10664     } else if(appData.tourneyType > 0) {
10665         roundsPerCycle = nPlayers - appData.tourneyType;
10666         pairingsPerRound = appData.tourneyType;
10667     }
10668     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10669     gamesPerCycle = gamesPerRound * roundsPerCycle;
10670     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10671     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10672     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10673     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10674     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10675     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10676
10677     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10678     if(appData.roundSync) *syncInterval = gamesPerRound;
10679
10680     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10681
10682     if(appData.tourneyType == 0) {
10683         if(curPairing == (nPlayers-1)/2 ) {
10684             *whitePlayer = curRound;
10685             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10686         } else {
10687             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10688             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10689             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10690             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10691         }
10692     } else if(appData.tourneyType > 1) {
10693         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10694         *whitePlayer = curRound + appData.tourneyType;
10695     } else if(appData.tourneyType > 0) {
10696         *whitePlayer = curPairing;
10697         *blackPlayer = curRound + appData.tourneyType;
10698     }
10699
10700     // take care of white/black alternation per round.
10701     // For cycles and games this is already taken care of by default, derived from matchGame!
10702     return curRound & 1;
10703 }
10704
10705 int
10706 NextTourneyGame (int nr, int *swapColors)
10707 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10708     char *p, *q;
10709     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10710     FILE *tf;
10711     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10712     tf = fopen(appData.tourneyFile, "r");
10713     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10714     ParseArgsFromFile(tf); fclose(tf);
10715     InitTimeControls(); // TC might be altered from tourney file
10716
10717     nPlayers = CountPlayers(appData.participants); // count participants
10718     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10719     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10720
10721     if(syncInterval) {
10722         p = q = appData.results;
10723         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10724         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10725             DisplayMessage(_("Waiting for other game(s)"),"");
10726             waitingForGame = TRUE;
10727             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10728             return 0;
10729         }
10730         waitingForGame = FALSE;
10731     }
10732
10733     if(appData.tourneyType < 0) {
10734         if(nr>=0 && !pairingReceived) {
10735             char buf[1<<16];
10736             if(pairing.pr == NoProc) {
10737                 if(!appData.pairingEngine[0]) {
10738                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10739                     return 0;
10740                 }
10741                 StartChessProgram(&pairing); // starts the pairing engine
10742             }
10743             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10744             SendToProgram(buf, &pairing);
10745             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10746             SendToProgram(buf, &pairing);
10747             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10748         }
10749         pairingReceived = 0;                              // ... so we continue here
10750         *swapColors = 0;
10751         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10752         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10753         matchGame = 1; roundNr = nr / syncInterval + 1;
10754     }
10755
10756     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10757
10758     // redefine engines, engine dir, etc.
10759     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10760     if(first.pr == NoProc) {
10761       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10762       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10763     }
10764     if(second.pr == NoProc) {
10765       SwapEngines(1);
10766       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10767       SwapEngines(1);         // and make that valid for second engine by swapping
10768       InitEngine(&second, 1);
10769     }
10770     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10771     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10772     return OK;
10773 }
10774
10775 void
10776 NextMatchGame ()
10777 {   // performs game initialization that does not invoke engines, and then tries to start the game
10778     int res, firstWhite, swapColors = 0;
10779     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10780     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
10781         char buf[MSG_SIZ];
10782         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10783         if(strcmp(buf, currentDebugFile)) { // name has changed
10784             FILE *f = fopen(buf, "w");
10785             if(f) { // if opening the new file failed, just keep using the old one
10786                 ASSIGN(currentDebugFile, buf);
10787                 fclose(debugFP);
10788                 debugFP = f;
10789             }
10790             if(appData.serverFileName) {
10791                 if(serverFP) fclose(serverFP);
10792                 serverFP = fopen(appData.serverFileName, "w");
10793                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10794                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10795             }
10796         }
10797     }
10798     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10799     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10800     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10801     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10802     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10803     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10804     Reset(FALSE, first.pr != NoProc);
10805     res = LoadGameOrPosition(matchGame); // setup game
10806     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10807     if(!res) return; // abort when bad game/pos file
10808     TwoMachinesEvent();
10809 }
10810
10811 void
10812 UserAdjudicationEvent (int result)
10813 {
10814     ChessMove gameResult = GameIsDrawn;
10815
10816     if( result > 0 ) {
10817         gameResult = WhiteWins;
10818     }
10819     else if( result < 0 ) {
10820         gameResult = BlackWins;
10821     }
10822
10823     if( gameMode == TwoMachinesPlay ) {
10824         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10825     }
10826 }
10827
10828
10829 // [HGM] save: calculate checksum of game to make games easily identifiable
10830 int
10831 StringCheckSum (char *s)
10832 {
10833         int i = 0;
10834         if(s==NULL) return 0;
10835         while(*s) i = i*259 + *s++;
10836         return i;
10837 }
10838
10839 int
10840 GameCheckSum ()
10841 {
10842         int i, sum=0;
10843         for(i=backwardMostMove; i<forwardMostMove; i++) {
10844                 sum += pvInfoList[i].depth;
10845                 sum += StringCheckSum(parseList[i]);
10846                 sum += StringCheckSum(commentList[i]);
10847                 sum *= 261;
10848         }
10849         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10850         return sum + StringCheckSum(commentList[i]);
10851 } // end of save patch
10852
10853 void
10854 GameEnds (ChessMove result, char *resultDetails, int whosays)
10855 {
10856     GameMode nextGameMode;
10857     int isIcsGame;
10858     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10859
10860     if(endingGame) return; /* [HGM] crash: forbid recursion */
10861     endingGame = 1;
10862     if(twoBoards) { // [HGM] dual: switch back to one board
10863         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10864         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10865     }
10866     if (appData.debugMode) {
10867       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10868               result, resultDetails ? resultDetails : "(null)", whosays);
10869     }
10870
10871     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10872
10873     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10874
10875     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10876         /* If we are playing on ICS, the server decides when the
10877            game is over, but the engine can offer to draw, claim
10878            a draw, or resign.
10879          */
10880 #if ZIPPY
10881         if (appData.zippyPlay && first.initDone) {
10882             if (result == GameIsDrawn) {
10883                 /* In case draw still needs to be claimed */
10884                 SendToICS(ics_prefix);
10885                 SendToICS("draw\n");
10886             } else if (StrCaseStr(resultDetails, "resign")) {
10887                 SendToICS(ics_prefix);
10888                 SendToICS("resign\n");
10889             }
10890         }
10891 #endif
10892         endingGame = 0; /* [HGM] crash */
10893         return;
10894     }
10895
10896     /* If we're loading the game from a file, stop */
10897     if (whosays == GE_FILE) {
10898       (void) StopLoadGameTimer();
10899       gameFileFP = NULL;
10900     }
10901
10902     /* Cancel draw offers */
10903     first.offeredDraw = second.offeredDraw = 0;
10904
10905     /* If this is an ICS game, only ICS can really say it's done;
10906        if not, anyone can. */
10907     isIcsGame = (gameMode == IcsPlayingWhite ||
10908                  gameMode == IcsPlayingBlack ||
10909                  gameMode == IcsObserving    ||
10910                  gameMode == IcsExamining);
10911
10912     if (!isIcsGame || whosays == GE_ICS) {
10913         /* OK -- not an ICS game, or ICS said it was done */
10914         StopClocks();
10915         if (!isIcsGame && !appData.noChessProgram)
10916           SetUserThinkingEnables();
10917
10918         /* [HGM] if a machine claims the game end we verify this claim */
10919         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10920             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10921                 char claimer;
10922                 ChessMove trueResult = (ChessMove) -1;
10923
10924                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10925                                             first.twoMachinesColor[0] :
10926                                             second.twoMachinesColor[0] ;
10927
10928                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10929                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10930                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10931                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10932                 } else
10933                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10934                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10935                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10936                 } else
10937                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10938                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10939                 }
10940
10941                 // now verify win claims, but not in drop games, as we don't understand those yet
10942                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10943                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10944                     (result == WhiteWins && claimer == 'w' ||
10945                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10946                       if (appData.debugMode) {
10947                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10948                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10949                       }
10950                       if(result != trueResult) {
10951                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10952                               result = claimer == 'w' ? BlackWins : WhiteWins;
10953                               resultDetails = buf;
10954                       }
10955                 } else
10956                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10957                     && (forwardMostMove <= backwardMostMove ||
10958                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10959                         (claimer=='b')==(forwardMostMove&1))
10960                                                                                   ) {
10961                       /* [HGM] verify: draws that were not flagged are false claims */
10962                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10963                       result = claimer == 'w' ? BlackWins : WhiteWins;
10964                       resultDetails = buf;
10965                 }
10966                 /* (Claiming a loss is accepted no questions asked!) */
10967             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10968                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10969                 result = GameUnfinished;
10970                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10971             }
10972             /* [HGM] bare: don't allow bare King to win */
10973             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10974                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10975                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10976                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10977                && result != GameIsDrawn)
10978             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10979                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10980                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10981                         if(p >= 0 && p <= (int)WhiteKing) k++;
10982                 }
10983                 if (appData.debugMode) {
10984                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10985                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10986                 }
10987                 if(k <= 1) {
10988                         result = GameIsDrawn;
10989                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10990                         resultDetails = buf;
10991                 }
10992             }
10993         }
10994
10995
10996         if(serverMoves != NULL && !loadFlag) { char c = '=';
10997             if(result==WhiteWins) c = '+';
10998             if(result==BlackWins) c = '-';
10999             if(resultDetails != NULL)
11000                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11001         }
11002         if (resultDetails != NULL) {
11003             gameInfo.result = result;
11004             gameInfo.resultDetails = StrSave(resultDetails);
11005
11006             /* display last move only if game was not loaded from file */
11007             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11008                 DisplayMove(currentMove - 1);
11009
11010             if (forwardMostMove != 0) {
11011                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11012                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11013                                                                 ) {
11014                     if (*appData.saveGameFile != NULLCHAR) {
11015                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11016                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11017                         else
11018                         SaveGameToFile(appData.saveGameFile, TRUE);
11019                     } else if (appData.autoSaveGames) {
11020                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11021                     }
11022                     if (*appData.savePositionFile != NULLCHAR) {
11023                         SavePositionToFile(appData.savePositionFile);
11024                     }
11025                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11026                 }
11027             }
11028
11029             /* Tell program how game ended in case it is learning */
11030             /* [HGM] Moved this to after saving the PGN, just in case */
11031             /* engine died and we got here through time loss. In that */
11032             /* case we will get a fatal error writing the pipe, which */
11033             /* would otherwise lose us the PGN.                       */
11034             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11035             /* output during GameEnds should never be fatal anymore   */
11036             if (gameMode == MachinePlaysWhite ||
11037                 gameMode == MachinePlaysBlack ||
11038                 gameMode == TwoMachinesPlay ||
11039                 gameMode == IcsPlayingWhite ||
11040                 gameMode == IcsPlayingBlack ||
11041                 gameMode == BeginningOfGame) {
11042                 char buf[MSG_SIZ];
11043                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11044                         resultDetails);
11045                 if (first.pr != NoProc) {
11046                     SendToProgram(buf, &first);
11047                 }
11048                 if (second.pr != NoProc &&
11049                     gameMode == TwoMachinesPlay) {
11050                     SendToProgram(buf, &second);
11051                 }
11052             }
11053         }
11054
11055         if (appData.icsActive) {
11056             if (appData.quietPlay &&
11057                 (gameMode == IcsPlayingWhite ||
11058                  gameMode == IcsPlayingBlack)) {
11059                 SendToICS(ics_prefix);
11060                 SendToICS("set shout 1\n");
11061             }
11062             nextGameMode = IcsIdle;
11063             ics_user_moved = FALSE;
11064             /* clean up premove.  It's ugly when the game has ended and the
11065              * premove highlights are still on the board.
11066              */
11067             if (gotPremove) {
11068               gotPremove = FALSE;
11069               ClearPremoveHighlights();
11070               DrawPosition(FALSE, boards[currentMove]);
11071             }
11072             if (whosays == GE_ICS) {
11073                 switch (result) {
11074                 case WhiteWins:
11075                     if (gameMode == IcsPlayingWhite)
11076                         PlayIcsWinSound();
11077                     else if(gameMode == IcsPlayingBlack)
11078                         PlayIcsLossSound();
11079                     break;
11080                 case BlackWins:
11081                     if (gameMode == IcsPlayingBlack)
11082                         PlayIcsWinSound();
11083                     else if(gameMode == IcsPlayingWhite)
11084                         PlayIcsLossSound();
11085                     break;
11086                 case GameIsDrawn:
11087                     PlayIcsDrawSound();
11088                     break;
11089                 default:
11090                     PlayIcsUnfinishedSound();
11091                 }
11092             }
11093             if(appData.quitNext) { ExitEvent(0); return; }
11094         } else if (gameMode == EditGame ||
11095                    gameMode == PlayFromGameFile ||
11096                    gameMode == AnalyzeMode ||
11097                    gameMode == AnalyzeFile) {
11098             nextGameMode = gameMode;
11099         } else {
11100             nextGameMode = EndOfGame;
11101         }
11102         pausing = FALSE;
11103         ModeHighlight();
11104     } else {
11105         nextGameMode = gameMode;
11106     }
11107
11108     if (appData.noChessProgram) {
11109         gameMode = nextGameMode;
11110         ModeHighlight();
11111         endingGame = 0; /* [HGM] crash */
11112         return;
11113     }
11114
11115     if (first.reuse) {
11116         /* Put first chess program into idle state */
11117         if (first.pr != NoProc &&
11118             (gameMode == MachinePlaysWhite ||
11119              gameMode == MachinePlaysBlack ||
11120              gameMode == TwoMachinesPlay ||
11121              gameMode == IcsPlayingWhite ||
11122              gameMode == IcsPlayingBlack ||
11123              gameMode == BeginningOfGame)) {
11124             SendToProgram("force\n", &first);
11125             if (first.usePing) {
11126               char buf[MSG_SIZ];
11127               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11128               SendToProgram(buf, &first);
11129             }
11130         }
11131     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11132         /* Kill off first chess program */
11133         if (first.isr != NULL)
11134           RemoveInputSource(first.isr);
11135         first.isr = NULL;
11136
11137         if (first.pr != NoProc) {
11138             ExitAnalyzeMode();
11139             DoSleep( appData.delayBeforeQuit );
11140             SendToProgram("quit\n", &first);
11141             DoSleep( appData.delayAfterQuit );
11142             DestroyChildProcess(first.pr, first.useSigterm);
11143             first.reload = TRUE;
11144         }
11145         first.pr = NoProc;
11146     }
11147     if (second.reuse) {
11148         /* Put second chess program into idle state */
11149         if (second.pr != NoProc &&
11150             gameMode == TwoMachinesPlay) {
11151             SendToProgram("force\n", &second);
11152             if (second.usePing) {
11153               char buf[MSG_SIZ];
11154               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11155               SendToProgram(buf, &second);
11156             }
11157         }
11158     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11159         /* Kill off second chess program */
11160         if (second.isr != NULL)
11161           RemoveInputSource(second.isr);
11162         second.isr = NULL;
11163
11164         if (second.pr != NoProc) {
11165             DoSleep( appData.delayBeforeQuit );
11166             SendToProgram("quit\n", &second);
11167             DoSleep( appData.delayAfterQuit );
11168             DestroyChildProcess(second.pr, second.useSigterm);
11169             second.reload = TRUE;
11170         }
11171         second.pr = NoProc;
11172     }
11173
11174     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11175         char resChar = '=';
11176         switch (result) {
11177         case WhiteWins:
11178           resChar = '+';
11179           if (first.twoMachinesColor[0] == 'w') {
11180             first.matchWins++;
11181           } else {
11182             second.matchWins++;
11183           }
11184           break;
11185         case BlackWins:
11186           resChar = '-';
11187           if (first.twoMachinesColor[0] == 'b') {
11188             first.matchWins++;
11189           } else {
11190             second.matchWins++;
11191           }
11192           break;
11193         case GameUnfinished:
11194           resChar = ' ';
11195         default:
11196           break;
11197         }
11198
11199         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11200         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11201             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11202             ReserveGame(nextGame, resChar); // sets nextGame
11203             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11204             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11205         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11206
11207         if (nextGame <= appData.matchGames && !abortMatch) {
11208             gameMode = nextGameMode;
11209             matchGame = nextGame; // this will be overruled in tourney mode!
11210             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11211             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11212             endingGame = 0; /* [HGM] crash */
11213             return;
11214         } else {
11215             gameMode = nextGameMode;
11216             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11217                      first.tidy, second.tidy,
11218                      first.matchWins, second.matchWins,
11219                      appData.matchGames - (first.matchWins + second.matchWins));
11220             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11221             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11222             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11223             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11224                 first.twoMachinesColor = "black\n";
11225                 second.twoMachinesColor = "white\n";
11226             } else {
11227                 first.twoMachinesColor = "white\n";
11228                 second.twoMachinesColor = "black\n";
11229             }
11230         }
11231     }
11232     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11233         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11234       ExitAnalyzeMode();
11235     gameMode = nextGameMode;
11236     ModeHighlight();
11237     endingGame = 0;  /* [HGM] crash */
11238     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11239         if(matchMode == TRUE) { // match through command line: exit with or without popup
11240             if(ranking) {
11241                 ToNrEvent(forwardMostMove);
11242                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11243                 else ExitEvent(0);
11244             } else DisplayFatalError(buf, 0, 0);
11245         } else { // match through menu; just stop, with or without popup
11246             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11247             ModeHighlight();
11248             if(ranking){
11249                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11250             } else DisplayNote(buf);
11251       }
11252       if(ranking) free(ranking);
11253     }
11254 }
11255
11256 /* Assumes program was just initialized (initString sent).
11257    Leaves program in force mode. */
11258 void
11259 FeedMovesToProgram (ChessProgramState *cps, int upto)
11260 {
11261     int i;
11262
11263     if (appData.debugMode)
11264       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11265               startedFromSetupPosition ? "position and " : "",
11266               backwardMostMove, upto, cps->which);
11267     if(currentlyInitializedVariant != gameInfo.variant) {
11268       char buf[MSG_SIZ];
11269         // [HGM] variantswitch: make engine aware of new variant
11270         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11271                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11272         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11273         SendToProgram(buf, cps);
11274         currentlyInitializedVariant = gameInfo.variant;
11275     }
11276     SendToProgram("force\n", cps);
11277     if (startedFromSetupPosition) {
11278         SendBoard(cps, backwardMostMove);
11279     if (appData.debugMode) {
11280         fprintf(debugFP, "feedMoves\n");
11281     }
11282     }
11283     for (i = backwardMostMove; i < upto; i++) {
11284         SendMoveToProgram(i, cps);
11285     }
11286 }
11287
11288
11289 int
11290 ResurrectChessProgram ()
11291 {
11292      /* The chess program may have exited.
11293         If so, restart it and feed it all the moves made so far. */
11294     static int doInit = 0;
11295
11296     if (appData.noChessProgram) return 1;
11297
11298     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11299         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11300         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11301         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11302     } else {
11303         if (first.pr != NoProc) return 1;
11304         StartChessProgram(&first);
11305     }
11306     InitChessProgram(&first, FALSE);
11307     FeedMovesToProgram(&first, currentMove);
11308
11309     if (!first.sendTime) {
11310         /* can't tell gnuchess what its clock should read,
11311            so we bow to its notion. */
11312         ResetClocks();
11313         timeRemaining[0][currentMove] = whiteTimeRemaining;
11314         timeRemaining[1][currentMove] = blackTimeRemaining;
11315     }
11316
11317     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11318                 appData.icsEngineAnalyze) && first.analysisSupport) {
11319       SendToProgram("analyze\n", &first);
11320       first.analyzing = TRUE;
11321     }
11322     return 1;
11323 }
11324
11325 /*
11326  * Button procedures
11327  */
11328 void
11329 Reset (int redraw, int init)
11330 {
11331     int i;
11332
11333     if (appData.debugMode) {
11334         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11335                 redraw, init, gameMode);
11336     }
11337     CleanupTail(); // [HGM] vari: delete any stored variations
11338     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11339     pausing = pauseExamInvalid = FALSE;
11340     startedFromSetupPosition = blackPlaysFirst = FALSE;
11341     firstMove = TRUE;
11342     whiteFlag = blackFlag = FALSE;
11343     userOfferedDraw = FALSE;
11344     hintRequested = bookRequested = FALSE;
11345     first.maybeThinking = FALSE;
11346     second.maybeThinking = FALSE;
11347     first.bookSuspend = FALSE; // [HGM] book
11348     second.bookSuspend = FALSE;
11349     thinkOutput[0] = NULLCHAR;
11350     lastHint[0] = NULLCHAR;
11351     ClearGameInfo(&gameInfo);
11352     gameInfo.variant = StringToVariant(appData.variant);
11353     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11354     ics_user_moved = ics_clock_paused = FALSE;
11355     ics_getting_history = H_FALSE;
11356     ics_gamenum = -1;
11357     white_holding[0] = black_holding[0] = NULLCHAR;
11358     ClearProgramStats();
11359     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11360
11361     ResetFrontEnd();
11362     ClearHighlights();
11363     flipView = appData.flipView;
11364     ClearPremoveHighlights();
11365     gotPremove = FALSE;
11366     alarmSounded = FALSE;
11367
11368     GameEnds(EndOfFile, NULL, GE_PLAYER);
11369     if(appData.serverMovesName != NULL) {
11370         /* [HGM] prepare to make moves file for broadcasting */
11371         clock_t t = clock();
11372         if(serverMoves != NULL) fclose(serverMoves);
11373         serverMoves = fopen(appData.serverMovesName, "r");
11374         if(serverMoves != NULL) {
11375             fclose(serverMoves);
11376             /* delay 15 sec before overwriting, so all clients can see end */
11377             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11378         }
11379         serverMoves = fopen(appData.serverMovesName, "w");
11380     }
11381
11382     ExitAnalyzeMode();
11383     gameMode = BeginningOfGame;
11384     ModeHighlight();
11385     if(appData.icsActive) gameInfo.variant = VariantNormal;
11386     currentMove = forwardMostMove = backwardMostMove = 0;
11387     MarkTargetSquares(1);
11388     InitPosition(redraw);
11389     for (i = 0; i < MAX_MOVES; i++) {
11390         if (commentList[i] != NULL) {
11391             free(commentList[i]);
11392             commentList[i] = NULL;
11393         }
11394     }
11395     ResetClocks();
11396     timeRemaining[0][0] = whiteTimeRemaining;
11397     timeRemaining[1][0] = blackTimeRemaining;
11398
11399     if (first.pr == NoProc) {
11400         StartChessProgram(&first);
11401     }
11402     if (init) {
11403             InitChessProgram(&first, startedFromSetupPosition);
11404     }
11405     DisplayTitle("");
11406     DisplayMessage("", "");
11407     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11408     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11409     ClearMap();        // [HGM] exclude: invalidate map
11410 }
11411
11412 void
11413 AutoPlayGameLoop ()
11414 {
11415     for (;;) {
11416         if (!AutoPlayOneMove())
11417           return;
11418         if (matchMode || appData.timeDelay == 0)
11419           continue;
11420         if (appData.timeDelay < 0)
11421           return;
11422         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11423         break;
11424     }
11425 }
11426
11427 void
11428 AnalyzeNextGame()
11429 {
11430     ReloadGame(1); // next game
11431 }
11432
11433 int
11434 AutoPlayOneMove ()
11435 {
11436     int fromX, fromY, toX, toY;
11437
11438     if (appData.debugMode) {
11439       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11440     }
11441
11442     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11443       return FALSE;
11444
11445     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11446       pvInfoList[currentMove].depth = programStats.depth;
11447       pvInfoList[currentMove].score = programStats.score;
11448       pvInfoList[currentMove].time  = 0;
11449       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11450       else { // append analysis of final position as comment
11451         char buf[MSG_SIZ];
11452         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11453         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11454       }
11455       programStats.depth = 0;
11456     }
11457
11458     if (currentMove >= forwardMostMove) {
11459       if(gameMode == AnalyzeFile) {
11460           if(appData.loadGameIndex == -1) {
11461             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11462           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11463           } else {
11464           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11465         }
11466       }
11467 //      gameMode = EndOfGame;
11468 //      ModeHighlight();
11469
11470       /* [AS] Clear current move marker at the end of a game */
11471       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11472
11473       return FALSE;
11474     }
11475
11476     toX = moveList[currentMove][2] - AAA;
11477     toY = moveList[currentMove][3] - ONE;
11478
11479     if (moveList[currentMove][1] == '@') {
11480         if (appData.highlightLastMove) {
11481             SetHighlights(-1, -1, toX, toY);
11482         }
11483     } else {
11484         fromX = moveList[currentMove][0] - AAA;
11485         fromY = moveList[currentMove][1] - ONE;
11486
11487         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11488
11489         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11490
11491         if (appData.highlightLastMove) {
11492             SetHighlights(fromX, fromY, toX, toY);
11493         }
11494     }
11495     DisplayMove(currentMove);
11496     SendMoveToProgram(currentMove++, &first);
11497     DisplayBothClocks();
11498     DrawPosition(FALSE, boards[currentMove]);
11499     // [HGM] PV info: always display, routine tests if empty
11500     DisplayComment(currentMove - 1, commentList[currentMove]);
11501     return TRUE;
11502 }
11503
11504
11505 int
11506 LoadGameOneMove (ChessMove readAhead)
11507 {
11508     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11509     char promoChar = NULLCHAR;
11510     ChessMove moveType;
11511     char move[MSG_SIZ];
11512     char *p, *q;
11513
11514     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11515         gameMode != AnalyzeMode && gameMode != Training) {
11516         gameFileFP = NULL;
11517         return FALSE;
11518     }
11519
11520     yyboardindex = forwardMostMove;
11521     if (readAhead != EndOfFile) {
11522       moveType = readAhead;
11523     } else {
11524       if (gameFileFP == NULL)
11525           return FALSE;
11526       moveType = (ChessMove) Myylex();
11527     }
11528
11529     done = FALSE;
11530     switch (moveType) {
11531       case Comment:
11532         if (appData.debugMode)
11533           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11534         p = yy_text;
11535
11536         /* append the comment but don't display it */
11537         AppendComment(currentMove, p, FALSE);
11538         return TRUE;
11539
11540       case WhiteCapturesEnPassant:
11541       case BlackCapturesEnPassant:
11542       case WhitePromotion:
11543       case BlackPromotion:
11544       case WhiteNonPromotion:
11545       case BlackNonPromotion:
11546       case NormalMove:
11547       case WhiteKingSideCastle:
11548       case WhiteQueenSideCastle:
11549       case BlackKingSideCastle:
11550       case BlackQueenSideCastle:
11551       case WhiteKingSideCastleWild:
11552       case WhiteQueenSideCastleWild:
11553       case BlackKingSideCastleWild:
11554       case BlackQueenSideCastleWild:
11555       /* PUSH Fabien */
11556       case WhiteHSideCastleFR:
11557       case WhiteASideCastleFR:
11558       case BlackHSideCastleFR:
11559       case BlackASideCastleFR:
11560       /* POP Fabien */
11561         if (appData.debugMode)
11562           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11563         fromX = currentMoveString[0] - AAA;
11564         fromY = currentMoveString[1] - ONE;
11565         toX = currentMoveString[2] - AAA;
11566         toY = currentMoveString[3] - ONE;
11567         promoChar = currentMoveString[4];
11568         break;
11569
11570       case WhiteDrop:
11571       case BlackDrop:
11572         if (appData.debugMode)
11573           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11574         fromX = moveType == WhiteDrop ?
11575           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11576         (int) CharToPiece(ToLower(currentMoveString[0]));
11577         fromY = DROP_RANK;
11578         toX = currentMoveString[2] - AAA;
11579         toY = currentMoveString[3] - ONE;
11580         break;
11581
11582       case WhiteWins:
11583       case BlackWins:
11584       case GameIsDrawn:
11585       case GameUnfinished:
11586         if (appData.debugMode)
11587           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11588         p = strchr(yy_text, '{');
11589         if (p == NULL) p = strchr(yy_text, '(');
11590         if (p == NULL) {
11591             p = yy_text;
11592             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11593         } else {
11594             q = strchr(p, *p == '{' ? '}' : ')');
11595             if (q != NULL) *q = NULLCHAR;
11596             p++;
11597         }
11598         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11599         GameEnds(moveType, p, GE_FILE);
11600         done = TRUE;
11601         if (cmailMsgLoaded) {
11602             ClearHighlights();
11603             flipView = WhiteOnMove(currentMove);
11604             if (moveType == GameUnfinished) flipView = !flipView;
11605             if (appData.debugMode)
11606               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11607         }
11608         break;
11609
11610       case EndOfFile:
11611         if (appData.debugMode)
11612           fprintf(debugFP, "Parser hit end of file\n");
11613         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11614           case MT_NONE:
11615           case MT_CHECK:
11616             break;
11617           case MT_CHECKMATE:
11618           case MT_STAINMATE:
11619             if (WhiteOnMove(currentMove)) {
11620                 GameEnds(BlackWins, "Black mates", GE_FILE);
11621             } else {
11622                 GameEnds(WhiteWins, "White mates", GE_FILE);
11623             }
11624             break;
11625           case MT_STALEMATE:
11626             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11627             break;
11628         }
11629         done = TRUE;
11630         break;
11631
11632       case MoveNumberOne:
11633         if (lastLoadGameStart == GNUChessGame) {
11634             /* GNUChessGames have numbers, but they aren't move numbers */
11635             if (appData.debugMode)
11636               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11637                       yy_text, (int) moveType);
11638             return LoadGameOneMove(EndOfFile); /* tail recursion */
11639         }
11640         /* else fall thru */
11641
11642       case XBoardGame:
11643       case GNUChessGame:
11644       case PGNTag:
11645         /* Reached start of next game in file */
11646         if (appData.debugMode)
11647           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11648         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11649           case MT_NONE:
11650           case MT_CHECK:
11651             break;
11652           case MT_CHECKMATE:
11653           case MT_STAINMATE:
11654             if (WhiteOnMove(currentMove)) {
11655                 GameEnds(BlackWins, "Black mates", GE_FILE);
11656             } else {
11657                 GameEnds(WhiteWins, "White mates", GE_FILE);
11658             }
11659             break;
11660           case MT_STALEMATE:
11661             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11662             break;
11663         }
11664         done = TRUE;
11665         break;
11666
11667       case PositionDiagram:     /* should not happen; ignore */
11668       case ElapsedTime:         /* ignore */
11669       case NAG:                 /* ignore */
11670         if (appData.debugMode)
11671           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11672                   yy_text, (int) moveType);
11673         return LoadGameOneMove(EndOfFile); /* tail recursion */
11674
11675       case IllegalMove:
11676         if (appData.testLegality) {
11677             if (appData.debugMode)
11678               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11679             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11680                     (forwardMostMove / 2) + 1,
11681                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11682             DisplayError(move, 0);
11683             done = TRUE;
11684         } else {
11685             if (appData.debugMode)
11686               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11687                       yy_text, currentMoveString);
11688             fromX = currentMoveString[0] - AAA;
11689             fromY = currentMoveString[1] - ONE;
11690             toX = currentMoveString[2] - AAA;
11691             toY = currentMoveString[3] - ONE;
11692             promoChar = currentMoveString[4];
11693         }
11694         break;
11695
11696       case AmbiguousMove:
11697         if (appData.debugMode)
11698           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11699         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11700                 (forwardMostMove / 2) + 1,
11701                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11702         DisplayError(move, 0);
11703         done = TRUE;
11704         break;
11705
11706       default:
11707       case ImpossibleMove:
11708         if (appData.debugMode)
11709           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11710         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11711                 (forwardMostMove / 2) + 1,
11712                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11713         DisplayError(move, 0);
11714         done = TRUE;
11715         break;
11716     }
11717
11718     if (done) {
11719         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11720             DrawPosition(FALSE, boards[currentMove]);
11721             DisplayBothClocks();
11722             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11723               DisplayComment(currentMove - 1, commentList[currentMove]);
11724         }
11725         (void) StopLoadGameTimer();
11726         gameFileFP = NULL;
11727         cmailOldMove = forwardMostMove;
11728         return FALSE;
11729     } else {
11730         /* currentMoveString is set as a side-effect of yylex */
11731
11732         thinkOutput[0] = NULLCHAR;
11733         MakeMove(fromX, fromY, toX, toY, promoChar);
11734         currentMove = forwardMostMove;
11735         return TRUE;
11736     }
11737 }
11738
11739 /* Load the nth game from the given file */
11740 int
11741 LoadGameFromFile (char *filename, int n, char *title, int useList)
11742 {
11743     FILE *f;
11744     char buf[MSG_SIZ];
11745
11746     if (strcmp(filename, "-") == 0) {
11747         f = stdin;
11748         title = "stdin";
11749     } else {
11750         f = fopen(filename, "rb");
11751         if (f == NULL) {
11752           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11753             DisplayError(buf, errno);
11754             return FALSE;
11755         }
11756     }
11757     if (fseek(f, 0, 0) == -1) {
11758         /* f is not seekable; probably a pipe */
11759         useList = FALSE;
11760     }
11761     if (useList && n == 0) {
11762         int error = GameListBuild(f);
11763         if (error) {
11764             DisplayError(_("Cannot build game list"), error);
11765         } else if (!ListEmpty(&gameList) &&
11766                    ((ListGame *) gameList.tailPred)->number > 1) {
11767             GameListPopUp(f, title);
11768             return TRUE;
11769         }
11770         GameListDestroy();
11771         n = 1;
11772     }
11773     if (n == 0) n = 1;
11774     return LoadGame(f, n, title, FALSE);
11775 }
11776
11777
11778 void
11779 MakeRegisteredMove ()
11780 {
11781     int fromX, fromY, toX, toY;
11782     char promoChar;
11783     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11784         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11785           case CMAIL_MOVE:
11786           case CMAIL_DRAW:
11787             if (appData.debugMode)
11788               fprintf(debugFP, "Restoring %s for game %d\n",
11789                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11790
11791             thinkOutput[0] = NULLCHAR;
11792             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11793             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11794             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11795             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11796             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11797             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11798             MakeMove(fromX, fromY, toX, toY, promoChar);
11799             ShowMove(fromX, fromY, toX, toY);
11800
11801             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11802               case MT_NONE:
11803               case MT_CHECK:
11804                 break;
11805
11806               case MT_CHECKMATE:
11807               case MT_STAINMATE:
11808                 if (WhiteOnMove(currentMove)) {
11809                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11810                 } else {
11811                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11812                 }
11813                 break;
11814
11815               case MT_STALEMATE:
11816                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11817                 break;
11818             }
11819
11820             break;
11821
11822           case CMAIL_RESIGN:
11823             if (WhiteOnMove(currentMove)) {
11824                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11825             } else {
11826                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11827             }
11828             break;
11829
11830           case CMAIL_ACCEPT:
11831             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11832             break;
11833
11834           default:
11835             break;
11836         }
11837     }
11838
11839     return;
11840 }
11841
11842 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11843 int
11844 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11845 {
11846     int retVal;
11847
11848     if (gameNumber > nCmailGames) {
11849         DisplayError(_("No more games in this message"), 0);
11850         return FALSE;
11851     }
11852     if (f == lastLoadGameFP) {
11853         int offset = gameNumber - lastLoadGameNumber;
11854         if (offset == 0) {
11855             cmailMsg[0] = NULLCHAR;
11856             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11857                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11858                 nCmailMovesRegistered--;
11859             }
11860             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11861             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11862                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11863             }
11864         } else {
11865             if (! RegisterMove()) return FALSE;
11866         }
11867     }
11868
11869     retVal = LoadGame(f, gameNumber, title, useList);
11870
11871     /* Make move registered during previous look at this game, if any */
11872     MakeRegisteredMove();
11873
11874     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11875         commentList[currentMove]
11876           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11877         DisplayComment(currentMove - 1, commentList[currentMove]);
11878     }
11879
11880     return retVal;
11881 }
11882
11883 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11884 int
11885 ReloadGame (int offset)
11886 {
11887     int gameNumber = lastLoadGameNumber + offset;
11888     if (lastLoadGameFP == NULL) {
11889         DisplayError(_("No game has been loaded yet"), 0);
11890         return FALSE;
11891     }
11892     if (gameNumber <= 0) {
11893         DisplayError(_("Can't back up any further"), 0);
11894         return FALSE;
11895     }
11896     if (cmailMsgLoaded) {
11897         return CmailLoadGame(lastLoadGameFP, gameNumber,
11898                              lastLoadGameTitle, lastLoadGameUseList);
11899     } else {
11900         return LoadGame(lastLoadGameFP, gameNumber,
11901                         lastLoadGameTitle, lastLoadGameUseList);
11902     }
11903 }
11904
11905 int keys[EmptySquare+1];
11906
11907 int
11908 PositionMatches (Board b1, Board b2)
11909 {
11910     int r, f, sum=0;
11911     switch(appData.searchMode) {
11912         case 1: return CompareWithRights(b1, b2);
11913         case 2:
11914             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11915                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11916             }
11917             return TRUE;
11918         case 3:
11919             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11920               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11921                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11922             }
11923             return sum==0;
11924         case 4:
11925             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11926                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11927             }
11928             return sum==0;
11929     }
11930     return TRUE;
11931 }
11932
11933 #define Q_PROMO  4
11934 #define Q_EP     3
11935 #define Q_BCASTL 2
11936 #define Q_WCASTL 1
11937
11938 int pieceList[256], quickBoard[256];
11939 ChessSquare pieceType[256] = { EmptySquare };
11940 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11941 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11942 int soughtTotal, turn;
11943 Boolean epOK, flipSearch;
11944
11945 typedef struct {
11946     unsigned char piece, to;
11947 } Move;
11948
11949 #define DSIZE (250000)
11950
11951 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11952 Move *moveDatabase = initialSpace;
11953 unsigned int movePtr, dataSize = DSIZE;
11954
11955 int
11956 MakePieceList (Board board, int *counts)
11957 {
11958     int r, f, n=Q_PROMO, total=0;
11959     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11960     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11961         int sq = f + (r<<4);
11962         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11963             quickBoard[sq] = ++n;
11964             pieceList[n] = sq;
11965             pieceType[n] = board[r][f];
11966             counts[board[r][f]]++;
11967             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11968             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11969             total++;
11970         }
11971     }
11972     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11973     return total;
11974 }
11975
11976 void
11977 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11978 {
11979     int sq = fromX + (fromY<<4);
11980     int piece = quickBoard[sq];
11981     quickBoard[sq] = 0;
11982     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11983     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11984         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11985         moveDatabase[movePtr++].piece = Q_WCASTL;
11986         quickBoard[sq] = piece;
11987         piece = quickBoard[from]; quickBoard[from] = 0;
11988         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11989     } else
11990     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11991         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11992         moveDatabase[movePtr++].piece = Q_BCASTL;
11993         quickBoard[sq] = piece;
11994         piece = quickBoard[from]; quickBoard[from] = 0;
11995         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11996     } else
11997     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11998         quickBoard[(fromY<<4)+toX] = 0;
11999         moveDatabase[movePtr].piece = Q_EP;
12000         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12001         moveDatabase[movePtr].to = sq;
12002     } else
12003     if(promoPiece != pieceType[piece]) {
12004         moveDatabase[movePtr++].piece = Q_PROMO;
12005         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12006     }
12007     moveDatabase[movePtr].piece = piece;
12008     quickBoard[sq] = piece;
12009     movePtr++;
12010 }
12011
12012 int
12013 PackGame (Board board)
12014 {
12015     Move *newSpace = NULL;
12016     moveDatabase[movePtr].piece = 0; // terminate previous game
12017     if(movePtr > dataSize) {
12018         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12019         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12020         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12021         if(newSpace) {
12022             int i;
12023             Move *p = moveDatabase, *q = newSpace;
12024             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12025             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12026             moveDatabase = newSpace;
12027         } else { // calloc failed, we must be out of memory. Too bad...
12028             dataSize = 0; // prevent calloc events for all subsequent games
12029             return 0;     // and signal this one isn't cached
12030         }
12031     }
12032     movePtr++;
12033     MakePieceList(board, counts);
12034     return movePtr;
12035 }
12036
12037 int
12038 QuickCompare (Board board, int *minCounts, int *maxCounts)
12039 {   // compare according to search mode
12040     int r, f;
12041     switch(appData.searchMode)
12042     {
12043       case 1: // exact position match
12044         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12045         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12046             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12047         }
12048         break;
12049       case 2: // can have extra material on empty squares
12050         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12051             if(board[r][f] == EmptySquare) continue;
12052             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12053         }
12054         break;
12055       case 3: // material with exact Pawn structure
12056         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12057             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12058             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12059         } // fall through to material comparison
12060       case 4: // exact material
12061         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12062         break;
12063       case 6: // material range with given imbalance
12064         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12065         // fall through to range comparison
12066       case 5: // material range
12067         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12068     }
12069     return TRUE;
12070 }
12071
12072 int
12073 QuickScan (Board board, Move *move)
12074 {   // reconstruct game,and compare all positions in it
12075     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12076     do {
12077         int piece = move->piece;
12078         int to = move->to, from = pieceList[piece];
12079         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12080           if(!piece) return -1;
12081           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12082             piece = (++move)->piece;
12083             from = pieceList[piece];
12084             counts[pieceType[piece]]--;
12085             pieceType[piece] = (ChessSquare) move->to;
12086             counts[move->to]++;
12087           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12088             counts[pieceType[quickBoard[to]]]--;
12089             quickBoard[to] = 0; total--;
12090             move++;
12091             continue;
12092           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12093             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12094             from  = pieceList[piece]; // so this must be King
12095             quickBoard[from] = 0;
12096             pieceList[piece] = to;
12097             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12098             quickBoard[from] = 0; // rook
12099             quickBoard[to] = piece;
12100             to = move->to; piece = move->piece;
12101             goto aftercastle;
12102           }
12103         }
12104         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12105         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12106         quickBoard[from] = 0;
12107       aftercastle:
12108         quickBoard[to] = piece;
12109         pieceList[piece] = to;
12110         cnt++; turn ^= 3;
12111         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12112            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12113            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12114                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12115           ) {
12116             static int lastCounts[EmptySquare+1];
12117             int i;
12118             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12119             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12120         } else stretch = 0;
12121         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12122         move++;
12123     } while(1);
12124 }
12125
12126 void
12127 InitSearch ()
12128 {
12129     int r, f;
12130     flipSearch = FALSE;
12131     CopyBoard(soughtBoard, boards[currentMove]);
12132     soughtTotal = MakePieceList(soughtBoard, maxSought);
12133     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12134     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12135     CopyBoard(reverseBoard, boards[currentMove]);
12136     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12137         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12138         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12139         reverseBoard[r][f] = piece;
12140     }
12141     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12142     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12143     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12144                  || (boards[currentMove][CASTLING][2] == NoRights ||
12145                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12146                  && (boards[currentMove][CASTLING][5] == NoRights ||
12147                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12148       ) {
12149         flipSearch = TRUE;
12150         CopyBoard(flipBoard, soughtBoard);
12151         CopyBoard(rotateBoard, reverseBoard);
12152         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12153             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12154             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12155         }
12156     }
12157     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12158     if(appData.searchMode >= 5) {
12159         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12160         MakePieceList(soughtBoard, minSought);
12161         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12162     }
12163     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12164         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12165 }
12166
12167 GameInfo dummyInfo;
12168 static int creatingBook;
12169
12170 int
12171 GameContainsPosition (FILE *f, ListGame *lg)
12172 {
12173     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12174     int fromX, fromY, toX, toY;
12175     char promoChar;
12176     static int initDone=FALSE;
12177
12178     // weed out games based on numerical tag comparison
12179     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12180     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12181     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12182     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12183     if(!initDone) {
12184         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12185         initDone = TRUE;
12186     }
12187     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12188     else CopyBoard(boards[scratch], initialPosition); // default start position
12189     if(lg->moves) {
12190         turn = btm + 1;
12191         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12192         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12193     }
12194     if(btm) plyNr++;
12195     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12196     fseek(f, lg->offset, 0);
12197     yynewfile(f);
12198     while(1) {
12199         yyboardindex = scratch;
12200         quickFlag = plyNr+1;
12201         next = Myylex();
12202         quickFlag = 0;
12203         switch(next) {
12204             case PGNTag:
12205                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12206             default:
12207                 continue;
12208
12209             case XBoardGame:
12210             case GNUChessGame:
12211                 if(plyNr) return -1; // after we have seen moves, this is for new game
12212               continue;
12213
12214             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12215             case ImpossibleMove:
12216             case WhiteWins: // game ends here with these four
12217             case BlackWins:
12218             case GameIsDrawn:
12219             case GameUnfinished:
12220                 return -1;
12221
12222             case IllegalMove:
12223                 if(appData.testLegality) return -1;
12224             case WhiteCapturesEnPassant:
12225             case BlackCapturesEnPassant:
12226             case WhitePromotion:
12227             case BlackPromotion:
12228             case WhiteNonPromotion:
12229             case BlackNonPromotion:
12230             case NormalMove:
12231             case WhiteKingSideCastle:
12232             case WhiteQueenSideCastle:
12233             case BlackKingSideCastle:
12234             case BlackQueenSideCastle:
12235             case WhiteKingSideCastleWild:
12236             case WhiteQueenSideCastleWild:
12237             case BlackKingSideCastleWild:
12238             case BlackQueenSideCastleWild:
12239             case WhiteHSideCastleFR:
12240             case WhiteASideCastleFR:
12241             case BlackHSideCastleFR:
12242             case BlackASideCastleFR:
12243                 fromX = currentMoveString[0] - AAA;
12244                 fromY = currentMoveString[1] - ONE;
12245                 toX = currentMoveString[2] - AAA;
12246                 toY = currentMoveString[3] - ONE;
12247                 promoChar = currentMoveString[4];
12248                 break;
12249             case WhiteDrop:
12250             case BlackDrop:
12251                 fromX = next == WhiteDrop ?
12252                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12253                   (int) CharToPiece(ToLower(currentMoveString[0]));
12254                 fromY = DROP_RANK;
12255                 toX = currentMoveString[2] - AAA;
12256                 toY = currentMoveString[3] - ONE;
12257                 promoChar = 0;
12258                 break;
12259         }
12260         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12261         plyNr++;
12262         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12263         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12264         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12265         if(appData.findMirror) {
12266             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12267             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12268         }
12269     }
12270 }
12271
12272 /* Load the nth game from open file f */
12273 int
12274 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12275 {
12276     ChessMove cm;
12277     char buf[MSG_SIZ];
12278     int gn = gameNumber;
12279     ListGame *lg = NULL;
12280     int numPGNTags = 0;
12281     int err, pos = -1;
12282     GameMode oldGameMode;
12283     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12284
12285     if (appData.debugMode)
12286         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12287
12288     if (gameMode == Training )
12289         SetTrainingModeOff();
12290
12291     oldGameMode = gameMode;
12292     if (gameMode != BeginningOfGame) {
12293       Reset(FALSE, TRUE);
12294     }
12295
12296     gameFileFP = f;
12297     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12298         fclose(lastLoadGameFP);
12299     }
12300
12301     if (useList) {
12302         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12303
12304         if (lg) {
12305             fseek(f, lg->offset, 0);
12306             GameListHighlight(gameNumber);
12307             pos = lg->position;
12308             gn = 1;
12309         }
12310         else {
12311             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12312               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12313             else
12314             DisplayError(_("Game number out of range"), 0);
12315             return FALSE;
12316         }
12317     } else {
12318         GameListDestroy();
12319         if (fseek(f, 0, 0) == -1) {
12320             if (f == lastLoadGameFP ?
12321                 gameNumber == lastLoadGameNumber + 1 :
12322                 gameNumber == 1) {
12323                 gn = 1;
12324             } else {
12325                 DisplayError(_("Can't seek on game file"), 0);
12326                 return FALSE;
12327             }
12328         }
12329     }
12330     lastLoadGameFP = f;
12331     lastLoadGameNumber = gameNumber;
12332     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12333     lastLoadGameUseList = useList;
12334
12335     yynewfile(f);
12336
12337     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12338       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12339                 lg->gameInfo.black);
12340             DisplayTitle(buf);
12341     } else if (*title != NULLCHAR) {
12342         if (gameNumber > 1) {
12343           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12344             DisplayTitle(buf);
12345         } else {
12346             DisplayTitle(title);
12347         }
12348     }
12349
12350     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12351         gameMode = PlayFromGameFile;
12352         ModeHighlight();
12353     }
12354
12355     currentMove = forwardMostMove = backwardMostMove = 0;
12356     CopyBoard(boards[0], initialPosition);
12357     StopClocks();
12358
12359     /*
12360      * Skip the first gn-1 games in the file.
12361      * Also skip over anything that precedes an identifiable
12362      * start of game marker, to avoid being confused by
12363      * garbage at the start of the file.  Currently
12364      * recognized start of game markers are the move number "1",
12365      * the pattern "gnuchess .* game", the pattern
12366      * "^[#;%] [^ ]* game file", and a PGN tag block.
12367      * A game that starts with one of the latter two patterns
12368      * will also have a move number 1, possibly
12369      * following a position diagram.
12370      * 5-4-02: Let's try being more lenient and allowing a game to
12371      * start with an unnumbered move.  Does that break anything?
12372      */
12373     cm = lastLoadGameStart = EndOfFile;
12374     while (gn > 0) {
12375         yyboardindex = forwardMostMove;
12376         cm = (ChessMove) Myylex();
12377         switch (cm) {
12378           case EndOfFile:
12379             if (cmailMsgLoaded) {
12380                 nCmailGames = CMAIL_MAX_GAMES - gn;
12381             } else {
12382                 Reset(TRUE, TRUE);
12383                 DisplayError(_("Game not found in file"), 0);
12384             }
12385             return FALSE;
12386
12387           case GNUChessGame:
12388           case XBoardGame:
12389             gn--;
12390             lastLoadGameStart = cm;
12391             break;
12392
12393           case MoveNumberOne:
12394             switch (lastLoadGameStart) {
12395               case GNUChessGame:
12396               case XBoardGame:
12397               case PGNTag:
12398                 break;
12399               case MoveNumberOne:
12400               case EndOfFile:
12401                 gn--;           /* count this game */
12402                 lastLoadGameStart = cm;
12403                 break;
12404               default:
12405                 /* impossible */
12406                 break;
12407             }
12408             break;
12409
12410           case PGNTag:
12411             switch (lastLoadGameStart) {
12412               case GNUChessGame:
12413               case PGNTag:
12414               case MoveNumberOne:
12415               case EndOfFile:
12416                 gn--;           /* count this game */
12417                 lastLoadGameStart = cm;
12418                 break;
12419               case XBoardGame:
12420                 lastLoadGameStart = cm; /* game counted already */
12421                 break;
12422               default:
12423                 /* impossible */
12424                 break;
12425             }
12426             if (gn > 0) {
12427                 do {
12428                     yyboardindex = forwardMostMove;
12429                     cm = (ChessMove) Myylex();
12430                 } while (cm == PGNTag || cm == Comment);
12431             }
12432             break;
12433
12434           case WhiteWins:
12435           case BlackWins:
12436           case GameIsDrawn:
12437             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12438                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12439                     != CMAIL_OLD_RESULT) {
12440                     nCmailResults ++ ;
12441                     cmailResult[  CMAIL_MAX_GAMES
12442                                 - gn - 1] = CMAIL_OLD_RESULT;
12443                 }
12444             }
12445             break;
12446
12447           case NormalMove:
12448             /* Only a NormalMove can be at the start of a game
12449              * without a position diagram. */
12450             if (lastLoadGameStart == EndOfFile ) {
12451               gn--;
12452               lastLoadGameStart = MoveNumberOne;
12453             }
12454             break;
12455
12456           default:
12457             break;
12458         }
12459     }
12460
12461     if (appData.debugMode)
12462       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12463
12464     if (cm == XBoardGame) {
12465         /* Skip any header junk before position diagram and/or move 1 */
12466         for (;;) {
12467             yyboardindex = forwardMostMove;
12468             cm = (ChessMove) Myylex();
12469
12470             if (cm == EndOfFile ||
12471                 cm == GNUChessGame || cm == XBoardGame) {
12472                 /* Empty game; pretend end-of-file and handle later */
12473                 cm = EndOfFile;
12474                 break;
12475             }
12476
12477             if (cm == MoveNumberOne || cm == PositionDiagram ||
12478                 cm == PGNTag || cm == Comment)
12479               break;
12480         }
12481     } else if (cm == GNUChessGame) {
12482         if (gameInfo.event != NULL) {
12483             free(gameInfo.event);
12484         }
12485         gameInfo.event = StrSave(yy_text);
12486     }
12487
12488     startedFromSetupPosition = FALSE;
12489     while (cm == PGNTag) {
12490         if (appData.debugMode)
12491           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12492         err = ParsePGNTag(yy_text, &gameInfo);
12493         if (!err) numPGNTags++;
12494
12495         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12496         if(gameInfo.variant != oldVariant) {
12497             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12498             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12499             InitPosition(TRUE);
12500             oldVariant = gameInfo.variant;
12501             if (appData.debugMode)
12502               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12503         }
12504
12505
12506         if (gameInfo.fen != NULL) {
12507           Board initial_position;
12508           startedFromSetupPosition = TRUE;
12509           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12510             Reset(TRUE, TRUE);
12511             DisplayError(_("Bad FEN position in file"), 0);
12512             return FALSE;
12513           }
12514           CopyBoard(boards[0], initial_position);
12515           if (blackPlaysFirst) {
12516             currentMove = forwardMostMove = backwardMostMove = 1;
12517             CopyBoard(boards[1], initial_position);
12518             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12519             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12520             timeRemaining[0][1] = whiteTimeRemaining;
12521             timeRemaining[1][1] = blackTimeRemaining;
12522             if (commentList[0] != NULL) {
12523               commentList[1] = commentList[0];
12524               commentList[0] = NULL;
12525             }
12526           } else {
12527             currentMove = forwardMostMove = backwardMostMove = 0;
12528           }
12529           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12530           {   int i;
12531               initialRulePlies = FENrulePlies;
12532               for( i=0; i< nrCastlingRights; i++ )
12533                   initialRights[i] = initial_position[CASTLING][i];
12534           }
12535           yyboardindex = forwardMostMove;
12536           free(gameInfo.fen);
12537           gameInfo.fen = NULL;
12538         }
12539
12540         yyboardindex = forwardMostMove;
12541         cm = (ChessMove) Myylex();
12542
12543         /* Handle comments interspersed among the tags */
12544         while (cm == Comment) {
12545             char *p;
12546             if (appData.debugMode)
12547               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12548             p = yy_text;
12549             AppendComment(currentMove, p, FALSE);
12550             yyboardindex = forwardMostMove;
12551             cm = (ChessMove) Myylex();
12552         }
12553     }
12554
12555     /* don't rely on existence of Event tag since if game was
12556      * pasted from clipboard the Event tag may not exist
12557      */
12558     if (numPGNTags > 0){
12559         char *tags;
12560         if (gameInfo.variant == VariantNormal) {
12561           VariantClass v = StringToVariant(gameInfo.event);
12562           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12563           if(v < VariantShogi) gameInfo.variant = v;
12564         }
12565         if (!matchMode) {
12566           if( appData.autoDisplayTags ) {
12567             tags = PGNTags(&gameInfo);
12568             TagsPopUp(tags, CmailMsg());
12569             free(tags);
12570           }
12571         }
12572     } else {
12573         /* Make something up, but don't display it now */
12574         SetGameInfo();
12575         TagsPopDown();
12576     }
12577
12578     if (cm == PositionDiagram) {
12579         int i, j;
12580         char *p;
12581         Board initial_position;
12582
12583         if (appData.debugMode)
12584           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12585
12586         if (!startedFromSetupPosition) {
12587             p = yy_text;
12588             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12589               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12590                 switch (*p) {
12591                   case '{':
12592                   case '[':
12593                   case '-':
12594                   case ' ':
12595                   case '\t':
12596                   case '\n':
12597                   case '\r':
12598                     break;
12599                   default:
12600                     initial_position[i][j++] = CharToPiece(*p);
12601                     break;
12602                 }
12603             while (*p == ' ' || *p == '\t' ||
12604                    *p == '\n' || *p == '\r') p++;
12605
12606             if (strncmp(p, "black", strlen("black"))==0)
12607               blackPlaysFirst = TRUE;
12608             else
12609               blackPlaysFirst = FALSE;
12610             startedFromSetupPosition = TRUE;
12611
12612             CopyBoard(boards[0], initial_position);
12613             if (blackPlaysFirst) {
12614                 currentMove = forwardMostMove = backwardMostMove = 1;
12615                 CopyBoard(boards[1], initial_position);
12616                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12617                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12618                 timeRemaining[0][1] = whiteTimeRemaining;
12619                 timeRemaining[1][1] = blackTimeRemaining;
12620                 if (commentList[0] != NULL) {
12621                     commentList[1] = commentList[0];
12622                     commentList[0] = NULL;
12623                 }
12624             } else {
12625                 currentMove = forwardMostMove = backwardMostMove = 0;
12626             }
12627         }
12628         yyboardindex = forwardMostMove;
12629         cm = (ChessMove) Myylex();
12630     }
12631
12632   if(!creatingBook) {
12633     if (first.pr == NoProc) {
12634         StartChessProgram(&first);
12635     }
12636     InitChessProgram(&first, FALSE);
12637     SendToProgram("force\n", &first);
12638     if (startedFromSetupPosition) {
12639         SendBoard(&first, forwardMostMove);
12640     if (appData.debugMode) {
12641         fprintf(debugFP, "Load Game\n");
12642     }
12643         DisplayBothClocks();
12644     }
12645   }
12646
12647     /* [HGM] server: flag to write setup moves in broadcast file as one */
12648     loadFlag = appData.suppressLoadMoves;
12649
12650     while (cm == Comment) {
12651         char *p;
12652         if (appData.debugMode)
12653           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12654         p = yy_text;
12655         AppendComment(currentMove, p, FALSE);
12656         yyboardindex = forwardMostMove;
12657         cm = (ChessMove) Myylex();
12658     }
12659
12660     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12661         cm == WhiteWins || cm == BlackWins ||
12662         cm == GameIsDrawn || cm == GameUnfinished) {
12663         DisplayMessage("", _("No moves in game"));
12664         if (cmailMsgLoaded) {
12665             if (appData.debugMode)
12666               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12667             ClearHighlights();
12668             flipView = FALSE;
12669         }
12670         DrawPosition(FALSE, boards[currentMove]);
12671         DisplayBothClocks();
12672         gameMode = EditGame;
12673         ModeHighlight();
12674         gameFileFP = NULL;
12675         cmailOldMove = 0;
12676         return TRUE;
12677     }
12678
12679     // [HGM] PV info: routine tests if comment empty
12680     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12681         DisplayComment(currentMove - 1, commentList[currentMove]);
12682     }
12683     if (!matchMode && appData.timeDelay != 0)
12684       DrawPosition(FALSE, boards[currentMove]);
12685
12686     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12687       programStats.ok_to_send = 1;
12688     }
12689
12690     /* if the first token after the PGN tags is a move
12691      * and not move number 1, retrieve it from the parser
12692      */
12693     if (cm != MoveNumberOne)
12694         LoadGameOneMove(cm);
12695
12696     /* load the remaining moves from the file */
12697     while (LoadGameOneMove(EndOfFile)) {
12698       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12699       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12700     }
12701
12702     /* rewind to the start of the game */
12703     currentMove = backwardMostMove;
12704
12705     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12706
12707     if (oldGameMode == AnalyzeFile) {
12708       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12709       AnalyzeFileEvent();
12710     } else
12711     if (oldGameMode == AnalyzeMode) {
12712       AnalyzeFileEvent();
12713     }
12714
12715     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12716         long int w, b; // [HGM] adjourn: restore saved clock times
12717         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12718         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12719             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12720             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12721         }
12722     }
12723
12724     if(creatingBook) return TRUE;
12725     if (!matchMode && pos > 0) {
12726         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12727     } else
12728     if (matchMode || appData.timeDelay == 0) {
12729       ToEndEvent();
12730     } else if (appData.timeDelay > 0) {
12731       AutoPlayGameLoop();
12732     }
12733
12734     if (appData.debugMode)
12735         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12736
12737     loadFlag = 0; /* [HGM] true game starts */
12738     return TRUE;
12739 }
12740
12741 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12742 int
12743 ReloadPosition (int offset)
12744 {
12745     int positionNumber = lastLoadPositionNumber + offset;
12746     if (lastLoadPositionFP == NULL) {
12747         DisplayError(_("No position has been loaded yet"), 0);
12748         return FALSE;
12749     }
12750     if (positionNumber <= 0) {
12751         DisplayError(_("Can't back up any further"), 0);
12752         return FALSE;
12753     }
12754     return LoadPosition(lastLoadPositionFP, positionNumber,
12755                         lastLoadPositionTitle);
12756 }
12757
12758 /* Load the nth position from the given file */
12759 int
12760 LoadPositionFromFile (char *filename, int n, char *title)
12761 {
12762     FILE *f;
12763     char buf[MSG_SIZ];
12764
12765     if (strcmp(filename, "-") == 0) {
12766         return LoadPosition(stdin, n, "stdin");
12767     } else {
12768         f = fopen(filename, "rb");
12769         if (f == NULL) {
12770             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12771             DisplayError(buf, errno);
12772             return FALSE;
12773         } else {
12774             return LoadPosition(f, n, title);
12775         }
12776     }
12777 }
12778
12779 /* Load the nth position from the given open file, and close it */
12780 int
12781 LoadPosition (FILE *f, int positionNumber, char *title)
12782 {
12783     char *p, line[MSG_SIZ];
12784     Board initial_position;
12785     int i, j, fenMode, pn;
12786
12787     if (gameMode == Training )
12788         SetTrainingModeOff();
12789
12790     if (gameMode != BeginningOfGame) {
12791         Reset(FALSE, TRUE);
12792     }
12793     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12794         fclose(lastLoadPositionFP);
12795     }
12796     if (positionNumber == 0) positionNumber = 1;
12797     lastLoadPositionFP = f;
12798     lastLoadPositionNumber = positionNumber;
12799     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12800     if (first.pr == NoProc && !appData.noChessProgram) {
12801       StartChessProgram(&first);
12802       InitChessProgram(&first, FALSE);
12803     }
12804     pn = positionNumber;
12805     if (positionNumber < 0) {
12806         /* Negative position number means to seek to that byte offset */
12807         if (fseek(f, -positionNumber, 0) == -1) {
12808             DisplayError(_("Can't seek on position file"), 0);
12809             return FALSE;
12810         };
12811         pn = 1;
12812     } else {
12813         if (fseek(f, 0, 0) == -1) {
12814             if (f == lastLoadPositionFP ?
12815                 positionNumber == lastLoadPositionNumber + 1 :
12816                 positionNumber == 1) {
12817                 pn = 1;
12818             } else {
12819                 DisplayError(_("Can't seek on position file"), 0);
12820                 return FALSE;
12821             }
12822         }
12823     }
12824     /* See if this file is FEN or old-style xboard */
12825     if (fgets(line, MSG_SIZ, f) == NULL) {
12826         DisplayError(_("Position not found in file"), 0);
12827         return FALSE;
12828     }
12829     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12830     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12831
12832     if (pn >= 2) {
12833         if (fenMode || line[0] == '#') pn--;
12834         while (pn > 0) {
12835             /* skip positions before number pn */
12836             if (fgets(line, MSG_SIZ, f) == NULL) {
12837                 Reset(TRUE, TRUE);
12838                 DisplayError(_("Position not found in file"), 0);
12839                 return FALSE;
12840             }
12841             if (fenMode || line[0] == '#') pn--;
12842         }
12843     }
12844
12845     if (fenMode) {
12846         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12847             DisplayError(_("Bad FEN position in file"), 0);
12848             return FALSE;
12849         }
12850     } else {
12851         (void) fgets(line, MSG_SIZ, f);
12852         (void) fgets(line, MSG_SIZ, f);
12853
12854         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12855             (void) fgets(line, MSG_SIZ, f);
12856             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12857                 if (*p == ' ')
12858                   continue;
12859                 initial_position[i][j++] = CharToPiece(*p);
12860             }
12861         }
12862
12863         blackPlaysFirst = FALSE;
12864         if (!feof(f)) {
12865             (void) fgets(line, MSG_SIZ, f);
12866             if (strncmp(line, "black", strlen("black"))==0)
12867               blackPlaysFirst = TRUE;
12868         }
12869     }
12870     startedFromSetupPosition = TRUE;
12871
12872     CopyBoard(boards[0], initial_position);
12873     if (blackPlaysFirst) {
12874         currentMove = forwardMostMove = backwardMostMove = 1;
12875         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12876         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12877         CopyBoard(boards[1], initial_position);
12878         DisplayMessage("", _("Black to play"));
12879     } else {
12880         currentMove = forwardMostMove = backwardMostMove = 0;
12881         DisplayMessage("", _("White to play"));
12882     }
12883     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12884     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12885         SendToProgram("force\n", &first);
12886         SendBoard(&first, forwardMostMove);
12887     }
12888     if (appData.debugMode) {
12889 int i, j;
12890   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12891   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12892         fprintf(debugFP, "Load Position\n");
12893     }
12894
12895     if (positionNumber > 1) {
12896       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12897         DisplayTitle(line);
12898     } else {
12899         DisplayTitle(title);
12900     }
12901     gameMode = EditGame;
12902     ModeHighlight();
12903     ResetClocks();
12904     timeRemaining[0][1] = whiteTimeRemaining;
12905     timeRemaining[1][1] = blackTimeRemaining;
12906     DrawPosition(FALSE, boards[currentMove]);
12907
12908     return TRUE;
12909 }
12910
12911
12912 void
12913 CopyPlayerNameIntoFileName (char **dest, char *src)
12914 {
12915     while (*src != NULLCHAR && *src != ',') {
12916         if (*src == ' ') {
12917             *(*dest)++ = '_';
12918             src++;
12919         } else {
12920             *(*dest)++ = *src++;
12921         }
12922     }
12923 }
12924
12925 char *
12926 DefaultFileName (char *ext)
12927 {
12928     static char def[MSG_SIZ];
12929     char *p;
12930
12931     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12932         p = def;
12933         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12934         *p++ = '-';
12935         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12936         *p++ = '.';
12937         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12938     } else {
12939         def[0] = NULLCHAR;
12940     }
12941     return def;
12942 }
12943
12944 /* Save the current game to the given file */
12945 int
12946 SaveGameToFile (char *filename, int append)
12947 {
12948     FILE *f;
12949     char buf[MSG_SIZ];
12950     int result, i, t,tot=0;
12951
12952     if (strcmp(filename, "-") == 0) {
12953         return SaveGame(stdout, 0, NULL);
12954     } else {
12955         for(i=0; i<10; i++) { // upto 10 tries
12956              f = fopen(filename, append ? "a" : "w");
12957              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12958              if(f || errno != 13) break;
12959              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12960              tot += t;
12961         }
12962         if (f == NULL) {
12963             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12964             DisplayError(buf, errno);
12965             return FALSE;
12966         } else {
12967             safeStrCpy(buf, lastMsg, MSG_SIZ);
12968             DisplayMessage(_("Waiting for access to save file"), "");
12969             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12970             DisplayMessage(_("Saving game"), "");
12971             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12972             result = SaveGame(f, 0, NULL);
12973             DisplayMessage(buf, "");
12974             return result;
12975         }
12976     }
12977 }
12978
12979 char *
12980 SavePart (char *str)
12981 {
12982     static char buf[MSG_SIZ];
12983     char *p;
12984
12985     p = strchr(str, ' ');
12986     if (p == NULL) return str;
12987     strncpy(buf, str, p - str);
12988     buf[p - str] = NULLCHAR;
12989     return buf;
12990 }
12991
12992 #define PGN_MAX_LINE 75
12993
12994 #define PGN_SIDE_WHITE  0
12995 #define PGN_SIDE_BLACK  1
12996
12997 static int
12998 FindFirstMoveOutOfBook (int side)
12999 {
13000     int result = -1;
13001
13002     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13003         int index = backwardMostMove;
13004         int has_book_hit = 0;
13005
13006         if( (index % 2) != side ) {
13007             index++;
13008         }
13009
13010         while( index < forwardMostMove ) {
13011             /* Check to see if engine is in book */
13012             int depth = pvInfoList[index].depth;
13013             int score = pvInfoList[index].score;
13014             int in_book = 0;
13015
13016             if( depth <= 2 ) {
13017                 in_book = 1;
13018             }
13019             else if( score == 0 && depth == 63 ) {
13020                 in_book = 1; /* Zappa */
13021             }
13022             else if( score == 2 && depth == 99 ) {
13023                 in_book = 1; /* Abrok */
13024             }
13025
13026             has_book_hit += in_book;
13027
13028             if( ! in_book ) {
13029                 result = index;
13030
13031                 break;
13032             }
13033
13034             index += 2;
13035         }
13036     }
13037
13038     return result;
13039 }
13040
13041 void
13042 GetOutOfBookInfo (char * buf)
13043 {
13044     int oob[2];
13045     int i;
13046     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13047
13048     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13049     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13050
13051     *buf = '\0';
13052
13053     if( oob[0] >= 0 || oob[1] >= 0 ) {
13054         for( i=0; i<2; i++ ) {
13055             int idx = oob[i];
13056
13057             if( idx >= 0 ) {
13058                 if( i > 0 && oob[0] >= 0 ) {
13059                     strcat( buf, "   " );
13060                 }
13061
13062                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13063                 sprintf( buf+strlen(buf), "%s%.2f",
13064                     pvInfoList[idx].score >= 0 ? "+" : "",
13065                     pvInfoList[idx].score / 100.0 );
13066             }
13067         }
13068     }
13069 }
13070
13071 /* Save game in PGN style and close the file */
13072 int
13073 SaveGamePGN (FILE *f)
13074 {
13075     int i, offset, linelen, newblock;
13076 //    char *movetext;
13077     char numtext[32];
13078     int movelen, numlen, blank;
13079     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13080
13081     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13082
13083     PrintPGNTags(f, &gameInfo);
13084
13085     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13086
13087     if (backwardMostMove > 0 || startedFromSetupPosition) {
13088         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13089         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13090         fprintf(f, "\n{--------------\n");
13091         PrintPosition(f, backwardMostMove);
13092         fprintf(f, "--------------}\n");
13093         free(fen);
13094     }
13095     else {
13096         /* [AS] Out of book annotation */
13097         if( appData.saveOutOfBookInfo ) {
13098             char buf[64];
13099
13100             GetOutOfBookInfo( buf );
13101
13102             if( buf[0] != '\0' ) {
13103                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13104             }
13105         }
13106
13107         fprintf(f, "\n");
13108     }
13109
13110     i = backwardMostMove;
13111     linelen = 0;
13112     newblock = TRUE;
13113
13114     while (i < forwardMostMove) {
13115         /* Print comments preceding this move */
13116         if (commentList[i] != NULL) {
13117             if (linelen > 0) fprintf(f, "\n");
13118             fprintf(f, "%s", commentList[i]);
13119             linelen = 0;
13120             newblock = TRUE;
13121         }
13122
13123         /* Format move number */
13124         if ((i % 2) == 0)
13125           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13126         else
13127           if (newblock)
13128             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13129           else
13130             numtext[0] = NULLCHAR;
13131
13132         numlen = strlen(numtext);
13133         newblock = FALSE;
13134
13135         /* Print move number */
13136         blank = linelen > 0 && numlen > 0;
13137         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13138             fprintf(f, "\n");
13139             linelen = 0;
13140             blank = 0;
13141         }
13142         if (blank) {
13143             fprintf(f, " ");
13144             linelen++;
13145         }
13146         fprintf(f, "%s", numtext);
13147         linelen += numlen;
13148
13149         /* Get move */
13150         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13151         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13152
13153         /* Print move */
13154         blank = linelen > 0 && movelen > 0;
13155         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13156             fprintf(f, "\n");
13157             linelen = 0;
13158             blank = 0;
13159         }
13160         if (blank) {
13161             fprintf(f, " ");
13162             linelen++;
13163         }
13164         fprintf(f, "%s", move_buffer);
13165         linelen += movelen;
13166
13167         /* [AS] Add PV info if present */
13168         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13169             /* [HGM] add time */
13170             char buf[MSG_SIZ]; int seconds;
13171
13172             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13173
13174             if( seconds <= 0)
13175               buf[0] = 0;
13176             else
13177               if( seconds < 30 )
13178                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13179               else
13180                 {
13181                   seconds = (seconds + 4)/10; // round to full seconds
13182                   if( seconds < 60 )
13183                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13184                   else
13185                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13186                 }
13187
13188             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13189                       pvInfoList[i].score >= 0 ? "+" : "",
13190                       pvInfoList[i].score / 100.0,
13191                       pvInfoList[i].depth,
13192                       buf );
13193
13194             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13195
13196             /* Print score/depth */
13197             blank = linelen > 0 && movelen > 0;
13198             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13199                 fprintf(f, "\n");
13200                 linelen = 0;
13201                 blank = 0;
13202             }
13203             if (blank) {
13204                 fprintf(f, " ");
13205                 linelen++;
13206             }
13207             fprintf(f, "%s", move_buffer);
13208             linelen += movelen;
13209         }
13210
13211         i++;
13212     }
13213
13214     /* Start a new line */
13215     if (linelen > 0) fprintf(f, "\n");
13216
13217     /* Print comments after last move */
13218     if (commentList[i] != NULL) {
13219         fprintf(f, "%s\n", commentList[i]);
13220     }
13221
13222     /* Print result */
13223     if (gameInfo.resultDetails != NULL &&
13224         gameInfo.resultDetails[0] != NULLCHAR) {
13225         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13226         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13227            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13228             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13229         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13230     } else {
13231         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13232     }
13233
13234     fclose(f);
13235     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13236     return TRUE;
13237 }
13238
13239 /* Save game in old style and close the file */
13240 int
13241 SaveGameOldStyle (FILE *f)
13242 {
13243     int i, offset;
13244     time_t tm;
13245
13246     tm = time((time_t *) NULL);
13247
13248     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13249     PrintOpponents(f);
13250
13251     if (backwardMostMove > 0 || startedFromSetupPosition) {
13252         fprintf(f, "\n[--------------\n");
13253         PrintPosition(f, backwardMostMove);
13254         fprintf(f, "--------------]\n");
13255     } else {
13256         fprintf(f, "\n");
13257     }
13258
13259     i = backwardMostMove;
13260     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13261
13262     while (i < forwardMostMove) {
13263         if (commentList[i] != NULL) {
13264             fprintf(f, "[%s]\n", commentList[i]);
13265         }
13266
13267         if ((i % 2) == 1) {
13268             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13269             i++;
13270         } else {
13271             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13272             i++;
13273             if (commentList[i] != NULL) {
13274                 fprintf(f, "\n");
13275                 continue;
13276             }
13277             if (i >= forwardMostMove) {
13278                 fprintf(f, "\n");
13279                 break;
13280             }
13281             fprintf(f, "%s\n", parseList[i]);
13282             i++;
13283         }
13284     }
13285
13286     if (commentList[i] != NULL) {
13287         fprintf(f, "[%s]\n", commentList[i]);
13288     }
13289
13290     /* This isn't really the old style, but it's close enough */
13291     if (gameInfo.resultDetails != NULL &&
13292         gameInfo.resultDetails[0] != NULLCHAR) {
13293         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13294                 gameInfo.resultDetails);
13295     } else {
13296         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13297     }
13298
13299     fclose(f);
13300     return TRUE;
13301 }
13302
13303 /* Save the current game to open file f and close the file */
13304 int
13305 SaveGame (FILE *f, int dummy, char *dummy2)
13306 {
13307     if (gameMode == EditPosition) EditPositionDone(TRUE);
13308     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13309     if (appData.oldSaveStyle)
13310       return SaveGameOldStyle(f);
13311     else
13312       return SaveGamePGN(f);
13313 }
13314
13315 /* Save the current position to the given file */
13316 int
13317 SavePositionToFile (char *filename)
13318 {
13319     FILE *f;
13320     char buf[MSG_SIZ];
13321
13322     if (strcmp(filename, "-") == 0) {
13323         return SavePosition(stdout, 0, NULL);
13324     } else {
13325         f = fopen(filename, "a");
13326         if (f == NULL) {
13327             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13328             DisplayError(buf, errno);
13329             return FALSE;
13330         } else {
13331             safeStrCpy(buf, lastMsg, MSG_SIZ);
13332             DisplayMessage(_("Waiting for access to save file"), "");
13333             flock(fileno(f), LOCK_EX); // [HGM] lock
13334             DisplayMessage(_("Saving position"), "");
13335             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13336             SavePosition(f, 0, NULL);
13337             DisplayMessage(buf, "");
13338             return TRUE;
13339         }
13340     }
13341 }
13342
13343 /* Save the current position to the given open file and close the file */
13344 int
13345 SavePosition (FILE *f, int dummy, char *dummy2)
13346 {
13347     time_t tm;
13348     char *fen;
13349
13350     if (gameMode == EditPosition) EditPositionDone(TRUE);
13351     if (appData.oldSaveStyle) {
13352         tm = time((time_t *) NULL);
13353
13354         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13355         PrintOpponents(f);
13356         fprintf(f, "[--------------\n");
13357         PrintPosition(f, currentMove);
13358         fprintf(f, "--------------]\n");
13359     } else {
13360         fen = PositionToFEN(currentMove, NULL, 1);
13361         fprintf(f, "%s\n", fen);
13362         free(fen);
13363     }
13364     fclose(f);
13365     return TRUE;
13366 }
13367
13368 void
13369 ReloadCmailMsgEvent (int unregister)
13370 {
13371 #if !WIN32
13372     static char *inFilename = NULL;
13373     static char *outFilename;
13374     int i;
13375     struct stat inbuf, outbuf;
13376     int status;
13377
13378     /* Any registered moves are unregistered if unregister is set, */
13379     /* i.e. invoked by the signal handler */
13380     if (unregister) {
13381         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13382             cmailMoveRegistered[i] = FALSE;
13383             if (cmailCommentList[i] != NULL) {
13384                 free(cmailCommentList[i]);
13385                 cmailCommentList[i] = NULL;
13386             }
13387         }
13388         nCmailMovesRegistered = 0;
13389     }
13390
13391     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13392         cmailResult[i] = CMAIL_NOT_RESULT;
13393     }
13394     nCmailResults = 0;
13395
13396     if (inFilename == NULL) {
13397         /* Because the filenames are static they only get malloced once  */
13398         /* and they never get freed                                      */
13399         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13400         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13401
13402         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13403         sprintf(outFilename, "%s.out", appData.cmailGameName);
13404     }
13405
13406     status = stat(outFilename, &outbuf);
13407     if (status < 0) {
13408         cmailMailedMove = FALSE;
13409     } else {
13410         status = stat(inFilename, &inbuf);
13411         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13412     }
13413
13414     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13415        counts the games, notes how each one terminated, etc.
13416
13417        It would be nice to remove this kludge and instead gather all
13418        the information while building the game list.  (And to keep it
13419        in the game list nodes instead of having a bunch of fixed-size
13420        parallel arrays.)  Note this will require getting each game's
13421        termination from the PGN tags, as the game list builder does
13422        not process the game moves.  --mann
13423        */
13424     cmailMsgLoaded = TRUE;
13425     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13426
13427     /* Load first game in the file or popup game menu */
13428     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13429
13430 #endif /* !WIN32 */
13431     return;
13432 }
13433
13434 int
13435 RegisterMove ()
13436 {
13437     FILE *f;
13438     char string[MSG_SIZ];
13439
13440     if (   cmailMailedMove
13441         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13442         return TRUE;            /* Allow free viewing  */
13443     }
13444
13445     /* Unregister move to ensure that we don't leave RegisterMove        */
13446     /* with the move registered when the conditions for registering no   */
13447     /* longer hold                                                       */
13448     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13449         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13450         nCmailMovesRegistered --;
13451
13452         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13453           {
13454               free(cmailCommentList[lastLoadGameNumber - 1]);
13455               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13456           }
13457     }
13458
13459     if (cmailOldMove == -1) {
13460         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13461         return FALSE;
13462     }
13463
13464     if (currentMove > cmailOldMove + 1) {
13465         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13466         return FALSE;
13467     }
13468
13469     if (currentMove < cmailOldMove) {
13470         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13471         return FALSE;
13472     }
13473
13474     if (forwardMostMove > currentMove) {
13475         /* Silently truncate extra moves */
13476         TruncateGame();
13477     }
13478
13479     if (   (currentMove == cmailOldMove + 1)
13480         || (   (currentMove == cmailOldMove)
13481             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13482                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13483         if (gameInfo.result != GameUnfinished) {
13484             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13485         }
13486
13487         if (commentList[currentMove] != NULL) {
13488             cmailCommentList[lastLoadGameNumber - 1]
13489               = StrSave(commentList[currentMove]);
13490         }
13491         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13492
13493         if (appData.debugMode)
13494           fprintf(debugFP, "Saving %s for game %d\n",
13495                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13496
13497         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13498
13499         f = fopen(string, "w");
13500         if (appData.oldSaveStyle) {
13501             SaveGameOldStyle(f); /* also closes the file */
13502
13503             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13504             f = fopen(string, "w");
13505             SavePosition(f, 0, NULL); /* also closes the file */
13506         } else {
13507             fprintf(f, "{--------------\n");
13508             PrintPosition(f, currentMove);
13509             fprintf(f, "--------------}\n\n");
13510
13511             SaveGame(f, 0, NULL); /* also closes the file*/
13512         }
13513
13514         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13515         nCmailMovesRegistered ++;
13516     } else if (nCmailGames == 1) {
13517         DisplayError(_("You have not made a move yet"), 0);
13518         return FALSE;
13519     }
13520
13521     return TRUE;
13522 }
13523
13524 void
13525 MailMoveEvent ()
13526 {
13527 #if !WIN32
13528     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13529     FILE *commandOutput;
13530     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13531     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13532     int nBuffers;
13533     int i;
13534     int archived;
13535     char *arcDir;
13536
13537     if (! cmailMsgLoaded) {
13538         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13539         return;
13540     }
13541
13542     if (nCmailGames == nCmailResults) {
13543         DisplayError(_("No unfinished games"), 0);
13544         return;
13545     }
13546
13547 #if CMAIL_PROHIBIT_REMAIL
13548     if (cmailMailedMove) {
13549       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);
13550         DisplayError(msg, 0);
13551         return;
13552     }
13553 #endif
13554
13555     if (! (cmailMailedMove || RegisterMove())) return;
13556
13557     if (   cmailMailedMove
13558         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13559       snprintf(string, MSG_SIZ, partCommandString,
13560                appData.debugMode ? " -v" : "", appData.cmailGameName);
13561         commandOutput = popen(string, "r");
13562
13563         if (commandOutput == NULL) {
13564             DisplayError(_("Failed to invoke cmail"), 0);
13565         } else {
13566             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13567                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13568             }
13569             if (nBuffers > 1) {
13570                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13571                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13572                 nBytes = MSG_SIZ - 1;
13573             } else {
13574                 (void) memcpy(msg, buffer, nBytes);
13575             }
13576             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13577
13578             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13579                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13580
13581                 archived = TRUE;
13582                 for (i = 0; i < nCmailGames; i ++) {
13583                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13584                         archived = FALSE;
13585                     }
13586                 }
13587                 if (   archived
13588                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13589                         != NULL)) {
13590                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13591                            arcDir,
13592                            appData.cmailGameName,
13593                            gameInfo.date);
13594                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13595                     cmailMsgLoaded = FALSE;
13596                 }
13597             }
13598
13599             DisplayInformation(msg);
13600             pclose(commandOutput);
13601         }
13602     } else {
13603         if ((*cmailMsg) != '\0') {
13604             DisplayInformation(cmailMsg);
13605         }
13606     }
13607
13608     return;
13609 #endif /* !WIN32 */
13610 }
13611
13612 char *
13613 CmailMsg ()
13614 {
13615 #if WIN32
13616     return NULL;
13617 #else
13618     int  prependComma = 0;
13619     char number[5];
13620     char string[MSG_SIZ];       /* Space for game-list */
13621     int  i;
13622
13623     if (!cmailMsgLoaded) return "";
13624
13625     if (cmailMailedMove) {
13626       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13627     } else {
13628         /* Create a list of games left */
13629       snprintf(string, MSG_SIZ, "[");
13630         for (i = 0; i < nCmailGames; i ++) {
13631             if (! (   cmailMoveRegistered[i]
13632                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13633                 if (prependComma) {
13634                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13635                 } else {
13636                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13637                     prependComma = 1;
13638                 }
13639
13640                 strcat(string, number);
13641             }
13642         }
13643         strcat(string, "]");
13644
13645         if (nCmailMovesRegistered + nCmailResults == 0) {
13646             switch (nCmailGames) {
13647               case 1:
13648                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13649                 break;
13650
13651               case 2:
13652                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13653                 break;
13654
13655               default:
13656                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13657                          nCmailGames);
13658                 break;
13659             }
13660         } else {
13661             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13662               case 1:
13663                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13664                          string);
13665                 break;
13666
13667               case 0:
13668                 if (nCmailResults == nCmailGames) {
13669                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13670                 } else {
13671                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13672                 }
13673                 break;
13674
13675               default:
13676                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13677                          string);
13678             }
13679         }
13680     }
13681     return cmailMsg;
13682 #endif /* WIN32 */
13683 }
13684
13685 void
13686 ResetGameEvent ()
13687 {
13688     if (gameMode == Training)
13689       SetTrainingModeOff();
13690
13691     Reset(TRUE, TRUE);
13692     cmailMsgLoaded = FALSE;
13693     if (appData.icsActive) {
13694       SendToICS(ics_prefix);
13695       SendToICS("refresh\n");
13696     }
13697 }
13698
13699 void
13700 ExitEvent (int status)
13701 {
13702     exiting++;
13703     if (exiting > 2) {
13704       /* Give up on clean exit */
13705       exit(status);
13706     }
13707     if (exiting > 1) {
13708       /* Keep trying for clean exit */
13709       return;
13710     }
13711
13712     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13713
13714     if (telnetISR != NULL) {
13715       RemoveInputSource(telnetISR);
13716     }
13717     if (icsPR != NoProc) {
13718       DestroyChildProcess(icsPR, TRUE);
13719     }
13720
13721     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13722     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13723
13724     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13725     /* make sure this other one finishes before killing it!                  */
13726     if(endingGame) { int count = 0;
13727         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13728         while(endingGame && count++ < 10) DoSleep(1);
13729         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13730     }
13731
13732     /* Kill off chess programs */
13733     if (first.pr != NoProc) {
13734         ExitAnalyzeMode();
13735
13736         DoSleep( appData.delayBeforeQuit );
13737         SendToProgram("quit\n", &first);
13738         DoSleep( appData.delayAfterQuit );
13739         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13740     }
13741     if (second.pr != NoProc) {
13742         DoSleep( appData.delayBeforeQuit );
13743         SendToProgram("quit\n", &second);
13744         DoSleep( appData.delayAfterQuit );
13745         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13746     }
13747     if (first.isr != NULL) {
13748         RemoveInputSource(first.isr);
13749     }
13750     if (second.isr != NULL) {
13751         RemoveInputSource(second.isr);
13752     }
13753
13754     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13755     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13756
13757     ShutDownFrontEnd();
13758     exit(status);
13759 }
13760
13761 void
13762 PauseEngine (ChessProgramState *cps)
13763 {
13764     SendToProgram("pause\n", cps);
13765     cps->pause = 2;
13766 }
13767
13768 void
13769 UnPauseEngine (ChessProgramState *cps)
13770 {
13771     SendToProgram("resume\n", cps);
13772     cps->pause = 1;
13773 }
13774
13775 void
13776 PauseEvent ()
13777 {
13778     if (appData.debugMode)
13779         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13780     if (pausing) {
13781         pausing = FALSE;
13782         ModeHighlight();
13783         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13784             StartClocks();
13785             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13786                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13787                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13788             }
13789             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13790             HandleMachineMove(stashedInputMove, stalledEngine);
13791             stalledEngine = NULL;
13792             return;
13793         }
13794         if (gameMode == MachinePlaysWhite ||
13795             gameMode == TwoMachinesPlay   ||
13796             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13797             if(first.pause)  UnPauseEngine(&first);
13798             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13799             if(second.pause) UnPauseEngine(&second);
13800             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13801             StartClocks();
13802         } else {
13803             DisplayBothClocks();
13804         }
13805         if (gameMode == PlayFromGameFile) {
13806             if (appData.timeDelay >= 0)
13807                 AutoPlayGameLoop();
13808         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13809             Reset(FALSE, TRUE);
13810             SendToICS(ics_prefix);
13811             SendToICS("refresh\n");
13812         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13813             ForwardInner(forwardMostMove);
13814         }
13815         pauseExamInvalid = FALSE;
13816     } else {
13817         switch (gameMode) {
13818           default:
13819             return;
13820           case IcsExamining:
13821             pauseExamForwardMostMove = forwardMostMove;
13822             pauseExamInvalid = FALSE;
13823             /* fall through */
13824           case IcsObserving:
13825           case IcsPlayingWhite:
13826           case IcsPlayingBlack:
13827             pausing = TRUE;
13828             ModeHighlight();
13829             return;
13830           case PlayFromGameFile:
13831             (void) StopLoadGameTimer();
13832             pausing = TRUE;
13833             ModeHighlight();
13834             break;
13835           case BeginningOfGame:
13836             if (appData.icsActive) return;
13837             /* else fall through */
13838           case MachinePlaysWhite:
13839           case MachinePlaysBlack:
13840           case TwoMachinesPlay:
13841             if (forwardMostMove == 0)
13842               return;           /* don't pause if no one has moved */
13843             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13844                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13845                 if(onMove->pause) {           // thinking engine can be paused
13846                     PauseEngine(onMove);      // do it
13847                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13848                         PauseEngine(onMove->other);
13849                     else
13850                         SendToProgram("easy\n", onMove->other);
13851                     StopClocks();
13852                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13853             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13854                 if(first.pause) {
13855                     PauseEngine(&first);
13856                     StopClocks();
13857                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13858             } else { // human on move, pause pondering by either method
13859                 if(first.pause)
13860                     PauseEngine(&first);
13861                 else if(appData.ponderNextMove)
13862                     SendToProgram("easy\n", &first);
13863                 StopClocks();
13864             }
13865             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13866           case AnalyzeMode:
13867             pausing = TRUE;
13868             ModeHighlight();
13869             break;
13870         }
13871     }
13872 }
13873
13874 void
13875 EditCommentEvent ()
13876 {
13877     char title[MSG_SIZ];
13878
13879     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13880       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13881     } else {
13882       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13883                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13884                parseList[currentMove - 1]);
13885     }
13886
13887     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13888 }
13889
13890
13891 void
13892 EditTagsEvent ()
13893 {
13894     char *tags = PGNTags(&gameInfo);
13895     bookUp = FALSE;
13896     EditTagsPopUp(tags, NULL);
13897     free(tags);
13898 }
13899
13900 void
13901 ToggleSecond ()
13902 {
13903   if(second.analyzing) {
13904     SendToProgram("exit\n", &second);
13905     second.analyzing = FALSE;
13906   } else {
13907     if (second.pr == NoProc) StartChessProgram(&second);
13908     InitChessProgram(&second, FALSE);
13909     FeedMovesToProgram(&second, currentMove);
13910
13911     SendToProgram("analyze\n", &second);
13912     second.analyzing = TRUE;
13913   }
13914 }
13915
13916 /* Toggle ShowThinking */
13917 void
13918 ToggleShowThinking()
13919 {
13920   appData.showThinking = !appData.showThinking;
13921   ShowThinkingEvent();
13922 }
13923
13924 int
13925 AnalyzeModeEvent ()
13926 {
13927     char buf[MSG_SIZ];
13928
13929     if (!first.analysisSupport) {
13930       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13931       DisplayError(buf, 0);
13932       return 0;
13933     }
13934     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13935     if (appData.icsActive) {
13936         if (gameMode != IcsObserving) {
13937           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13938             DisplayError(buf, 0);
13939             /* secure check */
13940             if (appData.icsEngineAnalyze) {
13941                 if (appData.debugMode)
13942                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13943                 ExitAnalyzeMode();
13944                 ModeHighlight();
13945             }
13946             return 0;
13947         }
13948         /* if enable, user wants to disable icsEngineAnalyze */
13949         if (appData.icsEngineAnalyze) {
13950                 ExitAnalyzeMode();
13951                 ModeHighlight();
13952                 return 0;
13953         }
13954         appData.icsEngineAnalyze = TRUE;
13955         if (appData.debugMode)
13956             fprintf(debugFP, "ICS engine analyze starting... \n");
13957     }
13958
13959     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13960     if (appData.noChessProgram || gameMode == AnalyzeMode)
13961       return 0;
13962
13963     if (gameMode != AnalyzeFile) {
13964         if (!appData.icsEngineAnalyze) {
13965                EditGameEvent();
13966                if (gameMode != EditGame) return 0;
13967         }
13968         if (!appData.showThinking) ToggleShowThinking();
13969         ResurrectChessProgram();
13970         SendToProgram("analyze\n", &first);
13971         first.analyzing = TRUE;
13972         /*first.maybeThinking = TRUE;*/
13973         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13974         EngineOutputPopUp();
13975     }
13976     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13977     pausing = FALSE;
13978     ModeHighlight();
13979     SetGameInfo();
13980
13981     StartAnalysisClock();
13982     GetTimeMark(&lastNodeCountTime);
13983     lastNodeCount = 0;
13984     return 1;
13985 }
13986
13987 void
13988 AnalyzeFileEvent ()
13989 {
13990     if (appData.noChessProgram || gameMode == AnalyzeFile)
13991       return;
13992
13993     if (!first.analysisSupport) {
13994       char buf[MSG_SIZ];
13995       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13996       DisplayError(buf, 0);
13997       return;
13998     }
13999
14000     if (gameMode != AnalyzeMode) {
14001         keepInfo = 1; // mere annotating should not alter PGN tags
14002         EditGameEvent();
14003         keepInfo = 0;
14004         if (gameMode != EditGame) return;
14005         if (!appData.showThinking) ToggleShowThinking();
14006         ResurrectChessProgram();
14007         SendToProgram("analyze\n", &first);
14008         first.analyzing = TRUE;
14009         /*first.maybeThinking = TRUE;*/
14010         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14011         EngineOutputPopUp();
14012     }
14013     gameMode = AnalyzeFile;
14014     pausing = FALSE;
14015     ModeHighlight();
14016
14017     StartAnalysisClock();
14018     GetTimeMark(&lastNodeCountTime);
14019     lastNodeCount = 0;
14020     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14021     AnalysisPeriodicEvent(1);
14022 }
14023
14024 void
14025 MachineWhiteEvent ()
14026 {
14027     char buf[MSG_SIZ];
14028     char *bookHit = NULL;
14029
14030     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14031       return;
14032
14033
14034     if (gameMode == PlayFromGameFile ||
14035         gameMode == TwoMachinesPlay  ||
14036         gameMode == Training         ||
14037         gameMode == AnalyzeMode      ||
14038         gameMode == EndOfGame)
14039         EditGameEvent();
14040
14041     if (gameMode == EditPosition)
14042         EditPositionDone(TRUE);
14043
14044     if (!WhiteOnMove(currentMove)) {
14045         DisplayError(_("It is not White's turn"), 0);
14046         return;
14047     }
14048
14049     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14050       ExitAnalyzeMode();
14051
14052     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14053         gameMode == AnalyzeFile)
14054         TruncateGame();
14055
14056     ResurrectChessProgram();    /* in case it isn't running */
14057     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14058         gameMode = MachinePlaysWhite;
14059         ResetClocks();
14060     } else
14061     gameMode = MachinePlaysWhite;
14062     pausing = FALSE;
14063     ModeHighlight();
14064     SetGameInfo();
14065     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14066     DisplayTitle(buf);
14067     if (first.sendName) {
14068       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14069       SendToProgram(buf, &first);
14070     }
14071     if (first.sendTime) {
14072       if (first.useColors) {
14073         SendToProgram("black\n", &first); /*gnu kludge*/
14074       }
14075       SendTimeRemaining(&first, TRUE);
14076     }
14077     if (first.useColors) {
14078       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14079     }
14080     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14081     SetMachineThinkingEnables();
14082     first.maybeThinking = TRUE;
14083     StartClocks();
14084     firstMove = FALSE;
14085
14086     if (appData.autoFlipView && !flipView) {
14087       flipView = !flipView;
14088       DrawPosition(FALSE, NULL);
14089       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14090     }
14091
14092     if(bookHit) { // [HGM] book: simulate book reply
14093         static char bookMove[MSG_SIZ]; // a bit generous?
14094
14095         programStats.nodes = programStats.depth = programStats.time =
14096         programStats.score = programStats.got_only_move = 0;
14097         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14098
14099         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14100         strcat(bookMove, bookHit);
14101         HandleMachineMove(bookMove, &first);
14102     }
14103 }
14104
14105 void
14106 MachineBlackEvent ()
14107 {
14108   char buf[MSG_SIZ];
14109   char *bookHit = NULL;
14110
14111     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14112         return;
14113
14114
14115     if (gameMode == PlayFromGameFile ||
14116         gameMode == TwoMachinesPlay  ||
14117         gameMode == Training         ||
14118         gameMode == AnalyzeMode      ||
14119         gameMode == EndOfGame)
14120         EditGameEvent();
14121
14122     if (gameMode == EditPosition)
14123         EditPositionDone(TRUE);
14124
14125     if (WhiteOnMove(currentMove)) {
14126         DisplayError(_("It is not Black's turn"), 0);
14127         return;
14128     }
14129
14130     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14131       ExitAnalyzeMode();
14132
14133     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14134         gameMode == AnalyzeFile)
14135         TruncateGame();
14136
14137     ResurrectChessProgram();    /* in case it isn't running */
14138     gameMode = MachinePlaysBlack;
14139     pausing = FALSE;
14140     ModeHighlight();
14141     SetGameInfo();
14142     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14143     DisplayTitle(buf);
14144     if (first.sendName) {
14145       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14146       SendToProgram(buf, &first);
14147     }
14148     if (first.sendTime) {
14149       if (first.useColors) {
14150         SendToProgram("white\n", &first); /*gnu kludge*/
14151       }
14152       SendTimeRemaining(&first, FALSE);
14153     }
14154     if (first.useColors) {
14155       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14156     }
14157     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14158     SetMachineThinkingEnables();
14159     first.maybeThinking = TRUE;
14160     StartClocks();
14161
14162     if (appData.autoFlipView && flipView) {
14163       flipView = !flipView;
14164       DrawPosition(FALSE, NULL);
14165       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14166     }
14167     if(bookHit) { // [HGM] book: simulate book reply
14168         static char bookMove[MSG_SIZ]; // a bit generous?
14169
14170         programStats.nodes = programStats.depth = programStats.time =
14171         programStats.score = programStats.got_only_move = 0;
14172         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14173
14174         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14175         strcat(bookMove, bookHit);
14176         HandleMachineMove(bookMove, &first);
14177     }
14178 }
14179
14180
14181 void
14182 DisplayTwoMachinesTitle ()
14183 {
14184     char buf[MSG_SIZ];
14185     if (appData.matchGames > 0) {
14186         if(appData.tourneyFile[0]) {
14187           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14188                    gameInfo.white, _("vs."), gameInfo.black,
14189                    nextGame+1, appData.matchGames+1,
14190                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14191         } else
14192         if (first.twoMachinesColor[0] == 'w') {
14193           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14194                    gameInfo.white, _("vs."),  gameInfo.black,
14195                    first.matchWins, second.matchWins,
14196                    matchGame - 1 - (first.matchWins + second.matchWins));
14197         } else {
14198           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14199                    gameInfo.white, _("vs."), gameInfo.black,
14200                    second.matchWins, first.matchWins,
14201                    matchGame - 1 - (first.matchWins + second.matchWins));
14202         }
14203     } else {
14204       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14205     }
14206     DisplayTitle(buf);
14207 }
14208
14209 void
14210 SettingsMenuIfReady ()
14211 {
14212   if (second.lastPing != second.lastPong) {
14213     DisplayMessage("", _("Waiting for second chess program"));
14214     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14215     return;
14216   }
14217   ThawUI();
14218   DisplayMessage("", "");
14219   SettingsPopUp(&second);
14220 }
14221
14222 int
14223 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14224 {
14225     char buf[MSG_SIZ];
14226     if (cps->pr == NoProc) {
14227         StartChessProgram(cps);
14228         if (cps->protocolVersion == 1) {
14229           retry();
14230           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14231         } else {
14232           /* kludge: allow timeout for initial "feature" command */
14233           if(retry != TwoMachinesEventIfReady) FreezeUI();
14234           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14235           DisplayMessage("", buf);
14236           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14237         }
14238         return 1;
14239     }
14240     return 0;
14241 }
14242
14243 void
14244 TwoMachinesEvent P((void))
14245 {
14246     int i;
14247     char buf[MSG_SIZ];
14248     ChessProgramState *onmove;
14249     char *bookHit = NULL;
14250     static int stalling = 0;
14251     TimeMark now;
14252     long wait;
14253
14254     if (appData.noChessProgram) return;
14255
14256     switch (gameMode) {
14257       case TwoMachinesPlay:
14258         return;
14259       case MachinePlaysWhite:
14260       case MachinePlaysBlack:
14261         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14262             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14263             return;
14264         }
14265         /* fall through */
14266       case BeginningOfGame:
14267       case PlayFromGameFile:
14268       case EndOfGame:
14269         EditGameEvent();
14270         if (gameMode != EditGame) return;
14271         break;
14272       case EditPosition:
14273         EditPositionDone(TRUE);
14274         break;
14275       case AnalyzeMode:
14276       case AnalyzeFile:
14277         ExitAnalyzeMode();
14278         break;
14279       case EditGame:
14280       default:
14281         break;
14282     }
14283
14284 //    forwardMostMove = currentMove;
14285     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14286     startingEngine = TRUE;
14287
14288     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14289
14290     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14291     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14292       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14293       return;
14294     }
14295     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14296
14297     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14298         startingEngine = FALSE;
14299         DisplayError("second engine does not play this", 0);
14300         return;
14301     }
14302
14303     if(!stalling) {
14304       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14305       SendToProgram("force\n", &second);
14306       stalling = 1;
14307       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14308       return;
14309     }
14310     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14311     if(appData.matchPause>10000 || appData.matchPause<10)
14312                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14313     wait = SubtractTimeMarks(&now, &pauseStart);
14314     if(wait < appData.matchPause) {
14315         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14316         return;
14317     }
14318     // we are now committed to starting the game
14319     stalling = 0;
14320     DisplayMessage("", "");
14321     if (startedFromSetupPosition) {
14322         SendBoard(&second, backwardMostMove);
14323     if (appData.debugMode) {
14324         fprintf(debugFP, "Two Machines\n");
14325     }
14326     }
14327     for (i = backwardMostMove; i < forwardMostMove; i++) {
14328         SendMoveToProgram(i, &second);
14329     }
14330
14331     gameMode = TwoMachinesPlay;
14332     pausing = startingEngine = FALSE;
14333     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14334     SetGameInfo();
14335     DisplayTwoMachinesTitle();
14336     firstMove = TRUE;
14337     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14338         onmove = &first;
14339     } else {
14340         onmove = &second;
14341     }
14342     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14343     SendToProgram(first.computerString, &first);
14344     if (first.sendName) {
14345       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14346       SendToProgram(buf, &first);
14347     }
14348     SendToProgram(second.computerString, &second);
14349     if (second.sendName) {
14350       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14351       SendToProgram(buf, &second);
14352     }
14353
14354     ResetClocks();
14355     if (!first.sendTime || !second.sendTime) {
14356         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14357         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14358     }
14359     if (onmove->sendTime) {
14360       if (onmove->useColors) {
14361         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14362       }
14363       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14364     }
14365     if (onmove->useColors) {
14366       SendToProgram(onmove->twoMachinesColor, onmove);
14367     }
14368     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14369 //    SendToProgram("go\n", onmove);
14370     onmove->maybeThinking = TRUE;
14371     SetMachineThinkingEnables();
14372
14373     StartClocks();
14374
14375     if(bookHit) { // [HGM] book: simulate book reply
14376         static char bookMove[MSG_SIZ]; // a bit generous?
14377
14378         programStats.nodes = programStats.depth = programStats.time =
14379         programStats.score = programStats.got_only_move = 0;
14380         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14381
14382         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14383         strcat(bookMove, bookHit);
14384         savedMessage = bookMove; // args for deferred call
14385         savedState = onmove;
14386         ScheduleDelayedEvent(DeferredBookMove, 1);
14387     }
14388 }
14389
14390 void
14391 TrainingEvent ()
14392 {
14393     if (gameMode == Training) {
14394       SetTrainingModeOff();
14395       gameMode = PlayFromGameFile;
14396       DisplayMessage("", _("Training mode off"));
14397     } else {
14398       gameMode = Training;
14399       animateTraining = appData.animate;
14400
14401       /* make sure we are not already at the end of the game */
14402       if (currentMove < forwardMostMove) {
14403         SetTrainingModeOn();
14404         DisplayMessage("", _("Training mode on"));
14405       } else {
14406         gameMode = PlayFromGameFile;
14407         DisplayError(_("Already at end of game"), 0);
14408       }
14409     }
14410     ModeHighlight();
14411 }
14412
14413 void
14414 IcsClientEvent ()
14415 {
14416     if (!appData.icsActive) return;
14417     switch (gameMode) {
14418       case IcsPlayingWhite:
14419       case IcsPlayingBlack:
14420       case IcsObserving:
14421       case IcsIdle:
14422       case BeginningOfGame:
14423       case IcsExamining:
14424         return;
14425
14426       case EditGame:
14427         break;
14428
14429       case EditPosition:
14430         EditPositionDone(TRUE);
14431         break;
14432
14433       case AnalyzeMode:
14434       case AnalyzeFile:
14435         ExitAnalyzeMode();
14436         break;
14437
14438       default:
14439         EditGameEvent();
14440         break;
14441     }
14442
14443     gameMode = IcsIdle;
14444     ModeHighlight();
14445     return;
14446 }
14447
14448 void
14449 EditGameEvent ()
14450 {
14451     int i;
14452
14453     switch (gameMode) {
14454       case Training:
14455         SetTrainingModeOff();
14456         break;
14457       case MachinePlaysWhite:
14458       case MachinePlaysBlack:
14459       case BeginningOfGame:
14460         SendToProgram("force\n", &first);
14461         SetUserThinkingEnables();
14462         break;
14463       case PlayFromGameFile:
14464         (void) StopLoadGameTimer();
14465         if (gameFileFP != NULL) {
14466             gameFileFP = NULL;
14467         }
14468         break;
14469       case EditPosition:
14470         EditPositionDone(TRUE);
14471         break;
14472       case AnalyzeMode:
14473       case AnalyzeFile:
14474         ExitAnalyzeMode();
14475         SendToProgram("force\n", &first);
14476         break;
14477       case TwoMachinesPlay:
14478         GameEnds(EndOfFile, NULL, GE_PLAYER);
14479         ResurrectChessProgram();
14480         SetUserThinkingEnables();
14481         break;
14482       case EndOfGame:
14483         ResurrectChessProgram();
14484         break;
14485       case IcsPlayingBlack:
14486       case IcsPlayingWhite:
14487         DisplayError(_("Warning: You are still playing a game"), 0);
14488         break;
14489       case IcsObserving:
14490         DisplayError(_("Warning: You are still observing a game"), 0);
14491         break;
14492       case IcsExamining:
14493         DisplayError(_("Warning: You are still examining a game"), 0);
14494         break;
14495       case IcsIdle:
14496         break;
14497       case EditGame:
14498       default:
14499         return;
14500     }
14501
14502     pausing = FALSE;
14503     StopClocks();
14504     first.offeredDraw = second.offeredDraw = 0;
14505
14506     if (gameMode == PlayFromGameFile) {
14507         whiteTimeRemaining = timeRemaining[0][currentMove];
14508         blackTimeRemaining = timeRemaining[1][currentMove];
14509         DisplayTitle("");
14510     }
14511
14512     if (gameMode == MachinePlaysWhite ||
14513         gameMode == MachinePlaysBlack ||
14514         gameMode == TwoMachinesPlay ||
14515         gameMode == EndOfGame) {
14516         i = forwardMostMove;
14517         while (i > currentMove) {
14518             SendToProgram("undo\n", &first);
14519             i--;
14520         }
14521         if(!adjustedClock) {
14522         whiteTimeRemaining = timeRemaining[0][currentMove];
14523         blackTimeRemaining = timeRemaining[1][currentMove];
14524         DisplayBothClocks();
14525         }
14526         if (whiteFlag || blackFlag) {
14527             whiteFlag = blackFlag = 0;
14528         }
14529         DisplayTitle("");
14530     }
14531
14532     gameMode = EditGame;
14533     ModeHighlight();
14534     SetGameInfo();
14535 }
14536
14537
14538 void
14539 EditPositionEvent ()
14540 {
14541     if (gameMode == EditPosition) {
14542         EditGameEvent();
14543         return;
14544     }
14545
14546     EditGameEvent();
14547     if (gameMode != EditGame) return;
14548
14549     gameMode = EditPosition;
14550     ModeHighlight();
14551     SetGameInfo();
14552     if (currentMove > 0)
14553       CopyBoard(boards[0], boards[currentMove]);
14554
14555     blackPlaysFirst = !WhiteOnMove(currentMove);
14556     ResetClocks();
14557     currentMove = forwardMostMove = backwardMostMove = 0;
14558     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14559     DisplayMove(-1);
14560     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14561 }
14562
14563 void
14564 ExitAnalyzeMode ()
14565 {
14566     /* [DM] icsEngineAnalyze - possible call from other functions */
14567     if (appData.icsEngineAnalyze) {
14568         appData.icsEngineAnalyze = FALSE;
14569
14570         DisplayMessage("",_("Close ICS engine analyze..."));
14571     }
14572     if (first.analysisSupport && first.analyzing) {
14573       SendToBoth("exit\n");
14574       first.analyzing = second.analyzing = FALSE;
14575     }
14576     thinkOutput[0] = NULLCHAR;
14577 }
14578
14579 void
14580 EditPositionDone (Boolean fakeRights)
14581 {
14582     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14583
14584     startedFromSetupPosition = TRUE;
14585     InitChessProgram(&first, FALSE);
14586     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14587       boards[0][EP_STATUS] = EP_NONE;
14588       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14589       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14590         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14591         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14592       } else boards[0][CASTLING][2] = NoRights;
14593       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14594         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14595         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14596       } else boards[0][CASTLING][5] = NoRights;
14597       if(gameInfo.variant == VariantSChess) {
14598         int i;
14599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14600           boards[0][VIRGIN][i] = 0;
14601           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14602           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14603         }
14604       }
14605     }
14606     SendToProgram("force\n", &first);
14607     if (blackPlaysFirst) {
14608         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14609         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14610         currentMove = forwardMostMove = backwardMostMove = 1;
14611         CopyBoard(boards[1], boards[0]);
14612     } else {
14613         currentMove = forwardMostMove = backwardMostMove = 0;
14614     }
14615     SendBoard(&first, forwardMostMove);
14616     if (appData.debugMode) {
14617         fprintf(debugFP, "EditPosDone\n");
14618     }
14619     DisplayTitle("");
14620     DisplayMessage("", "");
14621     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14622     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14623     gameMode = EditGame;
14624     ModeHighlight();
14625     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14626     ClearHighlights(); /* [AS] */
14627 }
14628
14629 /* Pause for `ms' milliseconds */
14630 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14631 void
14632 TimeDelay (long ms)
14633 {
14634     TimeMark m1, m2;
14635
14636     GetTimeMark(&m1);
14637     do {
14638         GetTimeMark(&m2);
14639     } while (SubtractTimeMarks(&m2, &m1) < ms);
14640 }
14641
14642 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14643 void
14644 SendMultiLineToICS (char *buf)
14645 {
14646     char temp[MSG_SIZ+1], *p;
14647     int len;
14648
14649     len = strlen(buf);
14650     if (len > MSG_SIZ)
14651       len = MSG_SIZ;
14652
14653     strncpy(temp, buf, len);
14654     temp[len] = 0;
14655
14656     p = temp;
14657     while (*p) {
14658         if (*p == '\n' || *p == '\r')
14659           *p = ' ';
14660         ++p;
14661     }
14662
14663     strcat(temp, "\n");
14664     SendToICS(temp);
14665     SendToPlayer(temp, strlen(temp));
14666 }
14667
14668 void
14669 SetWhiteToPlayEvent ()
14670 {
14671     if (gameMode == EditPosition) {
14672         blackPlaysFirst = FALSE;
14673         DisplayBothClocks();    /* works because currentMove is 0 */
14674     } else if (gameMode == IcsExamining) {
14675         SendToICS(ics_prefix);
14676         SendToICS("tomove white\n");
14677     }
14678 }
14679
14680 void
14681 SetBlackToPlayEvent ()
14682 {
14683     if (gameMode == EditPosition) {
14684         blackPlaysFirst = TRUE;
14685         currentMove = 1;        /* kludge */
14686         DisplayBothClocks();
14687         currentMove = 0;
14688     } else if (gameMode == IcsExamining) {
14689         SendToICS(ics_prefix);
14690         SendToICS("tomove black\n");
14691     }
14692 }
14693
14694 void
14695 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14696 {
14697     char buf[MSG_SIZ];
14698     ChessSquare piece = boards[0][y][x];
14699     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14700     static int lastVariant;
14701
14702     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14703
14704     switch (selection) {
14705       case ClearBoard:
14706         CopyBoard(currentBoard, boards[0]);
14707         CopyBoard(menuBoard, initialPosition);
14708         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14709             SendToICS(ics_prefix);
14710             SendToICS("bsetup clear\n");
14711         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14712             SendToICS(ics_prefix);
14713             SendToICS("clearboard\n");
14714         } else {
14715             int nonEmpty = 0;
14716             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14717                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14718                 for (y = 0; y < BOARD_HEIGHT; y++) {
14719                     if (gameMode == IcsExamining) {
14720                         if (boards[currentMove][y][x] != EmptySquare) {
14721                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14722                                     AAA + x, ONE + y);
14723                             SendToICS(buf);
14724                         }
14725                     } else {
14726                         if(boards[0][y][x] != p) nonEmpty++;
14727                         boards[0][y][x] = p;
14728                     }
14729                 }
14730                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14731             }
14732             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14733                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14734                     ChessSquare p = menuBoard[0][x];
14735                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14736                     p = menuBoard[BOARD_HEIGHT-1][x];
14737                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14738                 }
14739                 DisplayMessage("Clicking clock again restores position", "");
14740                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14741                 if(!nonEmpty) { // asked to clear an empty board
14742                     CopyBoard(boards[0], menuBoard);
14743                 } else
14744                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14745                     CopyBoard(boards[0], initialPosition);
14746                 } else
14747                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14748                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14749                     CopyBoard(boards[0], erasedBoard);
14750                 } else
14751                     CopyBoard(erasedBoard, currentBoard);
14752
14753             }
14754         }
14755         if (gameMode == EditPosition) {
14756             DrawPosition(FALSE, boards[0]);
14757         }
14758         break;
14759
14760       case WhitePlay:
14761         SetWhiteToPlayEvent();
14762         break;
14763
14764       case BlackPlay:
14765         SetBlackToPlayEvent();
14766         break;
14767
14768       case EmptySquare:
14769         if (gameMode == IcsExamining) {
14770             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14771             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14772             SendToICS(buf);
14773         } else {
14774             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14775                 if(x == BOARD_LEFT-2) {
14776                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14777                     boards[0][y][1] = 0;
14778                 } else
14779                 if(x == BOARD_RGHT+1) {
14780                     if(y >= gameInfo.holdingsSize) break;
14781                     boards[0][y][BOARD_WIDTH-2] = 0;
14782                 } else break;
14783             }
14784             boards[0][y][x] = EmptySquare;
14785             DrawPosition(FALSE, boards[0]);
14786         }
14787         break;
14788
14789       case PromotePiece:
14790         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14791            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14792             selection = (ChessSquare) (PROMOTED piece);
14793         } else if(piece == EmptySquare) selection = WhiteSilver;
14794         else selection = (ChessSquare)((int)piece - 1);
14795         goto defaultlabel;
14796
14797       case DemotePiece:
14798         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14799            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14800             selection = (ChessSquare) (DEMOTED piece);
14801         } else if(piece == EmptySquare) selection = BlackSilver;
14802         else selection = (ChessSquare)((int)piece + 1);
14803         goto defaultlabel;
14804
14805       case WhiteQueen:
14806       case BlackQueen:
14807         if(gameInfo.variant == VariantShatranj ||
14808            gameInfo.variant == VariantXiangqi  ||
14809            gameInfo.variant == VariantCourier  ||
14810            gameInfo.variant == VariantASEAN    ||
14811            gameInfo.variant == VariantMakruk     )
14812             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14813         goto defaultlabel;
14814
14815       case WhiteKing:
14816       case BlackKing:
14817         if(gameInfo.variant == VariantXiangqi)
14818             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14819         if(gameInfo.variant == VariantKnightmate)
14820             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14821       default:
14822         defaultlabel:
14823         if (gameMode == IcsExamining) {
14824             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14825             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14826                      PieceToChar(selection), AAA + x, ONE + y);
14827             SendToICS(buf);
14828         } else {
14829             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14830                 int n;
14831                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14832                     n = PieceToNumber(selection - BlackPawn);
14833                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14834                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14835                     boards[0][BOARD_HEIGHT-1-n][1]++;
14836                 } else
14837                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14838                     n = PieceToNumber(selection);
14839                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14840                     boards[0][n][BOARD_WIDTH-1] = selection;
14841                     boards[0][n][BOARD_WIDTH-2]++;
14842                 }
14843             } else
14844             boards[0][y][x] = selection;
14845             DrawPosition(TRUE, boards[0]);
14846             ClearHighlights();
14847             fromX = fromY = -1;
14848         }
14849         break;
14850     }
14851 }
14852
14853
14854 void
14855 DropMenuEvent (ChessSquare selection, int x, int y)
14856 {
14857     ChessMove moveType;
14858
14859     switch (gameMode) {
14860       case IcsPlayingWhite:
14861       case MachinePlaysBlack:
14862         if (!WhiteOnMove(currentMove)) {
14863             DisplayMoveError(_("It is Black's turn"));
14864             return;
14865         }
14866         moveType = WhiteDrop;
14867         break;
14868       case IcsPlayingBlack:
14869       case MachinePlaysWhite:
14870         if (WhiteOnMove(currentMove)) {
14871             DisplayMoveError(_("It is White's turn"));
14872             return;
14873         }
14874         moveType = BlackDrop;
14875         break;
14876       case EditGame:
14877         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14878         break;
14879       default:
14880         return;
14881     }
14882
14883     if (moveType == BlackDrop && selection < BlackPawn) {
14884       selection = (ChessSquare) ((int) selection
14885                                  + (int) BlackPawn - (int) WhitePawn);
14886     }
14887     if (boards[currentMove][y][x] != EmptySquare) {
14888         DisplayMoveError(_("That square is occupied"));
14889         return;
14890     }
14891
14892     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14893 }
14894
14895 void
14896 AcceptEvent ()
14897 {
14898     /* Accept a pending offer of any kind from opponent */
14899
14900     if (appData.icsActive) {
14901         SendToICS(ics_prefix);
14902         SendToICS("accept\n");
14903     } else if (cmailMsgLoaded) {
14904         if (currentMove == cmailOldMove &&
14905             commentList[cmailOldMove] != NULL &&
14906             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14907                    "Black offers a draw" : "White offers a draw")) {
14908             TruncateGame();
14909             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14910             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14911         } else {
14912             DisplayError(_("There is no pending offer on this move"), 0);
14913             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14914         }
14915     } else {
14916         /* Not used for offers from chess program */
14917     }
14918 }
14919
14920 void
14921 DeclineEvent ()
14922 {
14923     /* Decline a pending offer of any kind from opponent */
14924
14925     if (appData.icsActive) {
14926         SendToICS(ics_prefix);
14927         SendToICS("decline\n");
14928     } else if (cmailMsgLoaded) {
14929         if (currentMove == cmailOldMove &&
14930             commentList[cmailOldMove] != NULL &&
14931             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14932                    "Black offers a draw" : "White offers a draw")) {
14933 #ifdef NOTDEF
14934             AppendComment(cmailOldMove, "Draw declined", TRUE);
14935             DisplayComment(cmailOldMove - 1, "Draw declined");
14936 #endif /*NOTDEF*/
14937         } else {
14938             DisplayError(_("There is no pending offer on this move"), 0);
14939         }
14940     } else {
14941         /* Not used for offers from chess program */
14942     }
14943 }
14944
14945 void
14946 RematchEvent ()
14947 {
14948     /* Issue ICS rematch command */
14949     if (appData.icsActive) {
14950         SendToICS(ics_prefix);
14951         SendToICS("rematch\n");
14952     }
14953 }
14954
14955 void
14956 CallFlagEvent ()
14957 {
14958     /* Call your opponent's flag (claim a win on time) */
14959     if (appData.icsActive) {
14960         SendToICS(ics_prefix);
14961         SendToICS("flag\n");
14962     } else {
14963         switch (gameMode) {
14964           default:
14965             return;
14966           case MachinePlaysWhite:
14967             if (whiteFlag) {
14968                 if (blackFlag)
14969                   GameEnds(GameIsDrawn, "Both players ran out of time",
14970                            GE_PLAYER);
14971                 else
14972                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14973             } else {
14974                 DisplayError(_("Your opponent is not out of time"), 0);
14975             }
14976             break;
14977           case MachinePlaysBlack:
14978             if (blackFlag) {
14979                 if (whiteFlag)
14980                   GameEnds(GameIsDrawn, "Both players ran out of time",
14981                            GE_PLAYER);
14982                 else
14983                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14984             } else {
14985                 DisplayError(_("Your opponent is not out of time"), 0);
14986             }
14987             break;
14988         }
14989     }
14990 }
14991
14992 void
14993 ClockClick (int which)
14994 {       // [HGM] code moved to back-end from winboard.c
14995         if(which) { // black clock
14996           if (gameMode == EditPosition || gameMode == IcsExamining) {
14997             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14998             SetBlackToPlayEvent();
14999           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15000           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15001           } else if (shiftKey) {
15002             AdjustClock(which, -1);
15003           } else if (gameMode == IcsPlayingWhite ||
15004                      gameMode == MachinePlaysBlack) {
15005             CallFlagEvent();
15006           }
15007         } else { // white clock
15008           if (gameMode == EditPosition || gameMode == IcsExamining) {
15009             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15010             SetWhiteToPlayEvent();
15011           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15012           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15013           } else if (shiftKey) {
15014             AdjustClock(which, -1);
15015           } else if (gameMode == IcsPlayingBlack ||
15016                    gameMode == MachinePlaysWhite) {
15017             CallFlagEvent();
15018           }
15019         }
15020 }
15021
15022 void
15023 DrawEvent ()
15024 {
15025     /* Offer draw or accept pending draw offer from opponent */
15026
15027     if (appData.icsActive) {
15028         /* Note: tournament rules require draw offers to be
15029            made after you make your move but before you punch
15030            your clock.  Currently ICS doesn't let you do that;
15031            instead, you immediately punch your clock after making
15032            a move, but you can offer a draw at any time. */
15033
15034         SendToICS(ics_prefix);
15035         SendToICS("draw\n");
15036         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15037     } else if (cmailMsgLoaded) {
15038         if (currentMove == cmailOldMove &&
15039             commentList[cmailOldMove] != NULL &&
15040             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15041                    "Black offers a draw" : "White offers a draw")) {
15042             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15043             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15044         } else if (currentMove == cmailOldMove + 1) {
15045             char *offer = WhiteOnMove(cmailOldMove) ?
15046               "White offers a draw" : "Black offers a draw";
15047             AppendComment(currentMove, offer, TRUE);
15048             DisplayComment(currentMove - 1, offer);
15049             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15050         } else {
15051             DisplayError(_("You must make your move before offering a draw"), 0);
15052             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15053         }
15054     } else if (first.offeredDraw) {
15055         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15056     } else {
15057         if (first.sendDrawOffers) {
15058             SendToProgram("draw\n", &first);
15059             userOfferedDraw = TRUE;
15060         }
15061     }
15062 }
15063
15064 void
15065 AdjournEvent ()
15066 {
15067     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15068
15069     if (appData.icsActive) {
15070         SendToICS(ics_prefix);
15071         SendToICS("adjourn\n");
15072     } else {
15073         /* Currently GNU Chess doesn't offer or accept Adjourns */
15074     }
15075 }
15076
15077
15078 void
15079 AbortEvent ()
15080 {
15081     /* Offer Abort or accept pending Abort offer from opponent */
15082
15083     if (appData.icsActive) {
15084         SendToICS(ics_prefix);
15085         SendToICS("abort\n");
15086     } else {
15087         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15088     }
15089 }
15090
15091 void
15092 ResignEvent ()
15093 {
15094     /* Resign.  You can do this even if it's not your turn. */
15095
15096     if (appData.icsActive) {
15097         SendToICS(ics_prefix);
15098         SendToICS("resign\n");
15099     } else {
15100         switch (gameMode) {
15101           case MachinePlaysWhite:
15102             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15103             break;
15104           case MachinePlaysBlack:
15105             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15106             break;
15107           case EditGame:
15108             if (cmailMsgLoaded) {
15109                 TruncateGame();
15110                 if (WhiteOnMove(cmailOldMove)) {
15111                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15112                 } else {
15113                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15114                 }
15115                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15116             }
15117             break;
15118           default:
15119             break;
15120         }
15121     }
15122 }
15123
15124
15125 void
15126 StopObservingEvent ()
15127 {
15128     /* Stop observing current games */
15129     SendToICS(ics_prefix);
15130     SendToICS("unobserve\n");
15131 }
15132
15133 void
15134 StopExaminingEvent ()
15135 {
15136     /* Stop observing current game */
15137     SendToICS(ics_prefix);
15138     SendToICS("unexamine\n");
15139 }
15140
15141 void
15142 ForwardInner (int target)
15143 {
15144     int limit; int oldSeekGraphUp = seekGraphUp;
15145
15146     if (appData.debugMode)
15147         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15148                 target, currentMove, forwardMostMove);
15149
15150     if (gameMode == EditPosition)
15151       return;
15152
15153     seekGraphUp = FALSE;
15154     MarkTargetSquares(1);
15155
15156     if (gameMode == PlayFromGameFile && !pausing)
15157       PauseEvent();
15158
15159     if (gameMode == IcsExamining && pausing)
15160       limit = pauseExamForwardMostMove;
15161     else
15162       limit = forwardMostMove;
15163
15164     if (target > limit) target = limit;
15165
15166     if (target > 0 && moveList[target - 1][0]) {
15167         int fromX, fromY, toX, toY;
15168         toX = moveList[target - 1][2] - AAA;
15169         toY = moveList[target - 1][3] - ONE;
15170         if (moveList[target - 1][1] == '@') {
15171             if (appData.highlightLastMove) {
15172                 SetHighlights(-1, -1, toX, toY);
15173             }
15174         } else {
15175             fromX = moveList[target - 1][0] - AAA;
15176             fromY = moveList[target - 1][1] - ONE;
15177             if (target == currentMove + 1) {
15178                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15179             }
15180             if (appData.highlightLastMove) {
15181                 SetHighlights(fromX, fromY, toX, toY);
15182             }
15183         }
15184     }
15185     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15186         gameMode == Training || gameMode == PlayFromGameFile ||
15187         gameMode == AnalyzeFile) {
15188         while (currentMove < target) {
15189             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15190             SendMoveToProgram(currentMove++, &first);
15191         }
15192     } else {
15193         currentMove = target;
15194     }
15195
15196     if (gameMode == EditGame || gameMode == EndOfGame) {
15197         whiteTimeRemaining = timeRemaining[0][currentMove];
15198         blackTimeRemaining = timeRemaining[1][currentMove];
15199     }
15200     DisplayBothClocks();
15201     DisplayMove(currentMove - 1);
15202     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15203     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15204     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15205         DisplayComment(currentMove - 1, commentList[currentMove]);
15206     }
15207     ClearMap(); // [HGM] exclude: invalidate map
15208 }
15209
15210
15211 void
15212 ForwardEvent ()
15213 {
15214     if (gameMode == IcsExamining && !pausing) {
15215         SendToICS(ics_prefix);
15216         SendToICS("forward\n");
15217     } else {
15218         ForwardInner(currentMove + 1);
15219     }
15220 }
15221
15222 void
15223 ToEndEvent ()
15224 {
15225     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15226         /* to optimze, we temporarily turn off analysis mode while we feed
15227          * the remaining moves to the engine. Otherwise we get analysis output
15228          * after each move.
15229          */
15230         if (first.analysisSupport) {
15231           SendToProgram("exit\nforce\n", &first);
15232           first.analyzing = FALSE;
15233         }
15234     }
15235
15236     if (gameMode == IcsExamining && !pausing) {
15237         SendToICS(ics_prefix);
15238         SendToICS("forward 999999\n");
15239     } else {
15240         ForwardInner(forwardMostMove);
15241     }
15242
15243     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15244         /* we have fed all the moves, so reactivate analysis mode */
15245         SendToProgram("analyze\n", &first);
15246         first.analyzing = TRUE;
15247         /*first.maybeThinking = TRUE;*/
15248         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15249     }
15250 }
15251
15252 void
15253 BackwardInner (int target)
15254 {
15255     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15256
15257     if (appData.debugMode)
15258         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15259                 target, currentMove, forwardMostMove);
15260
15261     if (gameMode == EditPosition) return;
15262     seekGraphUp = FALSE;
15263     MarkTargetSquares(1);
15264     if (currentMove <= backwardMostMove) {
15265         ClearHighlights();
15266         DrawPosition(full_redraw, boards[currentMove]);
15267         return;
15268     }
15269     if (gameMode == PlayFromGameFile && !pausing)
15270       PauseEvent();
15271
15272     if (moveList[target][0]) {
15273         int fromX, fromY, toX, toY;
15274         toX = moveList[target][2] - AAA;
15275         toY = moveList[target][3] - ONE;
15276         if (moveList[target][1] == '@') {
15277             if (appData.highlightLastMove) {
15278                 SetHighlights(-1, -1, toX, toY);
15279             }
15280         } else {
15281             fromX = moveList[target][0] - AAA;
15282             fromY = moveList[target][1] - ONE;
15283             if (target == currentMove - 1) {
15284                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15285             }
15286             if (appData.highlightLastMove) {
15287                 SetHighlights(fromX, fromY, toX, toY);
15288             }
15289         }
15290     }
15291     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15292         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15293         while (currentMove > target) {
15294             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15295                 // null move cannot be undone. Reload program with move history before it.
15296                 int i;
15297                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15298                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15299                 }
15300                 SendBoard(&first, i);
15301               if(second.analyzing) SendBoard(&second, i);
15302                 for(currentMove=i; currentMove<target; currentMove++) {
15303                     SendMoveToProgram(currentMove, &first);
15304                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15305                 }
15306                 break;
15307             }
15308             SendToBoth("undo\n");
15309             currentMove--;
15310         }
15311     } else {
15312         currentMove = target;
15313     }
15314
15315     if (gameMode == EditGame || gameMode == EndOfGame) {
15316         whiteTimeRemaining = timeRemaining[0][currentMove];
15317         blackTimeRemaining = timeRemaining[1][currentMove];
15318     }
15319     DisplayBothClocks();
15320     DisplayMove(currentMove - 1);
15321     DrawPosition(full_redraw, boards[currentMove]);
15322     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15323     // [HGM] PV info: routine tests if comment empty
15324     DisplayComment(currentMove - 1, commentList[currentMove]);
15325     ClearMap(); // [HGM] exclude: invalidate map
15326 }
15327
15328 void
15329 BackwardEvent ()
15330 {
15331     if (gameMode == IcsExamining && !pausing) {
15332         SendToICS(ics_prefix);
15333         SendToICS("backward\n");
15334     } else {
15335         BackwardInner(currentMove - 1);
15336     }
15337 }
15338
15339 void
15340 ToStartEvent ()
15341 {
15342     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15343         /* to optimize, we temporarily turn off analysis mode while we undo
15344          * all the moves. Otherwise we get analysis output after each undo.
15345          */
15346         if (first.analysisSupport) {
15347           SendToProgram("exit\nforce\n", &first);
15348           first.analyzing = FALSE;
15349         }
15350     }
15351
15352     if (gameMode == IcsExamining && !pausing) {
15353         SendToICS(ics_prefix);
15354         SendToICS("backward 999999\n");
15355     } else {
15356         BackwardInner(backwardMostMove);
15357     }
15358
15359     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15360         /* we have fed all the moves, so reactivate analysis mode */
15361         SendToProgram("analyze\n", &first);
15362         first.analyzing = TRUE;
15363         /*first.maybeThinking = TRUE;*/
15364         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15365     }
15366 }
15367
15368 void
15369 ToNrEvent (int to)
15370 {
15371   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15372   if (to >= forwardMostMove) to = forwardMostMove;
15373   if (to <= backwardMostMove) to = backwardMostMove;
15374   if (to < currentMove) {
15375     BackwardInner(to);
15376   } else {
15377     ForwardInner(to);
15378   }
15379 }
15380
15381 void
15382 RevertEvent (Boolean annotate)
15383 {
15384     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15385         return;
15386     }
15387     if (gameMode != IcsExamining) {
15388         DisplayError(_("You are not examining a game"), 0);
15389         return;
15390     }
15391     if (pausing) {
15392         DisplayError(_("You can't revert while pausing"), 0);
15393         return;
15394     }
15395     SendToICS(ics_prefix);
15396     SendToICS("revert\n");
15397 }
15398
15399 void
15400 RetractMoveEvent ()
15401 {
15402     switch (gameMode) {
15403       case MachinePlaysWhite:
15404       case MachinePlaysBlack:
15405         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15406             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15407             return;
15408         }
15409         if (forwardMostMove < 2) return;
15410         currentMove = forwardMostMove = forwardMostMove - 2;
15411         whiteTimeRemaining = timeRemaining[0][currentMove];
15412         blackTimeRemaining = timeRemaining[1][currentMove];
15413         DisplayBothClocks();
15414         DisplayMove(currentMove - 1);
15415         ClearHighlights();/*!! could figure this out*/
15416         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15417         SendToProgram("remove\n", &first);
15418         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15419         break;
15420
15421       case BeginningOfGame:
15422       default:
15423         break;
15424
15425       case IcsPlayingWhite:
15426       case IcsPlayingBlack:
15427         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15428             SendToICS(ics_prefix);
15429             SendToICS("takeback 2\n");
15430         } else {
15431             SendToICS(ics_prefix);
15432             SendToICS("takeback 1\n");
15433         }
15434         break;
15435     }
15436 }
15437
15438 void
15439 MoveNowEvent ()
15440 {
15441     ChessProgramState *cps;
15442
15443     switch (gameMode) {
15444       case MachinePlaysWhite:
15445         if (!WhiteOnMove(forwardMostMove)) {
15446             DisplayError(_("It is your turn"), 0);
15447             return;
15448         }
15449         cps = &first;
15450         break;
15451       case MachinePlaysBlack:
15452         if (WhiteOnMove(forwardMostMove)) {
15453             DisplayError(_("It is your turn"), 0);
15454             return;
15455         }
15456         cps = &first;
15457         break;
15458       case TwoMachinesPlay:
15459         if (WhiteOnMove(forwardMostMove) ==
15460             (first.twoMachinesColor[0] == 'w')) {
15461             cps = &first;
15462         } else {
15463             cps = &second;
15464         }
15465         break;
15466       case BeginningOfGame:
15467       default:
15468         return;
15469     }
15470     SendToProgram("?\n", cps);
15471 }
15472
15473 void
15474 TruncateGameEvent ()
15475 {
15476     EditGameEvent();
15477     if (gameMode != EditGame) return;
15478     TruncateGame();
15479 }
15480
15481 void
15482 TruncateGame ()
15483 {
15484     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15485     if (forwardMostMove > currentMove) {
15486         if (gameInfo.resultDetails != NULL) {
15487             free(gameInfo.resultDetails);
15488             gameInfo.resultDetails = NULL;
15489             gameInfo.result = GameUnfinished;
15490         }
15491         forwardMostMove = currentMove;
15492         HistorySet(parseList, backwardMostMove, forwardMostMove,
15493                    currentMove-1);
15494     }
15495 }
15496
15497 void
15498 HintEvent ()
15499 {
15500     if (appData.noChessProgram) return;
15501     switch (gameMode) {
15502       case MachinePlaysWhite:
15503         if (WhiteOnMove(forwardMostMove)) {
15504             DisplayError(_("Wait until your turn."), 0);
15505             return;
15506         }
15507         break;
15508       case BeginningOfGame:
15509       case MachinePlaysBlack:
15510         if (!WhiteOnMove(forwardMostMove)) {
15511             DisplayError(_("Wait until your turn."), 0);
15512             return;
15513         }
15514         break;
15515       default:
15516         DisplayError(_("No hint available"), 0);
15517         return;
15518     }
15519     SendToProgram("hint\n", &first);
15520     hintRequested = TRUE;
15521 }
15522
15523 void
15524 CreateBookEvent ()
15525 {
15526     ListGame * lg = (ListGame *) gameList.head;
15527     FILE *f, *g;
15528     int nItem;
15529     static int secondTime = FALSE;
15530
15531     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15532         DisplayError(_("Game list not loaded or empty"), 0);
15533         return;
15534     }
15535
15536     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15537         fclose(g);
15538         secondTime++;
15539         DisplayNote(_("Book file exists! Try again for overwrite."));
15540         return;
15541     }
15542
15543     creatingBook = TRUE;
15544     secondTime = FALSE;
15545
15546     /* Get list size */
15547     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15548         LoadGame(f, nItem, "", TRUE);
15549         AddGameToBook(TRUE);
15550         lg = (ListGame *) lg->node.succ;
15551     }
15552
15553     creatingBook = FALSE;
15554     FlushBook();
15555 }
15556
15557 void
15558 BookEvent ()
15559 {
15560     if (appData.noChessProgram) return;
15561     switch (gameMode) {
15562       case MachinePlaysWhite:
15563         if (WhiteOnMove(forwardMostMove)) {
15564             DisplayError(_("Wait until your turn."), 0);
15565             return;
15566         }
15567         break;
15568       case BeginningOfGame:
15569       case MachinePlaysBlack:
15570         if (!WhiteOnMove(forwardMostMove)) {
15571             DisplayError(_("Wait until your turn."), 0);
15572             return;
15573         }
15574         break;
15575       case EditPosition:
15576         EditPositionDone(TRUE);
15577         break;
15578       case TwoMachinesPlay:
15579         return;
15580       default:
15581         break;
15582     }
15583     SendToProgram("bk\n", &first);
15584     bookOutput[0] = NULLCHAR;
15585     bookRequested = TRUE;
15586 }
15587
15588 void
15589 AboutGameEvent ()
15590 {
15591     char *tags = PGNTags(&gameInfo);
15592     TagsPopUp(tags, CmailMsg());
15593     free(tags);
15594 }
15595
15596 /* end button procedures */
15597
15598 void
15599 PrintPosition (FILE *fp, int move)
15600 {
15601     int i, j;
15602
15603     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15604         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15605             char c = PieceToChar(boards[move][i][j]);
15606             fputc(c == 'x' ? '.' : c, fp);
15607             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15608         }
15609     }
15610     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15611       fprintf(fp, "white to play\n");
15612     else
15613       fprintf(fp, "black to play\n");
15614 }
15615
15616 void
15617 PrintOpponents (FILE *fp)
15618 {
15619     if (gameInfo.white != NULL) {
15620         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15621     } else {
15622         fprintf(fp, "\n");
15623     }
15624 }
15625
15626 /* Find last component of program's own name, using some heuristics */
15627 void
15628 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15629 {
15630     char *p, *q, c;
15631     int local = (strcmp(host, "localhost") == 0);
15632     while (!local && (p = strchr(prog, ';')) != NULL) {
15633         p++;
15634         while (*p == ' ') p++;
15635         prog = p;
15636     }
15637     if (*prog == '"' || *prog == '\'') {
15638         q = strchr(prog + 1, *prog);
15639     } else {
15640         q = strchr(prog, ' ');
15641     }
15642     if (q == NULL) q = prog + strlen(prog);
15643     p = q;
15644     while (p >= prog && *p != '/' && *p != '\\') p--;
15645     p++;
15646     if(p == prog && *p == '"') p++;
15647     c = *q; *q = 0;
15648     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15649     memcpy(buf, p, q - p);
15650     buf[q - p] = NULLCHAR;
15651     if (!local) {
15652         strcat(buf, "@");
15653         strcat(buf, host);
15654     }
15655 }
15656
15657 char *
15658 TimeControlTagValue ()
15659 {
15660     char buf[MSG_SIZ];
15661     if (!appData.clockMode) {
15662       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15663     } else if (movesPerSession > 0) {
15664       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15665     } else if (timeIncrement == 0) {
15666       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15667     } else {
15668       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15669     }
15670     return StrSave(buf);
15671 }
15672
15673 void
15674 SetGameInfo ()
15675 {
15676     /* This routine is used only for certain modes */
15677     VariantClass v = gameInfo.variant;
15678     ChessMove r = GameUnfinished;
15679     char *p = NULL;
15680
15681     if(keepInfo) return;
15682
15683     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15684         r = gameInfo.result;
15685         p = gameInfo.resultDetails;
15686         gameInfo.resultDetails = NULL;
15687     }
15688     ClearGameInfo(&gameInfo);
15689     gameInfo.variant = v;
15690
15691     switch (gameMode) {
15692       case MachinePlaysWhite:
15693         gameInfo.event = StrSave( appData.pgnEventHeader );
15694         gameInfo.site = StrSave(HostName());
15695         gameInfo.date = PGNDate();
15696         gameInfo.round = StrSave("-");
15697         gameInfo.white = StrSave(first.tidy);
15698         gameInfo.black = StrSave(UserName());
15699         gameInfo.timeControl = TimeControlTagValue();
15700         break;
15701
15702       case MachinePlaysBlack:
15703         gameInfo.event = StrSave( appData.pgnEventHeader );
15704         gameInfo.site = StrSave(HostName());
15705         gameInfo.date = PGNDate();
15706         gameInfo.round = StrSave("-");
15707         gameInfo.white = StrSave(UserName());
15708         gameInfo.black = StrSave(first.tidy);
15709         gameInfo.timeControl = TimeControlTagValue();
15710         break;
15711
15712       case TwoMachinesPlay:
15713         gameInfo.event = StrSave( appData.pgnEventHeader );
15714         gameInfo.site = StrSave(HostName());
15715         gameInfo.date = PGNDate();
15716         if (roundNr > 0) {
15717             char buf[MSG_SIZ];
15718             snprintf(buf, MSG_SIZ, "%d", roundNr);
15719             gameInfo.round = StrSave(buf);
15720         } else {
15721             gameInfo.round = StrSave("-");
15722         }
15723         if (first.twoMachinesColor[0] == 'w') {
15724             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15725             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15726         } else {
15727             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15728             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15729         }
15730         gameInfo.timeControl = TimeControlTagValue();
15731         break;
15732
15733       case EditGame:
15734         gameInfo.event = StrSave("Edited game");
15735         gameInfo.site = StrSave(HostName());
15736         gameInfo.date = PGNDate();
15737         gameInfo.round = StrSave("-");
15738         gameInfo.white = StrSave("-");
15739         gameInfo.black = StrSave("-");
15740         gameInfo.result = r;
15741         gameInfo.resultDetails = p;
15742         break;
15743
15744       case EditPosition:
15745         gameInfo.event = StrSave("Edited position");
15746         gameInfo.site = StrSave(HostName());
15747         gameInfo.date = PGNDate();
15748         gameInfo.round = StrSave("-");
15749         gameInfo.white = StrSave("-");
15750         gameInfo.black = StrSave("-");
15751         break;
15752
15753       case IcsPlayingWhite:
15754       case IcsPlayingBlack:
15755       case IcsObserving:
15756       case IcsExamining:
15757         break;
15758
15759       case PlayFromGameFile:
15760         gameInfo.event = StrSave("Game from non-PGN file");
15761         gameInfo.site = StrSave(HostName());
15762         gameInfo.date = PGNDate();
15763         gameInfo.round = StrSave("-");
15764         gameInfo.white = StrSave("?");
15765         gameInfo.black = StrSave("?");
15766         break;
15767
15768       default:
15769         break;
15770     }
15771 }
15772
15773 void
15774 ReplaceComment (int index, char *text)
15775 {
15776     int len;
15777     char *p;
15778     float score;
15779
15780     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15781        pvInfoList[index-1].depth == len &&
15782        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15783        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15784     while (*text == '\n') text++;
15785     len = strlen(text);
15786     while (len > 0 && text[len - 1] == '\n') len--;
15787
15788     if (commentList[index] != NULL)
15789       free(commentList[index]);
15790
15791     if (len == 0) {
15792         commentList[index] = NULL;
15793         return;
15794     }
15795   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15796       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15797       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15798     commentList[index] = (char *) malloc(len + 2);
15799     strncpy(commentList[index], text, len);
15800     commentList[index][len] = '\n';
15801     commentList[index][len + 1] = NULLCHAR;
15802   } else {
15803     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15804     char *p;
15805     commentList[index] = (char *) malloc(len + 7);
15806     safeStrCpy(commentList[index], "{\n", 3);
15807     safeStrCpy(commentList[index]+2, text, len+1);
15808     commentList[index][len+2] = NULLCHAR;
15809     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15810     strcat(commentList[index], "\n}\n");
15811   }
15812 }
15813
15814 void
15815 CrushCRs (char *text)
15816 {
15817   char *p = text;
15818   char *q = text;
15819   char ch;
15820
15821   do {
15822     ch = *p++;
15823     if (ch == '\r') continue;
15824     *q++ = ch;
15825   } while (ch != '\0');
15826 }
15827
15828 void
15829 AppendComment (int index, char *text, Boolean addBraces)
15830 /* addBraces  tells if we should add {} */
15831 {
15832     int oldlen, len;
15833     char *old;
15834
15835 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15836     if(addBraces == 3) addBraces = 0; else // force appending literally
15837     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15838
15839     CrushCRs(text);
15840     while (*text == '\n') text++;
15841     len = strlen(text);
15842     while (len > 0 && text[len - 1] == '\n') len--;
15843     text[len] = NULLCHAR;
15844
15845     if (len == 0) return;
15846
15847     if (commentList[index] != NULL) {
15848       Boolean addClosingBrace = addBraces;
15849         old = commentList[index];
15850         oldlen = strlen(old);
15851         while(commentList[index][oldlen-1] ==  '\n')
15852           commentList[index][--oldlen] = NULLCHAR;
15853         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15854         safeStrCpy(commentList[index], old, oldlen + len + 6);
15855         free(old);
15856         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15857         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15858           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15859           while (*text == '\n') { text++; len--; }
15860           commentList[index][--oldlen] = NULLCHAR;
15861       }
15862         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15863         else          strcat(commentList[index], "\n");
15864         strcat(commentList[index], text);
15865         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15866         else          strcat(commentList[index], "\n");
15867     } else {
15868         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15869         if(addBraces)
15870           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15871         else commentList[index][0] = NULLCHAR;
15872         strcat(commentList[index], text);
15873         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15874         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15875     }
15876 }
15877
15878 static char *
15879 FindStr (char * text, char * sub_text)
15880 {
15881     char * result = strstr( text, sub_text );
15882
15883     if( result != NULL ) {
15884         result += strlen( sub_text );
15885     }
15886
15887     return result;
15888 }
15889
15890 /* [AS] Try to extract PV info from PGN comment */
15891 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15892 char *
15893 GetInfoFromComment (int index, char * text)
15894 {
15895     char * sep = text, *p;
15896
15897     if( text != NULL && index > 0 ) {
15898         int score = 0;
15899         int depth = 0;
15900         int time = -1, sec = 0, deci;
15901         char * s_eval = FindStr( text, "[%eval " );
15902         char * s_emt = FindStr( text, "[%emt " );
15903 #if 0
15904         if( s_eval != NULL || s_emt != NULL ) {
15905 #else
15906         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15907 #endif
15908             /* New style */
15909             char delim;
15910
15911             if( s_eval != NULL ) {
15912                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15913                     return text;
15914                 }
15915
15916                 if( delim != ']' ) {
15917                     return text;
15918                 }
15919             }
15920
15921             if( s_emt != NULL ) {
15922             }
15923                 return text;
15924         }
15925         else {
15926             /* We expect something like: [+|-]nnn.nn/dd */
15927             int score_lo = 0;
15928
15929             if(*text != '{') return text; // [HGM] braces: must be normal comment
15930
15931             sep = strchr( text, '/' );
15932             if( sep == NULL || sep < (text+4) ) {
15933                 return text;
15934             }
15935
15936             p = text;
15937             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15938             if(p[1] == '(') { // comment starts with PV
15939                p = strchr(p, ')'); // locate end of PV
15940                if(p == NULL || sep < p+5) return text;
15941                // at this point we have something like "{(.*) +0.23/6 ..."
15942                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15943                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15944                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15945             }
15946             time = -1; sec = -1; deci = -1;
15947             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15948                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15949                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15950                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15951                 return text;
15952             }
15953
15954             if( score_lo < 0 || score_lo >= 100 ) {
15955                 return text;
15956             }
15957
15958             if(sec >= 0) time = 600*time + 10*sec; else
15959             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15960
15961             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15962
15963             /* [HGM] PV time: now locate end of PV info */
15964             while( *++sep >= '0' && *sep <= '9'); // strip depth
15965             if(time >= 0)
15966             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15967             if(sec >= 0)
15968             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15969             if(deci >= 0)
15970             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15971             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15972         }
15973
15974         if( depth <= 0 ) {
15975             return text;
15976         }
15977
15978         if( time < 0 ) {
15979             time = -1;
15980         }
15981
15982         pvInfoList[index-1].depth = depth;
15983         pvInfoList[index-1].score = score;
15984         pvInfoList[index-1].time  = 10*time; // centi-sec
15985         if(*sep == '}') *sep = 0; else *--sep = '{';
15986         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15987     }
15988     return sep;
15989 }
15990
15991 void
15992 SendToProgram (char *message, ChessProgramState *cps)
15993 {
15994     int count, outCount, error;
15995     char buf[MSG_SIZ];
15996
15997     if (cps->pr == NoProc) return;
15998     Attention(cps);
15999
16000     if (appData.debugMode) {
16001         TimeMark now;
16002         GetTimeMark(&now);
16003         fprintf(debugFP, "%ld >%-6s: %s",
16004                 SubtractTimeMarks(&now, &programStartTime),
16005                 cps->which, message);
16006         if(serverFP)
16007             fprintf(serverFP, "%ld >%-6s: %s",
16008                 SubtractTimeMarks(&now, &programStartTime),
16009                 cps->which, message), fflush(serverFP);
16010     }
16011
16012     count = strlen(message);
16013     outCount = OutputToProcess(cps->pr, message, count, &error);
16014     if (outCount < count && !exiting
16015                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16016       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16017       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16018         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16019             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16020                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16021                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16022                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16023             } else {
16024                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16025                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16026                 gameInfo.result = res;
16027             }
16028             gameInfo.resultDetails = StrSave(buf);
16029         }
16030         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16031         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16032     }
16033 }
16034
16035 void
16036 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16037 {
16038     char *end_str;
16039     char buf[MSG_SIZ];
16040     ChessProgramState *cps = (ChessProgramState *)closure;
16041
16042     if (isr != cps->isr) return; /* Killed intentionally */
16043     if (count <= 0) {
16044         if (count == 0) {
16045             RemoveInputSource(cps->isr);
16046             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16047                     _(cps->which), cps->program);
16048             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16049             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16050                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16051                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16052                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16053                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16054                 } else {
16055                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16056                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16057                     gameInfo.result = res;
16058                 }
16059                 gameInfo.resultDetails = StrSave(buf);
16060             }
16061             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16062             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16063         } else {
16064             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16065                     _(cps->which), cps->program);
16066             RemoveInputSource(cps->isr);
16067
16068             /* [AS] Program is misbehaving badly... kill it */
16069             if( count == -2 ) {
16070                 DestroyChildProcess( cps->pr, 9 );
16071                 cps->pr = NoProc;
16072             }
16073
16074             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16075         }
16076         return;
16077     }
16078
16079     if ((end_str = strchr(message, '\r')) != NULL)
16080       *end_str = NULLCHAR;
16081     if ((end_str = strchr(message, '\n')) != NULL)
16082       *end_str = NULLCHAR;
16083
16084     if (appData.debugMode) {
16085         TimeMark now; int print = 1;
16086         char *quote = ""; char c; int i;
16087
16088         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16089                 char start = message[0];
16090                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16091                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16092                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16093                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16094                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16095                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16096                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16097                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16098                    sscanf(message, "hint: %c", &c)!=1 &&
16099                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16100                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16101                     print = (appData.engineComments >= 2);
16102                 }
16103                 message[0] = start; // restore original message
16104         }
16105         if(print) {
16106                 GetTimeMark(&now);
16107                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16108                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16109                         quote,
16110                         message);
16111                 if(serverFP)
16112                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16113                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16114                         quote,
16115                         message), fflush(serverFP);
16116         }
16117     }
16118
16119     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16120     if (appData.icsEngineAnalyze) {
16121         if (strstr(message, "whisper") != NULL ||
16122              strstr(message, "kibitz") != NULL ||
16123             strstr(message, "tellics") != NULL) return;
16124     }
16125
16126     HandleMachineMove(message, cps);
16127 }
16128
16129
16130 void
16131 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16132 {
16133     char buf[MSG_SIZ];
16134     int seconds;
16135
16136     if( timeControl_2 > 0 ) {
16137         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16138             tc = timeControl_2;
16139         }
16140     }
16141     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16142     inc /= cps->timeOdds;
16143     st  /= cps->timeOdds;
16144
16145     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16146
16147     if (st > 0) {
16148       /* Set exact time per move, normally using st command */
16149       if (cps->stKludge) {
16150         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16151         seconds = st % 60;
16152         if (seconds == 0) {
16153           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16154         } else {
16155           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16156         }
16157       } else {
16158         snprintf(buf, MSG_SIZ, "st %d\n", st);
16159       }
16160     } else {
16161       /* Set conventional or incremental time control, using level command */
16162       if (seconds == 0) {
16163         /* Note old gnuchess bug -- minutes:seconds used to not work.
16164            Fixed in later versions, but still avoid :seconds
16165            when seconds is 0. */
16166         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16167       } else {
16168         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16169                  seconds, inc/1000.);
16170       }
16171     }
16172     SendToProgram(buf, cps);
16173
16174     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16175     /* Orthogonally, limit search to given depth */
16176     if (sd > 0) {
16177       if (cps->sdKludge) {
16178         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16179       } else {
16180         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16181       }
16182       SendToProgram(buf, cps);
16183     }
16184
16185     if(cps->nps >= 0) { /* [HGM] nps */
16186         if(cps->supportsNPS == FALSE)
16187           cps->nps = -1; // don't use if engine explicitly says not supported!
16188         else {
16189           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16190           SendToProgram(buf, cps);
16191         }
16192     }
16193 }
16194
16195 ChessProgramState *
16196 WhitePlayer ()
16197 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16198 {
16199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16200        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16201         return &second;
16202     return &first;
16203 }
16204
16205 void
16206 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16207 {
16208     char message[MSG_SIZ];
16209     long time, otime;
16210
16211     /* Note: this routine must be called when the clocks are stopped
16212        or when they have *just* been set or switched; otherwise
16213        it will be off by the time since the current tick started.
16214     */
16215     if (machineWhite) {
16216         time = whiteTimeRemaining / 10;
16217         otime = blackTimeRemaining / 10;
16218     } else {
16219         time = blackTimeRemaining / 10;
16220         otime = whiteTimeRemaining / 10;
16221     }
16222     /* [HGM] translate opponent's time by time-odds factor */
16223     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16224
16225     if (time <= 0) time = 1;
16226     if (otime <= 0) otime = 1;
16227
16228     snprintf(message, MSG_SIZ, "time %ld\n", time);
16229     SendToProgram(message, cps);
16230
16231     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16232     SendToProgram(message, cps);
16233 }
16234
16235 char *
16236 EngineDefinedVariant (ChessProgramState *cps, int n)
16237 {   // return name of n-th unknown variant that engine supports
16238     static char buf[MSG_SIZ];
16239     char *p, *s = cps->variants;
16240     if(!s) return NULL;
16241     do { // parse string from variants feature
16242       VariantClass v;
16243         p = strchr(s, ',');
16244         if(p) *p = NULLCHAR;
16245       v = StringToVariant(s);
16246       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16247         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16248             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16249         }
16250         if(p) *p++ = ',';
16251         if(n < 0) return buf;
16252     } while(s = p);
16253     return NULL;
16254 }
16255
16256 int
16257 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16258 {
16259   char buf[MSG_SIZ];
16260   int len = strlen(name);
16261   int val;
16262
16263   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16264     (*p) += len + 1;
16265     sscanf(*p, "%d", &val);
16266     *loc = (val != 0);
16267     while (**p && **p != ' ')
16268       (*p)++;
16269     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16270     SendToProgram(buf, cps);
16271     return TRUE;
16272   }
16273   return FALSE;
16274 }
16275
16276 int
16277 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16278 {
16279   char buf[MSG_SIZ];
16280   int len = strlen(name);
16281   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16282     (*p) += len + 1;
16283     sscanf(*p, "%d", loc);
16284     while (**p && **p != ' ') (*p)++;
16285     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16286     SendToProgram(buf, cps);
16287     return TRUE;
16288   }
16289   return FALSE;
16290 }
16291
16292 int
16293 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16294 {
16295   char buf[MSG_SIZ];
16296   int len = strlen(name);
16297   if (strncmp((*p), name, len) == 0
16298       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16299     (*p) += len + 2;
16300     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16301     sscanf(*p, "%[^\"]", *loc);
16302     while (**p && **p != '\"') (*p)++;
16303     if (**p == '\"') (*p)++;
16304     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16305     SendToProgram(buf, cps);
16306     return TRUE;
16307   }
16308   return FALSE;
16309 }
16310
16311 int
16312 ParseOption (Option *opt, ChessProgramState *cps)
16313 // [HGM] options: process the string that defines an engine option, and determine
16314 // name, type, default value, and allowed value range
16315 {
16316         char *p, *q, buf[MSG_SIZ];
16317         int n, min = (-1)<<31, max = 1<<31, def;
16318
16319         if(p = strstr(opt->name, " -spin ")) {
16320             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16321             if(max < min) max = min; // enforce consistency
16322             if(def < min) def = min;
16323             if(def > max) def = max;
16324             opt->value = def;
16325             opt->min = min;
16326             opt->max = max;
16327             opt->type = Spin;
16328         } else if((p = strstr(opt->name, " -slider "))) {
16329             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16330             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16331             if(max < min) max = min; // enforce consistency
16332             if(def < min) def = min;
16333             if(def > max) def = max;
16334             opt->value = def;
16335             opt->min = min;
16336             opt->max = max;
16337             opt->type = Spin; // Slider;
16338         } else if((p = strstr(opt->name, " -string "))) {
16339             opt->textValue = p+9;
16340             opt->type = TextBox;
16341         } else if((p = strstr(opt->name, " -file "))) {
16342             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16343             opt->textValue = p+7;
16344             opt->type = FileName; // FileName;
16345         } else if((p = strstr(opt->name, " -path "))) {
16346             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16347             opt->textValue = p+7;
16348             opt->type = PathName; // PathName;
16349         } else if(p = strstr(opt->name, " -check ")) {
16350             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16351             opt->value = (def != 0);
16352             opt->type = CheckBox;
16353         } else if(p = strstr(opt->name, " -combo ")) {
16354             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16355             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16356             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16357             opt->value = n = 0;
16358             while(q = StrStr(q, " /// ")) {
16359                 n++; *q = 0;    // count choices, and null-terminate each of them
16360                 q += 5;
16361                 if(*q == '*') { // remember default, which is marked with * prefix
16362                     q++;
16363                     opt->value = n;
16364                 }
16365                 cps->comboList[cps->comboCnt++] = q;
16366             }
16367             cps->comboList[cps->comboCnt++] = NULL;
16368             opt->max = n + 1;
16369             opt->type = ComboBox;
16370         } else if(p = strstr(opt->name, " -button")) {
16371             opt->type = Button;
16372         } else if(p = strstr(opt->name, " -save")) {
16373             opt->type = SaveButton;
16374         } else return FALSE;
16375         *p = 0; // terminate option name
16376         // now look if the command-line options define a setting for this engine option.
16377         if(cps->optionSettings && cps->optionSettings[0])
16378             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16379         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16380           snprintf(buf, MSG_SIZ, "option %s", p);
16381                 if(p = strstr(buf, ",")) *p = 0;
16382                 if(q = strchr(buf, '=')) switch(opt->type) {
16383                     case ComboBox:
16384                         for(n=0; n<opt->max; n++)
16385                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16386                         break;
16387                     case TextBox:
16388                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16389                         break;
16390                     case Spin:
16391                     case CheckBox:
16392                         opt->value = atoi(q+1);
16393                     default:
16394                         break;
16395                 }
16396                 strcat(buf, "\n");
16397                 SendToProgram(buf, cps);
16398         }
16399         return TRUE;
16400 }
16401
16402 void
16403 FeatureDone (ChessProgramState *cps, int val)
16404 {
16405   DelayedEventCallback cb = GetDelayedEvent();
16406   if ((cb == InitBackEnd3 && cps == &first) ||
16407       (cb == SettingsMenuIfReady && cps == &second) ||
16408       (cb == LoadEngine) ||
16409       (cb == TwoMachinesEventIfReady)) {
16410     CancelDelayedEvent();
16411     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16412   }
16413   cps->initDone = val;
16414   if(val) cps->reload = FALSE;
16415 }
16416
16417 /* Parse feature command from engine */
16418 void
16419 ParseFeatures (char *args, ChessProgramState *cps)
16420 {
16421   char *p = args;
16422   char *q = NULL;
16423   int val;
16424   char buf[MSG_SIZ];
16425
16426   for (;;) {
16427     while (*p == ' ') p++;
16428     if (*p == NULLCHAR) return;
16429
16430     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16431     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16432     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16433     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16434     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16435     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16436     if (BoolFeature(&p, "reuse", &val, cps)) {
16437       /* Engine can disable reuse, but can't enable it if user said no */
16438       if (!val) cps->reuse = FALSE;
16439       continue;
16440     }
16441     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16442     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16443       if (gameMode == TwoMachinesPlay) {
16444         DisplayTwoMachinesTitle();
16445       } else {
16446         DisplayTitle("");
16447       }
16448       continue;
16449     }
16450     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16451     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16452     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16453     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16454     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16455     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16456     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16457     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16458     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16459     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16460     if (IntFeature(&p, "done", &val, cps)) {
16461       FeatureDone(cps, val);
16462       continue;
16463     }
16464     /* Added by Tord: */
16465     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16466     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16467     /* End of additions by Tord */
16468
16469     /* [HGM] added features: */
16470     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16471     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16472     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16473     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16474     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16475     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16476     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16477     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16478         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16479         FREE(cps->option[cps->nrOptions].name);
16480         cps->option[cps->nrOptions].name = q; q = NULL;
16481         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16482           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16483             SendToProgram(buf, cps);
16484             continue;
16485         }
16486         if(cps->nrOptions >= MAX_OPTIONS) {
16487             cps->nrOptions--;
16488             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16489             DisplayError(buf, 0);
16490         }
16491         continue;
16492     }
16493     /* End of additions by HGM */
16494
16495     /* unknown feature: complain and skip */
16496     q = p;
16497     while (*q && *q != '=') q++;
16498     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16499     SendToProgram(buf, cps);
16500     p = q;
16501     if (*p == '=') {
16502       p++;
16503       if (*p == '\"') {
16504         p++;
16505         while (*p && *p != '\"') p++;
16506         if (*p == '\"') p++;
16507       } else {
16508         while (*p && *p != ' ') p++;
16509       }
16510     }
16511   }
16512
16513 }
16514
16515 void
16516 PeriodicUpdatesEvent (int newState)
16517 {
16518     if (newState == appData.periodicUpdates)
16519       return;
16520
16521     appData.periodicUpdates=newState;
16522
16523     /* Display type changes, so update it now */
16524 //    DisplayAnalysis();
16525
16526     /* Get the ball rolling again... */
16527     if (newState) {
16528         AnalysisPeriodicEvent(1);
16529         StartAnalysisClock();
16530     }
16531 }
16532
16533 void
16534 PonderNextMoveEvent (int newState)
16535 {
16536     if (newState == appData.ponderNextMove) return;
16537     if (gameMode == EditPosition) EditPositionDone(TRUE);
16538     if (newState) {
16539         SendToProgram("hard\n", &first);
16540         if (gameMode == TwoMachinesPlay) {
16541             SendToProgram("hard\n", &second);
16542         }
16543     } else {
16544         SendToProgram("easy\n", &first);
16545         thinkOutput[0] = NULLCHAR;
16546         if (gameMode == TwoMachinesPlay) {
16547             SendToProgram("easy\n", &second);
16548         }
16549     }
16550     appData.ponderNextMove = newState;
16551 }
16552
16553 void
16554 NewSettingEvent (int option, int *feature, char *command, int value)
16555 {
16556     char buf[MSG_SIZ];
16557
16558     if (gameMode == EditPosition) EditPositionDone(TRUE);
16559     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16560     if(feature == NULL || *feature) SendToProgram(buf, &first);
16561     if (gameMode == TwoMachinesPlay) {
16562         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16563     }
16564 }
16565
16566 void
16567 ShowThinkingEvent ()
16568 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16569 {
16570     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16571     int newState = appData.showThinking
16572         // [HGM] thinking: other features now need thinking output as well
16573         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16574
16575     if (oldState == newState) return;
16576     oldState = newState;
16577     if (gameMode == EditPosition) EditPositionDone(TRUE);
16578     if (oldState) {
16579         SendToProgram("post\n", &first);
16580         if (gameMode == TwoMachinesPlay) {
16581             SendToProgram("post\n", &second);
16582         }
16583     } else {
16584         SendToProgram("nopost\n", &first);
16585         thinkOutput[0] = NULLCHAR;
16586         if (gameMode == TwoMachinesPlay) {
16587             SendToProgram("nopost\n", &second);
16588         }
16589     }
16590 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16591 }
16592
16593 void
16594 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16595 {
16596   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16597   if (pr == NoProc) return;
16598   AskQuestion(title, question, replyPrefix, pr);
16599 }
16600
16601 void
16602 TypeInEvent (char firstChar)
16603 {
16604     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16605         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16606         gameMode == AnalyzeMode || gameMode == EditGame ||
16607         gameMode == EditPosition || gameMode == IcsExamining ||
16608         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16609         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16610                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16611                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16612         gameMode == Training) PopUpMoveDialog(firstChar);
16613 }
16614
16615 void
16616 TypeInDoneEvent (char *move)
16617 {
16618         Board board;
16619         int n, fromX, fromY, toX, toY;
16620         char promoChar;
16621         ChessMove moveType;
16622
16623         // [HGM] FENedit
16624         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16625                 EditPositionPasteFEN(move);
16626                 return;
16627         }
16628         // [HGM] movenum: allow move number to be typed in any mode
16629         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16630           ToNrEvent(2*n-1);
16631           return;
16632         }
16633         // undocumented kludge: allow command-line option to be typed in!
16634         // (potentially fatal, and does not implement the effect of the option.)
16635         // should only be used for options that are values on which future decisions will be made,
16636         // and definitely not on options that would be used during initialization.
16637         if(strstr(move, "!!! -") == move) {
16638             ParseArgsFromString(move+4);
16639             return;
16640         }
16641
16642       if (gameMode != EditGame && currentMove != forwardMostMove &&
16643         gameMode != Training) {
16644         DisplayMoveError(_("Displayed move is not current"));
16645       } else {
16646         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16647           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16648         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16649         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16650           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16651           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16652         } else {
16653           DisplayMoveError(_("Could not parse move"));
16654         }
16655       }
16656 }
16657
16658 void
16659 DisplayMove (int moveNumber)
16660 {
16661     char message[MSG_SIZ];
16662     char res[MSG_SIZ];
16663     char cpThinkOutput[MSG_SIZ];
16664
16665     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16666
16667     if (moveNumber == forwardMostMove - 1 ||
16668         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16669
16670         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16671
16672         if (strchr(cpThinkOutput, '\n')) {
16673             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16674         }
16675     } else {
16676         *cpThinkOutput = NULLCHAR;
16677     }
16678
16679     /* [AS] Hide thinking from human user */
16680     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16681         *cpThinkOutput = NULLCHAR;
16682         if( thinkOutput[0] != NULLCHAR ) {
16683             int i;
16684
16685             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16686                 cpThinkOutput[i] = '.';
16687             }
16688             cpThinkOutput[i] = NULLCHAR;
16689             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16690         }
16691     }
16692
16693     if (moveNumber == forwardMostMove - 1 &&
16694         gameInfo.resultDetails != NULL) {
16695         if (gameInfo.resultDetails[0] == NULLCHAR) {
16696           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16697         } else {
16698           snprintf(res, MSG_SIZ, " {%s} %s",
16699                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16700         }
16701     } else {
16702         res[0] = NULLCHAR;
16703     }
16704
16705     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16706         DisplayMessage(res, cpThinkOutput);
16707     } else {
16708       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16709                 WhiteOnMove(moveNumber) ? " " : ".. ",
16710                 parseList[moveNumber], res);
16711         DisplayMessage(message, cpThinkOutput);
16712     }
16713 }
16714
16715 void
16716 DisplayComment (int moveNumber, char *text)
16717 {
16718     char title[MSG_SIZ];
16719
16720     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16721       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16722     } else {
16723       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16724               WhiteOnMove(moveNumber) ? " " : ".. ",
16725               parseList[moveNumber]);
16726     }
16727     if (text != NULL && (appData.autoDisplayComment || commentUp))
16728         CommentPopUp(title, text);
16729 }
16730
16731 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16732  * might be busy thinking or pondering.  It can be omitted if your
16733  * gnuchess is configured to stop thinking immediately on any user
16734  * input.  However, that gnuchess feature depends on the FIONREAD
16735  * ioctl, which does not work properly on some flavors of Unix.
16736  */
16737 void
16738 Attention (ChessProgramState *cps)
16739 {
16740 #if ATTENTION
16741     if (!cps->useSigint) return;
16742     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16743     switch (gameMode) {
16744       case MachinePlaysWhite:
16745       case MachinePlaysBlack:
16746       case TwoMachinesPlay:
16747       case IcsPlayingWhite:
16748       case IcsPlayingBlack:
16749       case AnalyzeMode:
16750       case AnalyzeFile:
16751         /* Skip if we know it isn't thinking */
16752         if (!cps->maybeThinking) return;
16753         if (appData.debugMode)
16754           fprintf(debugFP, "Interrupting %s\n", cps->which);
16755         InterruptChildProcess(cps->pr);
16756         cps->maybeThinking = FALSE;
16757         break;
16758       default:
16759         break;
16760     }
16761 #endif /*ATTENTION*/
16762 }
16763
16764 int
16765 CheckFlags ()
16766 {
16767     if (whiteTimeRemaining <= 0) {
16768         if (!whiteFlag) {
16769             whiteFlag = TRUE;
16770             if (appData.icsActive) {
16771                 if (appData.autoCallFlag &&
16772                     gameMode == IcsPlayingBlack && !blackFlag) {
16773                   SendToICS(ics_prefix);
16774                   SendToICS("flag\n");
16775                 }
16776             } else {
16777                 if (blackFlag) {
16778                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16779                 } else {
16780                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16781                     if (appData.autoCallFlag) {
16782                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16783                         return TRUE;
16784                     }
16785                 }
16786             }
16787         }
16788     }
16789     if (blackTimeRemaining <= 0) {
16790         if (!blackFlag) {
16791             blackFlag = TRUE;
16792             if (appData.icsActive) {
16793                 if (appData.autoCallFlag &&
16794                     gameMode == IcsPlayingWhite && !whiteFlag) {
16795                   SendToICS(ics_prefix);
16796                   SendToICS("flag\n");
16797                 }
16798             } else {
16799                 if (whiteFlag) {
16800                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16801                 } else {
16802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16803                     if (appData.autoCallFlag) {
16804                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16805                         return TRUE;
16806                     }
16807                 }
16808             }
16809         }
16810     }
16811     return FALSE;
16812 }
16813
16814 void
16815 CheckTimeControl ()
16816 {
16817     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16818         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16819
16820     /*
16821      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16822      */
16823     if ( !WhiteOnMove(forwardMostMove) ) {
16824         /* White made time control */
16825         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16826         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16827         /* [HGM] time odds: correct new time quota for time odds! */
16828                                             / WhitePlayer()->timeOdds;
16829         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16830     } else {
16831         lastBlack -= blackTimeRemaining;
16832         /* Black made time control */
16833         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16834                                             / WhitePlayer()->other->timeOdds;
16835         lastWhite = whiteTimeRemaining;
16836     }
16837 }
16838
16839 void
16840 DisplayBothClocks ()
16841 {
16842     int wom = gameMode == EditPosition ?
16843       !blackPlaysFirst : WhiteOnMove(currentMove);
16844     DisplayWhiteClock(whiteTimeRemaining, wom);
16845     DisplayBlackClock(blackTimeRemaining, !wom);
16846 }
16847
16848
16849 /* Timekeeping seems to be a portability nightmare.  I think everyone
16850    has ftime(), but I'm really not sure, so I'm including some ifdefs
16851    to use other calls if you don't.  Clocks will be less accurate if
16852    you have neither ftime nor gettimeofday.
16853 */
16854
16855 /* VS 2008 requires the #include outside of the function */
16856 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16857 #include <sys/timeb.h>
16858 #endif
16859
16860 /* Get the current time as a TimeMark */
16861 void
16862 GetTimeMark (TimeMark *tm)
16863 {
16864 #if HAVE_GETTIMEOFDAY
16865
16866     struct timeval timeVal;
16867     struct timezone timeZone;
16868
16869     gettimeofday(&timeVal, &timeZone);
16870     tm->sec = (long) timeVal.tv_sec;
16871     tm->ms = (int) (timeVal.tv_usec / 1000L);
16872
16873 #else /*!HAVE_GETTIMEOFDAY*/
16874 #if HAVE_FTIME
16875
16876 // include <sys/timeb.h> / moved to just above start of function
16877     struct timeb timeB;
16878
16879     ftime(&timeB);
16880     tm->sec = (long) timeB.time;
16881     tm->ms = (int) timeB.millitm;
16882
16883 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16884     tm->sec = (long) time(NULL);
16885     tm->ms = 0;
16886 #endif
16887 #endif
16888 }
16889
16890 /* Return the difference in milliseconds between two
16891    time marks.  We assume the difference will fit in a long!
16892 */
16893 long
16894 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16895 {
16896     return 1000L*(tm2->sec - tm1->sec) +
16897            (long) (tm2->ms - tm1->ms);
16898 }
16899
16900
16901 /*
16902  * Code to manage the game clocks.
16903  *
16904  * In tournament play, black starts the clock and then white makes a move.
16905  * We give the human user a slight advantage if he is playing white---the
16906  * clocks don't run until he makes his first move, so it takes zero time.
16907  * Also, we don't account for network lag, so we could get out of sync
16908  * with GNU Chess's clock -- but then, referees are always right.
16909  */
16910
16911 static TimeMark tickStartTM;
16912 static long intendedTickLength;
16913
16914 long
16915 NextTickLength (long timeRemaining)
16916 {
16917     long nominalTickLength, nextTickLength;
16918
16919     if (timeRemaining > 0L && timeRemaining <= 10000L)
16920       nominalTickLength = 100L;
16921     else
16922       nominalTickLength = 1000L;
16923     nextTickLength = timeRemaining % nominalTickLength;
16924     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16925
16926     return nextTickLength;
16927 }
16928
16929 /* Adjust clock one minute up or down */
16930 void
16931 AdjustClock (Boolean which, int dir)
16932 {
16933     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16934     if(which) blackTimeRemaining += 60000*dir;
16935     else      whiteTimeRemaining += 60000*dir;
16936     DisplayBothClocks();
16937     adjustedClock = TRUE;
16938 }
16939
16940 /* Stop clocks and reset to a fresh time control */
16941 void
16942 ResetClocks ()
16943 {
16944     (void) StopClockTimer();
16945     if (appData.icsActive) {
16946         whiteTimeRemaining = blackTimeRemaining = 0;
16947     } else if (searchTime) {
16948         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16949         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16950     } else { /* [HGM] correct new time quote for time odds */
16951         whiteTC = blackTC = fullTimeControlString;
16952         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16953         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16954     }
16955     if (whiteFlag || blackFlag) {
16956         DisplayTitle("");
16957         whiteFlag = blackFlag = FALSE;
16958     }
16959     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16960     DisplayBothClocks();
16961     adjustedClock = FALSE;
16962 }
16963
16964 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16965
16966 /* Decrement running clock by amount of time that has passed */
16967 void
16968 DecrementClocks ()
16969 {
16970     long timeRemaining;
16971     long lastTickLength, fudge;
16972     TimeMark now;
16973
16974     if (!appData.clockMode) return;
16975     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16976
16977     GetTimeMark(&now);
16978
16979     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16980
16981     /* Fudge if we woke up a little too soon */
16982     fudge = intendedTickLength - lastTickLength;
16983     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16984
16985     if (WhiteOnMove(forwardMostMove)) {
16986         if(whiteNPS >= 0) lastTickLength = 0;
16987         timeRemaining = whiteTimeRemaining -= lastTickLength;
16988         if(timeRemaining < 0 && !appData.icsActive) {
16989             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16990             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16991                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16992                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16993             }
16994         }
16995         DisplayWhiteClock(whiteTimeRemaining - fudge,
16996                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16997     } else {
16998         if(blackNPS >= 0) lastTickLength = 0;
16999         timeRemaining = blackTimeRemaining -= lastTickLength;
17000         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17001             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17002             if(suddenDeath) {
17003                 blackStartMove = forwardMostMove;
17004                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17005             }
17006         }
17007         DisplayBlackClock(blackTimeRemaining - fudge,
17008                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17009     }
17010     if (CheckFlags()) return;
17011
17012     if(twoBoards) { // count down secondary board's clocks as well
17013         activePartnerTime -= lastTickLength;
17014         partnerUp = 1;
17015         if(activePartner == 'W')
17016             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17017         else
17018             DisplayBlackClock(activePartnerTime, TRUE);
17019         partnerUp = 0;
17020     }
17021
17022     tickStartTM = now;
17023     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17024     StartClockTimer(intendedTickLength);
17025
17026     /* if the time remaining has fallen below the alarm threshold, sound the
17027      * alarm. if the alarm has sounded and (due to a takeback or time control
17028      * with increment) the time remaining has increased to a level above the
17029      * threshold, reset the alarm so it can sound again.
17030      */
17031
17032     if (appData.icsActive && appData.icsAlarm) {
17033
17034         /* make sure we are dealing with the user's clock */
17035         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17036                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17037            )) return;
17038
17039         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17040             alarmSounded = FALSE;
17041         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17042             PlayAlarmSound();
17043             alarmSounded = TRUE;
17044         }
17045     }
17046 }
17047
17048
17049 /* A player has just moved, so stop the previously running
17050    clock and (if in clock mode) start the other one.
17051    We redisplay both clocks in case we're in ICS mode, because
17052    ICS gives us an update to both clocks after every move.
17053    Note that this routine is called *after* forwardMostMove
17054    is updated, so the last fractional tick must be subtracted
17055    from the color that is *not* on move now.
17056 */
17057 void
17058 SwitchClocks (int newMoveNr)
17059 {
17060     long lastTickLength;
17061     TimeMark now;
17062     int flagged = FALSE;
17063
17064     GetTimeMark(&now);
17065
17066     if (StopClockTimer() && appData.clockMode) {
17067         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17068         if (!WhiteOnMove(forwardMostMove)) {
17069             if(blackNPS >= 0) lastTickLength = 0;
17070             blackTimeRemaining -= lastTickLength;
17071            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17072 //         if(pvInfoList[forwardMostMove].time == -1)
17073                  pvInfoList[forwardMostMove].time =               // use GUI time
17074                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17075         } else {
17076            if(whiteNPS >= 0) lastTickLength = 0;
17077            whiteTimeRemaining -= lastTickLength;
17078            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17079 //         if(pvInfoList[forwardMostMove].time == -1)
17080                  pvInfoList[forwardMostMove].time =
17081                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17082         }
17083         flagged = CheckFlags();
17084     }
17085     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17086     CheckTimeControl();
17087
17088     if (flagged || !appData.clockMode) return;
17089
17090     switch (gameMode) {
17091       case MachinePlaysBlack:
17092       case MachinePlaysWhite:
17093       case BeginningOfGame:
17094         if (pausing) return;
17095         break;
17096
17097       case EditGame:
17098       case PlayFromGameFile:
17099       case IcsExamining:
17100         return;
17101
17102       default:
17103         break;
17104     }
17105
17106     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17107         if(WhiteOnMove(forwardMostMove))
17108              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17109         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17110     }
17111
17112     tickStartTM = now;
17113     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17114       whiteTimeRemaining : blackTimeRemaining);
17115     StartClockTimer(intendedTickLength);
17116 }
17117
17118
17119 /* Stop both clocks */
17120 void
17121 StopClocks ()
17122 {
17123     long lastTickLength;
17124     TimeMark now;
17125
17126     if (!StopClockTimer()) return;
17127     if (!appData.clockMode) return;
17128
17129     GetTimeMark(&now);
17130
17131     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17132     if (WhiteOnMove(forwardMostMove)) {
17133         if(whiteNPS >= 0) lastTickLength = 0;
17134         whiteTimeRemaining -= lastTickLength;
17135         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17136     } else {
17137         if(blackNPS >= 0) lastTickLength = 0;
17138         blackTimeRemaining -= lastTickLength;
17139         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17140     }
17141     CheckFlags();
17142 }
17143
17144 /* Start clock of player on move.  Time may have been reset, so
17145    if clock is already running, stop and restart it. */
17146 void
17147 StartClocks ()
17148 {
17149     (void) StopClockTimer(); /* in case it was running already */
17150     DisplayBothClocks();
17151     if (CheckFlags()) return;
17152
17153     if (!appData.clockMode) return;
17154     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17155
17156     GetTimeMark(&tickStartTM);
17157     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17158       whiteTimeRemaining : blackTimeRemaining);
17159
17160    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17161     whiteNPS = blackNPS = -1;
17162     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17163        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17164         whiteNPS = first.nps;
17165     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17166        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17167         blackNPS = first.nps;
17168     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17169         whiteNPS = second.nps;
17170     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17171         blackNPS = second.nps;
17172     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17173
17174     StartClockTimer(intendedTickLength);
17175 }
17176
17177 char *
17178 TimeString (long ms)
17179 {
17180     long second, minute, hour, day;
17181     char *sign = "";
17182     static char buf[32];
17183
17184     if (ms > 0 && ms <= 9900) {
17185       /* convert milliseconds to tenths, rounding up */
17186       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17187
17188       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17189       return buf;
17190     }
17191
17192     /* convert milliseconds to seconds, rounding up */
17193     /* use floating point to avoid strangeness of integer division
17194        with negative dividends on many machines */
17195     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17196
17197     if (second < 0) {
17198         sign = "-";
17199         second = -second;
17200     }
17201
17202     day = second / (60 * 60 * 24);
17203     second = second % (60 * 60 * 24);
17204     hour = second / (60 * 60);
17205     second = second % (60 * 60);
17206     minute = second / 60;
17207     second = second % 60;
17208
17209     if (day > 0)
17210       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17211               sign, day, hour, minute, second);
17212     else if (hour > 0)
17213       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17214     else
17215       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17216
17217     return buf;
17218 }
17219
17220
17221 /*
17222  * This is necessary because some C libraries aren't ANSI C compliant yet.
17223  */
17224 char *
17225 StrStr (char *string, char *match)
17226 {
17227     int i, length;
17228
17229     length = strlen(match);
17230
17231     for (i = strlen(string) - length; i >= 0; i--, string++)
17232       if (!strncmp(match, string, length))
17233         return string;
17234
17235     return NULL;
17236 }
17237
17238 char *
17239 StrCaseStr (char *string, char *match)
17240 {
17241     int i, j, length;
17242
17243     length = strlen(match);
17244
17245     for (i = strlen(string) - length; i >= 0; i--, string++) {
17246         for (j = 0; j < length; j++) {
17247             if (ToLower(match[j]) != ToLower(string[j]))
17248               break;
17249         }
17250         if (j == length) return string;
17251     }
17252
17253     return NULL;
17254 }
17255
17256 #ifndef _amigados
17257 int
17258 StrCaseCmp (char *s1, char *s2)
17259 {
17260     char c1, c2;
17261
17262     for (;;) {
17263         c1 = ToLower(*s1++);
17264         c2 = ToLower(*s2++);
17265         if (c1 > c2) return 1;
17266         if (c1 < c2) return -1;
17267         if (c1 == NULLCHAR) return 0;
17268     }
17269 }
17270
17271
17272 int
17273 ToLower (int c)
17274 {
17275     return isupper(c) ? tolower(c) : c;
17276 }
17277
17278
17279 int
17280 ToUpper (int c)
17281 {
17282     return islower(c) ? toupper(c) : c;
17283 }
17284 #endif /* !_amigados    */
17285
17286 char *
17287 StrSave (char *s)
17288 {
17289   char *ret;
17290
17291   if ((ret = (char *) malloc(strlen(s) + 1)))
17292     {
17293       safeStrCpy(ret, s, strlen(s)+1);
17294     }
17295   return ret;
17296 }
17297
17298 char *
17299 StrSavePtr (char *s, char **savePtr)
17300 {
17301     if (*savePtr) {
17302         free(*savePtr);
17303     }
17304     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17305       safeStrCpy(*savePtr, s, strlen(s)+1);
17306     }
17307     return(*savePtr);
17308 }
17309
17310 char *
17311 PGNDate ()
17312 {
17313     time_t clock;
17314     struct tm *tm;
17315     char buf[MSG_SIZ];
17316
17317     clock = time((time_t *)NULL);
17318     tm = localtime(&clock);
17319     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17320             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17321     return StrSave(buf);
17322 }
17323
17324
17325 char *
17326 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17327 {
17328     int i, j, fromX, fromY, toX, toY;
17329     int whiteToPlay;
17330     char buf[MSG_SIZ];
17331     char *p, *q;
17332     int emptycount;
17333     ChessSquare piece;
17334
17335     whiteToPlay = (gameMode == EditPosition) ?
17336       !blackPlaysFirst : (move % 2 == 0);
17337     p = buf;
17338
17339     /* Piece placement data */
17340     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17341         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17342         emptycount = 0;
17343         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17344             if (boards[move][i][j] == EmptySquare) {
17345                 emptycount++;
17346             } else { ChessSquare piece = boards[move][i][j];
17347                 if (emptycount > 0) {
17348                     if(emptycount<10) /* [HGM] can be >= 10 */
17349                         *p++ = '0' + emptycount;
17350                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17351                     emptycount = 0;
17352                 }
17353                 if(PieceToChar(piece) == '+') {
17354                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17355                     *p++ = '+';
17356                     piece = (ChessSquare)(DEMOTED piece);
17357                 }
17358                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17359                 if(p[-1] == '~') {
17360                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17361                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17362                     *p++ = '~';
17363                 }
17364             }
17365         }
17366         if (emptycount > 0) {
17367             if(emptycount<10) /* [HGM] can be >= 10 */
17368                 *p++ = '0' + emptycount;
17369             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17370             emptycount = 0;
17371         }
17372         *p++ = '/';
17373     }
17374     *(p - 1) = ' ';
17375
17376     /* [HGM] print Crazyhouse or Shogi holdings */
17377     if( gameInfo.holdingsWidth ) {
17378         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17379         q = p;
17380         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17381             piece = boards[move][i][BOARD_WIDTH-1];
17382             if( piece != EmptySquare )
17383               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17384                   *p++ = PieceToChar(piece);
17385         }
17386         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17387             piece = boards[move][BOARD_HEIGHT-i-1][0];
17388             if( piece != EmptySquare )
17389               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17390                   *p++ = PieceToChar(piece);
17391         }
17392
17393         if( q == p ) *p++ = '-';
17394         *p++ = ']';
17395         *p++ = ' ';
17396     }
17397
17398     /* Active color */
17399     *p++ = whiteToPlay ? 'w' : 'b';
17400     *p++ = ' ';
17401
17402   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17403     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17404   } else {
17405   if(nrCastlingRights) {
17406      q = p;
17407      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17408        /* [HGM] write directly from rights */
17409            if(boards[move][CASTLING][2] != NoRights &&
17410               boards[move][CASTLING][0] != NoRights   )
17411                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17412            if(boards[move][CASTLING][2] != NoRights &&
17413               boards[move][CASTLING][1] != NoRights   )
17414                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17415            if(boards[move][CASTLING][5] != NoRights &&
17416               boards[move][CASTLING][3] != NoRights   )
17417                 *p++ = boards[move][CASTLING][3] + AAA;
17418            if(boards[move][CASTLING][5] != NoRights &&
17419               boards[move][CASTLING][4] != NoRights   )
17420                 *p++ = boards[move][CASTLING][4] + AAA;
17421      } else {
17422
17423         /* [HGM] write true castling rights */
17424         if( nrCastlingRights == 6 ) {
17425             int q, k=0;
17426             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17427                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17428             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17429                  boards[move][CASTLING][2] != NoRights  );
17430             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17431                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17432                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17433                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17434                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17435             }
17436             if(q) *p++ = 'Q';
17437             k = 0;
17438             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17439                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17440             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17441                  boards[move][CASTLING][5] != NoRights  );
17442             if(gameInfo.variant == VariantSChess) {
17443                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17444                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17445                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17446                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17447             }
17448             if(q) *p++ = 'q';
17449         }
17450      }
17451      if (q == p) *p++ = '-'; /* No castling rights */
17452      *p++ = ' ';
17453   }
17454
17455   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17456      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17457      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17458     /* En passant target square */
17459     if (move > backwardMostMove) {
17460         fromX = moveList[move - 1][0] - AAA;
17461         fromY = moveList[move - 1][1] - ONE;
17462         toX = moveList[move - 1][2] - AAA;
17463         toY = moveList[move - 1][3] - ONE;
17464         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17465             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17466             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17467             fromX == toX) {
17468             /* 2-square pawn move just happened */
17469             *p++ = toX + AAA;
17470             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17471         } else {
17472             *p++ = '-';
17473         }
17474     } else if(move == backwardMostMove) {
17475         // [HGM] perhaps we should always do it like this, and forget the above?
17476         if((signed char)boards[move][EP_STATUS] >= 0) {
17477             *p++ = boards[move][EP_STATUS] + AAA;
17478             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17479         } else {
17480             *p++ = '-';
17481         }
17482     } else {
17483         *p++ = '-';
17484     }
17485     *p++ = ' ';
17486   }
17487   }
17488
17489     if(moveCounts)
17490     {   int i = 0, j=move;
17491
17492         /* [HGM] find reversible plies */
17493         if (appData.debugMode) { int k;
17494             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17495             for(k=backwardMostMove; k<=forwardMostMove; k++)
17496                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17497
17498         }
17499
17500         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17501         if( j == backwardMostMove ) i += initialRulePlies;
17502         sprintf(p, "%d ", i);
17503         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17504
17505         /* Fullmove number */
17506         sprintf(p, "%d", (move / 2) + 1);
17507     } else *--p = NULLCHAR;
17508
17509     return StrSave(buf);
17510 }
17511
17512 Boolean
17513 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17514 {
17515     int i, j, k, w=0;
17516     char *p, c;
17517     int emptycount, virgin[BOARD_FILES];
17518     ChessSquare piece;
17519
17520     p = fen;
17521
17522     /* Piece placement data */
17523     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17524         j = 0;
17525         for (;;) {
17526             if (*p == '/' || *p == ' ' || *p == '[' ) {
17527                 if(j > w) w = j;
17528                 emptycount = gameInfo.boardWidth - j;
17529                 while (emptycount--)
17530                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17531                 if (*p == '/') p++;
17532                 else if(autoSize) { // we stumbled unexpectedly into end of board
17533                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17534                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17535                     }
17536                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17537                 }
17538                 break;
17539 #if(BOARD_FILES >= 10)
17540             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17541                 p++; emptycount=10;
17542                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17543                 while (emptycount--)
17544                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17545 #endif
17546             } else if (*p == '*') {
17547                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17548             } else if (isdigit(*p)) {
17549                 emptycount = *p++ - '0';
17550                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17551                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17552                 while (emptycount--)
17553                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17554             } else if (*p == '+' || isalpha(*p)) {
17555                 if (j >= gameInfo.boardWidth) return FALSE;
17556                 if(*p=='+') {
17557                     piece = CharToPiece(*++p);
17558                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17559                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17560                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17561                 } else piece = CharToPiece(*p++);
17562
17563                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17564                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17565                     piece = (ChessSquare) (PROMOTED piece);
17566                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17567                     p++;
17568                 }
17569                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17570             } else {
17571                 return FALSE;
17572             }
17573         }
17574     }
17575     while (*p == '/' || *p == ' ') p++;
17576
17577     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17578
17579     /* [HGM] by default clear Crazyhouse holdings, if present */
17580     if(gameInfo.holdingsWidth) {
17581        for(i=0; i<BOARD_HEIGHT; i++) {
17582            board[i][0]             = EmptySquare; /* black holdings */
17583            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17584            board[i][1]             = (ChessSquare) 0; /* black counts */
17585            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17586        }
17587     }
17588
17589     /* [HGM] look for Crazyhouse holdings here */
17590     while(*p==' ') p++;
17591     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17592         if(*p == '[') p++;
17593         if(*p == '-' ) p++; /* empty holdings */ else {
17594             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17595             /* if we would allow FEN reading to set board size, we would   */
17596             /* have to add holdings and shift the board read so far here   */
17597             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17598                 p++;
17599                 if((int) piece >= (int) BlackPawn ) {
17600                     i = (int)piece - (int)BlackPawn;
17601                     i = PieceToNumber((ChessSquare)i);
17602                     if( i >= gameInfo.holdingsSize ) return FALSE;
17603                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17604                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17605                 } else {
17606                     i = (int)piece - (int)WhitePawn;
17607                     i = PieceToNumber((ChessSquare)i);
17608                     if( i >= gameInfo.holdingsSize ) return FALSE;
17609                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17610                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17611                 }
17612             }
17613         }
17614         if(*p == ']') p++;
17615     }
17616
17617     while(*p == ' ') p++;
17618
17619     /* Active color */
17620     c = *p++;
17621     if(appData.colorNickNames) {
17622       if( c == appData.colorNickNames[0] ) c = 'w'; else
17623       if( c == appData.colorNickNames[1] ) c = 'b';
17624     }
17625     switch (c) {
17626       case 'w':
17627         *blackPlaysFirst = FALSE;
17628         break;
17629       case 'b':
17630         *blackPlaysFirst = TRUE;
17631         break;
17632       default:
17633         return FALSE;
17634     }
17635
17636     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17637     /* return the extra info in global variiables             */
17638
17639     /* set defaults in case FEN is incomplete */
17640     board[EP_STATUS] = EP_UNKNOWN;
17641     for(i=0; i<nrCastlingRights; i++ ) {
17642         board[CASTLING][i] =
17643             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17644     }   /* assume possible unless obviously impossible */
17645     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17646     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17647     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17648                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17649     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17650     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17651     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17652                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17653     FENrulePlies = 0;
17654
17655     while(*p==' ') p++;
17656     if(nrCastlingRights) {
17657       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17658       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17659           /* castling indicator present, so default becomes no castlings */
17660           for(i=0; i<nrCastlingRights; i++ ) {
17661                  board[CASTLING][i] = NoRights;
17662           }
17663       }
17664       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17665              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17666              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17667              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17668         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17669
17670         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17671             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17672             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17673         }
17674         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17675             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17676         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17677                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17678         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17679                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17680         switch(c) {
17681           case'K':
17682               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17683               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17684               board[CASTLING][2] = whiteKingFile;
17685               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17686               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17687               break;
17688           case'Q':
17689               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17690               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17691               board[CASTLING][2] = whiteKingFile;
17692               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17693               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17694               break;
17695           case'k':
17696               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17697               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17698               board[CASTLING][5] = blackKingFile;
17699               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17700               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17701               break;
17702           case'q':
17703               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17704               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17705               board[CASTLING][5] = blackKingFile;
17706               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17707               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17708           case '-':
17709               break;
17710           default: /* FRC castlings */
17711               if(c >= 'a') { /* black rights */
17712                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17713                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17714                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17715                   if(i == BOARD_RGHT) break;
17716                   board[CASTLING][5] = i;
17717                   c -= AAA;
17718                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17719                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17720                   if(c > i)
17721                       board[CASTLING][3] = c;
17722                   else
17723                       board[CASTLING][4] = c;
17724               } else { /* white rights */
17725                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17726                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17727                     if(board[0][i] == WhiteKing) break;
17728                   if(i == BOARD_RGHT) break;
17729                   board[CASTLING][2] = i;
17730                   c -= AAA - 'a' + 'A';
17731                   if(board[0][c] >= WhiteKing) break;
17732                   if(c > i)
17733                       board[CASTLING][0] = c;
17734                   else
17735                       board[CASTLING][1] = c;
17736               }
17737         }
17738       }
17739       for(i=0; i<nrCastlingRights; i++)
17740         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17741       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17742     if (appData.debugMode) {
17743         fprintf(debugFP, "FEN castling rights:");
17744         for(i=0; i<nrCastlingRights; i++)
17745         fprintf(debugFP, " %d", board[CASTLING][i]);
17746         fprintf(debugFP, "\n");
17747     }
17748
17749       while(*p==' ') p++;
17750     }
17751
17752     /* read e.p. field in games that know e.p. capture */
17753     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17754        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17755        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17756       if(*p=='-') {
17757         p++; board[EP_STATUS] = EP_NONE;
17758       } else {
17759          char c = *p++ - AAA;
17760
17761          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17762          if(*p >= '0' && *p <='9') p++;
17763          board[EP_STATUS] = c;
17764       }
17765     }
17766
17767
17768     if(sscanf(p, "%d", &i) == 1) {
17769         FENrulePlies = i; /* 50-move ply counter */
17770         /* (The move number is still ignored)    */
17771     }
17772
17773     return TRUE;
17774 }
17775
17776 void
17777 EditPositionPasteFEN (char *fen)
17778 {
17779   if (fen != NULL) {
17780     Board initial_position;
17781
17782     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17783       DisplayError(_("Bad FEN position in clipboard"), 0);
17784       return ;
17785     } else {
17786       int savedBlackPlaysFirst = blackPlaysFirst;
17787       EditPositionEvent();
17788       blackPlaysFirst = savedBlackPlaysFirst;
17789       CopyBoard(boards[0], initial_position);
17790       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17791       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17792       DisplayBothClocks();
17793       DrawPosition(FALSE, boards[currentMove]);
17794     }
17795   }
17796 }
17797
17798 static char cseq[12] = "\\   ";
17799
17800 Boolean
17801 set_cont_sequence (char *new_seq)
17802 {
17803     int len;
17804     Boolean ret;
17805
17806     // handle bad attempts to set the sequence
17807         if (!new_seq)
17808                 return 0; // acceptable error - no debug
17809
17810     len = strlen(new_seq);
17811     ret = (len > 0) && (len < sizeof(cseq));
17812     if (ret)
17813       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17814     else if (appData.debugMode)
17815       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17816     return ret;
17817 }
17818
17819 /*
17820     reformat a source message so words don't cross the width boundary.  internal
17821     newlines are not removed.  returns the wrapped size (no null character unless
17822     included in source message).  If dest is NULL, only calculate the size required
17823     for the dest buffer.  lp argument indicats line position upon entry, and it's
17824     passed back upon exit.
17825 */
17826 int
17827 wrap (char *dest, char *src, int count, int width, int *lp)
17828 {
17829     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17830
17831     cseq_len = strlen(cseq);
17832     old_line = line = *lp;
17833     ansi = len = clen = 0;
17834
17835     for (i=0; i < count; i++)
17836     {
17837         if (src[i] == '\033')
17838             ansi = 1;
17839
17840         // if we hit the width, back up
17841         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17842         {
17843             // store i & len in case the word is too long
17844             old_i = i, old_len = len;
17845
17846             // find the end of the last word
17847             while (i && src[i] != ' ' && src[i] != '\n')
17848             {
17849                 i--;
17850                 len--;
17851             }
17852
17853             // word too long?  restore i & len before splitting it
17854             if ((old_i-i+clen) >= width)
17855             {
17856                 i = old_i;
17857                 len = old_len;
17858             }
17859
17860             // extra space?
17861             if (i && src[i-1] == ' ')
17862                 len--;
17863
17864             if (src[i] != ' ' && src[i] != '\n')
17865             {
17866                 i--;
17867                 if (len)
17868                     len--;
17869             }
17870
17871             // now append the newline and continuation sequence
17872             if (dest)
17873                 dest[len] = '\n';
17874             len++;
17875             if (dest)
17876                 strncpy(dest+len, cseq, cseq_len);
17877             len += cseq_len;
17878             line = cseq_len;
17879             clen = cseq_len;
17880             continue;
17881         }
17882
17883         if (dest)
17884             dest[len] = src[i];
17885         len++;
17886         if (!ansi)
17887             line++;
17888         if (src[i] == '\n')
17889             line = 0;
17890         if (src[i] == 'm')
17891             ansi = 0;
17892     }
17893     if (dest && appData.debugMode)
17894     {
17895         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17896             count, width, line, len, *lp);
17897         show_bytes(debugFP, src, count);
17898         fprintf(debugFP, "\ndest: ");
17899         show_bytes(debugFP, dest, len);
17900         fprintf(debugFP, "\n");
17901     }
17902     *lp = dest ? line : old_line;
17903
17904     return len;
17905 }
17906
17907 // [HGM] vari: routines for shelving variations
17908 Boolean modeRestore = FALSE;
17909
17910 void
17911 PushInner (int firstMove, int lastMove)
17912 {
17913         int i, j, nrMoves = lastMove - firstMove;
17914
17915         // push current tail of game on stack
17916         savedResult[storedGames] = gameInfo.result;
17917         savedDetails[storedGames] = gameInfo.resultDetails;
17918         gameInfo.resultDetails = NULL;
17919         savedFirst[storedGames] = firstMove;
17920         savedLast [storedGames] = lastMove;
17921         savedFramePtr[storedGames] = framePtr;
17922         framePtr -= nrMoves; // reserve space for the boards
17923         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17924             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17925             for(j=0; j<MOVE_LEN; j++)
17926                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17927             for(j=0; j<2*MOVE_LEN; j++)
17928                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17929             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17930             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17931             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17932             pvInfoList[firstMove+i-1].depth = 0;
17933             commentList[framePtr+i] = commentList[firstMove+i];
17934             commentList[firstMove+i] = NULL;
17935         }
17936
17937         storedGames++;
17938         forwardMostMove = firstMove; // truncate game so we can start variation
17939 }
17940
17941 void
17942 PushTail (int firstMove, int lastMove)
17943 {
17944         if(appData.icsActive) { // only in local mode
17945                 forwardMostMove = currentMove; // mimic old ICS behavior
17946                 return;
17947         }
17948         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17949
17950         PushInner(firstMove, lastMove);
17951         if(storedGames == 1) GreyRevert(FALSE);
17952         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17953 }
17954
17955 void
17956 PopInner (Boolean annotate)
17957 {
17958         int i, j, nrMoves;
17959         char buf[8000], moveBuf[20];
17960
17961         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17962         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17963         nrMoves = savedLast[storedGames] - currentMove;
17964         if(annotate) {
17965                 int cnt = 10;
17966                 if(!WhiteOnMove(currentMove))
17967                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17968                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17969                 for(i=currentMove; i<forwardMostMove; i++) {
17970                         if(WhiteOnMove(i))
17971                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17972                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17973                         strcat(buf, moveBuf);
17974                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17975                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17976                 }
17977                 strcat(buf, ")");
17978         }
17979         for(i=1; i<=nrMoves; i++) { // copy last variation back
17980             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17981             for(j=0; j<MOVE_LEN; j++)
17982                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17983             for(j=0; j<2*MOVE_LEN; j++)
17984                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17985             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17986             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17987             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17988             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17989             commentList[currentMove+i] = commentList[framePtr+i];
17990             commentList[framePtr+i] = NULL;
17991         }
17992         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17993         framePtr = savedFramePtr[storedGames];
17994         gameInfo.result = savedResult[storedGames];
17995         if(gameInfo.resultDetails != NULL) {
17996             free(gameInfo.resultDetails);
17997       }
17998         gameInfo.resultDetails = savedDetails[storedGames];
17999         forwardMostMove = currentMove + nrMoves;
18000 }
18001
18002 Boolean
18003 PopTail (Boolean annotate)
18004 {
18005         if(appData.icsActive) return FALSE; // only in local mode
18006         if(!storedGames) return FALSE; // sanity
18007         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18008
18009         PopInner(annotate);
18010         if(currentMove < forwardMostMove) ForwardEvent(); else
18011         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18012
18013         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18014         return TRUE;
18015 }
18016
18017 void
18018 CleanupTail ()
18019 {       // remove all shelved variations
18020         int i;
18021         for(i=0; i<storedGames; i++) {
18022             if(savedDetails[i])
18023                 free(savedDetails[i]);
18024             savedDetails[i] = NULL;
18025         }
18026         for(i=framePtr; i<MAX_MOVES; i++) {
18027                 if(commentList[i]) free(commentList[i]);
18028                 commentList[i] = NULL;
18029         }
18030         framePtr = MAX_MOVES-1;
18031         storedGames = 0;
18032 }
18033
18034 void
18035 LoadVariation (int index, char *text)
18036 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18037         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18038         int level = 0, move;
18039
18040         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18041         // first find outermost bracketing variation
18042         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18043             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18044                 if(*p == '{') wait = '}'; else
18045                 if(*p == '[') wait = ']'; else
18046                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18047                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18048             }
18049             if(*p == wait) wait = NULLCHAR; // closing ]} found
18050             p++;
18051         }
18052         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18053         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18054         end[1] = NULLCHAR; // clip off comment beyond variation
18055         ToNrEvent(currentMove-1);
18056         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18057         // kludge: use ParsePV() to append variation to game
18058         move = currentMove;
18059         ParsePV(start, TRUE, TRUE);
18060         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18061         ClearPremoveHighlights();
18062         CommentPopDown();
18063         ToNrEvent(currentMove+1);
18064 }
18065
18066 void
18067 LoadTheme ()
18068 {
18069     char *p, *q, buf[MSG_SIZ];
18070     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18071         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18072         ParseArgsFromString(buf);
18073         ActivateTheme(TRUE); // also redo colors
18074         return;
18075     }
18076     p = nickName;
18077     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18078     {
18079         int len;
18080         q = appData.themeNames;
18081         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18082       if(appData.useBitmaps) {
18083         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18084                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18085                 appData.liteBackTextureMode,
18086                 appData.darkBackTextureMode );
18087       } else {
18088         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18089                 Col2Text(2),   // lightSquareColor
18090                 Col2Text(3) ); // darkSquareColor
18091       }
18092       if(appData.useBorder) {
18093         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18094                 appData.border);
18095       } else {
18096         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18097       }
18098       if(appData.useFont) {
18099         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18100                 appData.renderPiecesWithFont,
18101                 appData.fontToPieceTable,
18102                 Col2Text(9),    // appData.fontBackColorWhite
18103                 Col2Text(10) ); // appData.fontForeColorBlack
18104       } else {
18105         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18106                 appData.pieceDirectory);
18107         if(!appData.pieceDirectory[0])
18108           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18109                 Col2Text(0),   // whitePieceColor
18110                 Col2Text(1) ); // blackPieceColor
18111       }
18112       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18113                 Col2Text(4),   // highlightSquareColor
18114                 Col2Text(5) ); // premoveHighlightColor
18115         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18116         if(insert != q) insert[-1] = NULLCHAR;
18117         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18118         if(q)   free(q);
18119     }
18120     ActivateTheme(FALSE);
18121 }