6675344b945a1c61985e18cda44bf6bfb306a794
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569 ChessSquare  lionArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackLion, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackKnight, BlackRook }
574 };
575
576
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
583 };
584
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
590 };
591
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
597 };
598
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
604 };
605
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
611 };
612
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
618 };
619
620 #ifdef GOTHIC
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
626 };
627 #else // !GOTHIC
628 #define GothicArray CapablancaArray
629 #endif // !GOTHIC
630
631 #ifdef FALCON
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
637 };
638 #else // !FALCON
639 #define FalconArray CapablancaArray
640 #endif // !FALCON
641
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
648
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
655 };
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
669 };
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
674
675
676 Board initialPosition;
677
678
679 /* Convert str to a rating. Checks for special cases of "----",
680
681    "++++", etc. Also strips ()'s */
682 int
683 string_to_rating (char *str)
684 {
685   while(*str && !isdigit(*str)) ++str;
686   if (!*str)
687     return 0;   /* One of the special "no rating" cases */
688   else
689     return atoi(str);
690 }
691
692 void
693 ClearProgramStats ()
694 {
695     /* Init programStats */
696     programStats.movelist[0] = 0;
697     programStats.depth = 0;
698     programStats.nr_moves = 0;
699     programStats.moves_left = 0;
700     programStats.nodes = 0;
701     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
702     programStats.score = 0;
703     programStats.got_only_move = 0;
704     programStats.got_fail = 0;
705     programStats.line_is_book = 0;
706 }
707
708 void
709 CommonEngineInit ()
710 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711     if (appData.firstPlaysBlack) {
712         first.twoMachinesColor = "black\n";
713         second.twoMachinesColor = "white\n";
714     } else {
715         first.twoMachinesColor = "white\n";
716         second.twoMachinesColor = "black\n";
717     }
718
719     first.other = &second;
720     second.other = &first;
721
722     { float norm = 1;
723         if(appData.timeOddsMode) {
724             norm = appData.timeOdds[0];
725             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
726         }
727         first.timeOdds  = appData.timeOdds[0]/norm;
728         second.timeOdds = appData.timeOdds[1]/norm;
729     }
730
731     if(programVersion) free(programVersion);
732     if (appData.noChessProgram) {
733         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734         sprintf(programVersion, "%s", PACKAGE_STRING);
735     } else {
736       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
739     }
740 }
741
742 void
743 UnloadEngine (ChessProgramState *cps)
744 {
745         /* Kill off first chess program */
746         if (cps->isr != NULL)
747           RemoveInputSource(cps->isr);
748         cps->isr = NULL;
749
750         if (cps->pr != NoProc) {
751             ExitAnalyzeMode();
752             DoSleep( appData.delayBeforeQuit );
753             SendToProgram("quit\n", cps);
754             DoSleep( appData.delayAfterQuit );
755             DestroyChildProcess(cps->pr, cps->useSigterm);
756         }
757         cps->pr = NoProc;
758         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
759 }
760
761 void
762 ClearOptions (ChessProgramState *cps)
763 {
764     int i;
765     cps->nrOptions = cps->comboCnt = 0;
766     for(i=0; i<MAX_OPTIONS; i++) {
767         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768         cps->option[i].textValue = 0;
769     }
770 }
771
772 char *engineNames[] = {
773   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 N_("first"),
776   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
778 N_("second")
779 };
780
781 void
782 InitEngine (ChessProgramState *cps, int n)
783 {   // [HGM] all engine initialiation put in a function that does one engine
784
785     ClearOptions(cps);
786
787     cps->which = engineNames[n];
788     cps->maybeThinking = FALSE;
789     cps->pr = NoProc;
790     cps->isr = NULL;
791     cps->sendTime = 2;
792     cps->sendDrawOffers = 1;
793
794     cps->program = appData.chessProgram[n];
795     cps->host = appData.host[n];
796     cps->dir = appData.directory[n];
797     cps->initString = appData.engInitString[n];
798     cps->computerString = appData.computerString[n];
799     cps->useSigint  = TRUE;
800     cps->useSigterm = TRUE;
801     cps->reuse = appData.reuse[n];
802     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
803     cps->useSetboard = FALSE;
804     cps->useSAN = FALSE;
805     cps->usePing = FALSE;
806     cps->lastPing = 0;
807     cps->lastPong = 0;
808     cps->usePlayother = FALSE;
809     cps->useColors = TRUE;
810     cps->useUsermove = FALSE;
811     cps->sendICS = FALSE;
812     cps->sendName = appData.icsActive;
813     cps->sdKludge = FALSE;
814     cps->stKludge = FALSE;
815     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816     TidyProgramName(cps->program, cps->host, cps->tidy);
817     cps->matchWins = 0;
818     ASSIGN(cps->variants, appData.variant);
819     cps->analysisSupport = 2; /* detect */
820     cps->analyzing = FALSE;
821     cps->initDone = FALSE;
822     cps->reload = FALSE;
823
824     /* New features added by Tord: */
825     cps->useFEN960 = FALSE;
826     cps->useOOCastle = TRUE;
827     /* End of new features added by Tord. */
828     cps->fenOverride  = appData.fenOverride[n];
829
830     /* [HGM] time odds: set factor for each machine */
831     cps->timeOdds  = appData.timeOdds[n];
832
833     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834     cps->accumulateTC = appData.accumulateTC[n];
835     cps->maxNrOfSessions = 1;
836
837     /* [HGM] debug */
838     cps->debug = FALSE;
839
840     cps->supportsNPS = UNKNOWN;
841     cps->memSize = FALSE;
842     cps->maxCores = FALSE;
843     ASSIGN(cps->egtFormats, "");
844
845     /* [HGM] options */
846     cps->optionSettings  = appData.engOptions[n];
847
848     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849     cps->isUCI = appData.isUCI[n]; /* [AS] */
850     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
851     cps->highlight = 0;
852
853     if (appData.protocolVersion[n] > PROTOVER
854         || appData.protocolVersion[n] < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.protocolVersion[n]);
861         if( (len >= MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         cps->protocolVersion = appData.protocolVersion[n];
869       }
870
871     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
872     ParseFeatures(appData.featureDefaults, cps);
873 }
874
875 ChessProgramState *savCps;
876
877 GameMode oldMode;
878
879 void
880 LoadEngine ()
881 {
882     int i;
883     if(WaitForEngine(savCps, LoadEngine)) return;
884     CommonEngineInit(); // recalculate time odds
885     if(gameInfo.variant != StringToVariant(appData.variant)) {
886         // we changed variant when loading the engine; this forces us to reset
887         Reset(TRUE, savCps != &first);
888         oldMode = BeginningOfGame; // to prevent restoring old mode
889     }
890     InitChessProgram(savCps, FALSE);
891     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892     DisplayMessage("", "");
893     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
895     ThawUI();
896     SetGNUMode();
897     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
898 }
899
900 void
901 ReplaceEngine (ChessProgramState *cps, int n)
902 {
903     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
904     keepInfo = 1;
905     if(oldMode != BeginningOfGame) EditGameEvent();
906     keepInfo = 0;
907     UnloadEngine(cps);
908     appData.noChessProgram = FALSE;
909     appData.clockMode = TRUE;
910     InitEngine(cps, n);
911     UpdateLogos(TRUE);
912     if(n) return; // only startup first engine immediately; second can wait
913     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
914     LoadEngine();
915 }
916
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
919
920 static char resetOptions[] =
921         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
925
926 void
927 FloatToFront(char **list, char *engineLine)
928 {
929     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
930     int i=0;
931     if(appData.recentEngines <= 0) return;
932     TidyProgramName(engineLine, "localhost", tidy+1);
933     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934     strncpy(buf+1, *list, MSG_SIZ-50);
935     if(p = strstr(buf, tidy)) { // tidy name appears in list
936         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937         while(*p++ = *++q); // squeeze out
938     }
939     strcat(tidy, buf+1); // put list behind tidy name
940     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942     ASSIGN(*list, tidy+1);
943 }
944
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
946
947 void
948 Load (ChessProgramState *cps, int i)
949 {
950     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956         appData.firstProtocolVersion = PROTOVER;
957         ParseArgsFromString(buf);
958         SwapEngines(i);
959         ReplaceEngine(cps, i);
960         FloatToFront(&appData.recentEngineList, engineLine);
961         return;
962     }
963     p = engineName;
964     while(q = strchr(p, SLASH)) p = q+1;
965     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966     if(engineDir[0] != NULLCHAR) {
967         ASSIGN(appData.directory[i], engineDir); p = engineName;
968     } else if(p != engineName) { // derive directory from engine path, when not given
969         p[-1] = 0;
970         ASSIGN(appData.directory[i], engineName);
971         p[-1] = SLASH;
972         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973     } else { ASSIGN(appData.directory[i], "."); }
974     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
975     if(params[0]) {
976         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977         snprintf(command, MSG_SIZ, "%s %s", p, params);
978         p = command;
979     }
980     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981     ASSIGN(appData.chessProgram[i], p);
982     appData.isUCI[i] = isUCI;
983     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984     appData.hasOwnBookUCI[i] = hasBook;
985     if(!nickName[0]) useNick = FALSE;
986     if(useNick) ASSIGN(appData.pgnName[i], nickName);
987     if(addToList) {
988         int len;
989         char quote;
990         q = firstChessProgramNames;
991         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994                         quote, p, quote, appData.directory[i],
995                         useNick ? " -fn \"" : "",
996                         useNick ? nickName : "",
997                         useNick ? "\"" : "",
998                         v1 ? " -firstProtocolVersion 1" : "",
999                         hasBook ? "" : " -fNoOwnBookUCI",
1000                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001                         storeVariant ? " -variant " : "",
1002                         storeVariant ? VariantName(gameInfo.variant) : "");
1003         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005         if(insert != q) insert[-1] = NULLCHAR;
1006         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1007         if(q)   free(q);
1008         FloatToFront(&appData.recentEngineList, buf);
1009     }
1010     ReplaceEngine(cps, i);
1011 }
1012
1013 void
1014 InitTimeControls ()
1015 {
1016     int matched, min, sec;
1017     /*
1018      * Parse timeControl resource
1019      */
1020     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021                           appData.movesPerSession)) {
1022         char buf[MSG_SIZ];
1023         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024         DisplayFatalError(buf, 0, 2);
1025     }
1026
1027     /*
1028      * Parse searchTime resource
1029      */
1030     if (*appData.searchTime != NULLCHAR) {
1031         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1032         if (matched == 1) {
1033             searchTime = min * 60;
1034         } else if (matched == 2) {
1035             searchTime = min * 60 + sec;
1036         } else {
1037             char buf[MSG_SIZ];
1038             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039             DisplayFatalError(buf, 0, 2);
1040         }
1041     }
1042 }
1043
1044 void
1045 InitBackEnd1 ()
1046 {
1047
1048     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1050
1051     GetTimeMark(&programStartTime);
1052     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053     appData.seedBase = random() + (random()<<15);
1054     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1055
1056     ClearProgramStats();
1057     programStats.ok_to_send = 1;
1058     programStats.seen_stat = 0;
1059
1060     /*
1061      * Initialize game list
1062      */
1063     ListNew(&gameList);
1064
1065
1066     /*
1067      * Internet chess server status
1068      */
1069     if (appData.icsActive) {
1070         appData.matchMode = FALSE;
1071         appData.matchGames = 0;
1072 #if ZIPPY
1073         appData.noChessProgram = !appData.zippyPlay;
1074 #else
1075         appData.zippyPlay = FALSE;
1076         appData.zippyTalk = FALSE;
1077         appData.noChessProgram = TRUE;
1078 #endif
1079         if (*appData.icsHelper != NULLCHAR) {
1080             appData.useTelnet = TRUE;
1081             appData.telnetProgram = appData.icsHelper;
1082         }
1083     } else {
1084         appData.zippyTalk = appData.zippyPlay = FALSE;
1085     }
1086
1087     /* [AS] Initialize pv info list [HGM] and game state */
1088     {
1089         int i, j;
1090
1091         for( i=0; i<=framePtr; i++ ) {
1092             pvInfoList[i].depth = -1;
1093             boards[i][EP_STATUS] = EP_NONE;
1094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1095         }
1096     }
1097
1098     InitTimeControls();
1099
1100     /* [AS] Adjudication threshold */
1101     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1102
1103     InitEngine(&first, 0);
1104     InitEngine(&second, 1);
1105     CommonEngineInit();
1106
1107     pairing.which = "pairing"; // pairing engine
1108     pairing.pr = NoProc;
1109     pairing.isr = NULL;
1110     pairing.program = appData.pairingEngine;
1111     pairing.host = "localhost";
1112     pairing.dir = ".";
1113
1114     if (appData.icsActive) {
1115         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1116     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117         appData.clockMode = FALSE;
1118         first.sendTime = second.sendTime = 0;
1119     }
1120
1121 #if ZIPPY
1122     /* Override some settings from environment variables, for backward
1123        compatibility.  Unfortunately it's not feasible to have the env
1124        vars just set defaults, at least in xboard.  Ugh.
1125     */
1126     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1127       ZippyInit();
1128     }
1129 #endif
1130
1131     if (!appData.icsActive) {
1132       char buf[MSG_SIZ];
1133       int len;
1134
1135       /* Check for variants that are supported only in ICS mode,
1136          or not at all.  Some that are accepted here nevertheless
1137          have bugs; see comments below.
1138       */
1139       VariantClass variant = StringToVariant(appData.variant);
1140       switch (variant) {
1141       case VariantBughouse:     /* need four players and two boards */
1142       case VariantKriegspiel:   /* need to hide pieces and move details */
1143         /* case VariantFischeRandom: (Fabien: moved below) */
1144         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145         if( (len >= MSG_SIZ) && appData.debugMode )
1146           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1147
1148         DisplayFatalError(buf, 0, 2);
1149         return;
1150
1151       case VariantUnknown:
1152       case VariantLoadable:
1153       case Variant29:
1154       case Variant30:
1155       case Variant31:
1156       case Variant32:
1157       case Variant33:
1158       case Variant34:
1159       case Variant35:
1160       case Variant36:
1161       default:
1162         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163         if( (len >= MSG_SIZ) && appData.debugMode )
1164           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1165
1166         DisplayFatalError(buf, 0, 2);
1167         return;
1168
1169       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1170       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1171       case VariantGothic:     /* [HGM] should work */
1172       case VariantCapablanca: /* [HGM] should work */
1173       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1174       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1175       case VariantChu:        /* [HGM] experimental */
1176       case VariantKnightmate: /* [HGM] should work */
1177       case VariantCylinder:   /* [HGM] untested */
1178       case VariantFalcon:     /* [HGM] untested */
1179       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180                                  offboard interposition not understood */
1181       case VariantNormal:     /* definitely works! */
1182       case VariantWildCastle: /* pieces not automatically shuffled */
1183       case VariantNoCastle:   /* pieces not automatically shuffled */
1184       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185       case VariantLosers:     /* should work except for win condition,
1186                                  and doesn't know captures are mandatory */
1187       case VariantSuicide:    /* should work except for win condition,
1188                                  and doesn't know captures are mandatory */
1189       case VariantGiveaway:   /* should work except for win condition,
1190                                  and doesn't know captures are mandatory */
1191       case VariantTwoKings:   /* should work */
1192       case VariantAtomic:     /* should work except for win condition */
1193       case Variant3Check:     /* should work except for win condition */
1194       case VariantShatranj:   /* should work except for all win conditions */
1195       case VariantMakruk:     /* should work except for draw countdown */
1196       case VariantASEAN :     /* should work except for draw countdown */
1197       case VariantBerolina:   /* might work if TestLegality is off */
1198       case VariantCapaRandom: /* should work */
1199       case VariantJanus:      /* should work */
1200       case VariantSuper:      /* experimental */
1201       case VariantGreat:      /* experimental, requires legality testing to be off */
1202       case VariantSChess:     /* S-Chess, should work */
1203       case VariantGrand:      /* should work */
1204       case VariantSpartan:    /* should work */
1205       case VariantLion:       /* should work */
1206         break;
1207       }
1208     }
1209
1210 }
1211
1212 int
1213 NextIntegerFromString (char ** str, long * value)
1214 {
1215     int result = -1;
1216     char * s = *str;
1217
1218     while( *s == ' ' || *s == '\t' ) {
1219         s++;
1220     }
1221
1222     *value = 0;
1223
1224     if( *s >= '0' && *s <= '9' ) {
1225         while( *s >= '0' && *s <= '9' ) {
1226             *value = *value * 10 + (*s - '0');
1227             s++;
1228         }
1229
1230         result = 0;
1231     }
1232
1233     *str = s;
1234
1235     return result;
1236 }
1237
1238 int
1239 NextTimeControlFromString (char ** str, long * value)
1240 {
1241     long temp;
1242     int result = NextIntegerFromString( str, &temp );
1243
1244     if( result == 0 ) {
1245         *value = temp * 60; /* Minutes */
1246         if( **str == ':' ) {
1247             (*str)++;
1248             result = NextIntegerFromString( str, &temp );
1249             *value += temp; /* Seconds */
1250         }
1251     }
1252
1253     return result;
1254 }
1255
1256 int
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259     int result = -1, type = 0; long temp, temp2;
1260
1261     if(**str != ':') return -1; // old params remain in force!
1262     (*str)++;
1263     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264     if( NextIntegerFromString( str, &temp ) ) return -1;
1265     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1266
1267     if(**str != '/') {
1268         /* time only: incremental or sudden-death time control */
1269         if(**str == '+') { /* increment follows; read it */
1270             (*str)++;
1271             if(**str == '!') type = *(*str)++; // Bronstein TC
1272             if(result = NextIntegerFromString( str, &temp2)) return -1;
1273             *inc = temp2 * 1000;
1274             if(**str == '.') { // read fraction of increment
1275                 char *start = ++(*str);
1276                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1277                 temp2 *= 1000;
1278                 while(start++ < *str) temp2 /= 10;
1279                 *inc += temp2;
1280             }
1281         } else *inc = 0;
1282         *moves = 0; *tc = temp * 1000; *incType = type;
1283         return 0;
1284     }
1285
1286     (*str)++; /* classical time control */
1287     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1288
1289     if(result == 0) {
1290         *moves = temp;
1291         *tc    = temp2 * 1000;
1292         *inc   = 0;
1293         *incType = type;
1294     }
1295     return result;
1296 }
1297
1298 int
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 {   /* [HGM] get time to add from the multi-session time-control string */
1301     int incType, moves=1; /* kludge to force reading of first session */
1302     long time, increment;
1303     char *s = tcString;
1304
1305     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1306     do {
1307         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309         if(movenr == -1) return time;    /* last move before new session     */
1310         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312         if(!moves) return increment;     /* current session is incremental   */
1313         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314     } while(movenr >= -1);               /* try again for next session       */
1315
1316     return 0; // no new time quota on this move
1317 }
1318
1319 int
1320 ParseTimeControl (char *tc, float ti, int mps)
1321 {
1322   long tc1;
1323   long tc2;
1324   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1325   int min, sec=0;
1326
1327   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1330   if(ti > 0) {
1331
1332     if(mps)
1333       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1334     else
1335       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1336   } else {
1337     if(mps)
1338       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1339     else
1340       snprintf(buf, MSG_SIZ, ":%s", mytc);
1341   }
1342   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1343
1344   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1345     return FALSE;
1346   }
1347
1348   if( *tc == '/' ) {
1349     /* Parse second time control */
1350     tc++;
1351
1352     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1353       return FALSE;
1354     }
1355
1356     if( tc2 == 0 ) {
1357       return FALSE;
1358     }
1359
1360     timeControl_2 = tc2 * 1000;
1361   }
1362   else {
1363     timeControl_2 = 0;
1364   }
1365
1366   if( tc1 == 0 ) {
1367     return FALSE;
1368   }
1369
1370   timeControl = tc1 * 1000;
1371
1372   if (ti >= 0) {
1373     timeIncrement = ti * 1000;  /* convert to ms */
1374     movesPerSession = 0;
1375   } else {
1376     timeIncrement = 0;
1377     movesPerSession = mps;
1378   }
1379   return TRUE;
1380 }
1381
1382 void
1383 InitBackEnd2 ()
1384 {
1385     if (appData.debugMode) {
1386 #    ifdef __GIT_VERSION
1387       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1388 #    else
1389       fprintf(debugFP, "Version: %s\n", programVersion);
1390 #    endif
1391     }
1392     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1393
1394     set_cont_sequence(appData.wrapContSeq);
1395     if (appData.matchGames > 0) {
1396         appData.matchMode = TRUE;
1397     } else if (appData.matchMode) {
1398         appData.matchGames = 1;
1399     }
1400     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401         appData.matchGames = appData.sameColorGames;
1402     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1405     }
1406     Reset(TRUE, FALSE);
1407     if (appData.noChessProgram || first.protocolVersion == 1) {
1408       InitBackEnd3();
1409     } else {
1410       /* kludge: allow timeout for initial "feature" commands */
1411       FreezeUI();
1412       DisplayMessage("", _("Starting chess program"));
1413       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1414     }
1415 }
1416
1417 int
1418 CalculateIndex (int index, int gameNr)
1419 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1420     int res;
1421     if(index > 0) return index; // fixed nmber
1422     if(index == 0) return 1;
1423     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1425     return res;
1426 }
1427
1428 int
1429 LoadGameOrPosition (int gameNr)
1430 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431     if (*appData.loadGameFile != NULLCHAR) {
1432         if (!LoadGameFromFile(appData.loadGameFile,
1433                 CalculateIndex(appData.loadGameIndex, gameNr),
1434                               appData.loadGameFile, FALSE)) {
1435             DisplayFatalError(_("Bad game file"), 0, 1);
1436             return 0;
1437         }
1438     } else if (*appData.loadPositionFile != NULLCHAR) {
1439         if (!LoadPositionFromFile(appData.loadPositionFile,
1440                 CalculateIndex(appData.loadPositionIndex, gameNr),
1441                                   appData.loadPositionFile)) {
1442             DisplayFatalError(_("Bad position file"), 0, 1);
1443             return 0;
1444         }
1445     }
1446     return 1;
1447 }
1448
1449 void
1450 ReserveGame (int gameNr, char resChar)
1451 {
1452     FILE *tf = fopen(appData.tourneyFile, "r+");
1453     char *p, *q, c, buf[MSG_SIZ];
1454     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455     safeStrCpy(buf, lastMsg, MSG_SIZ);
1456     DisplayMessage(_("Pick new game"), "");
1457     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458     ParseArgsFromFile(tf);
1459     p = q = appData.results;
1460     if(appData.debugMode) {
1461       char *r = appData.participants;
1462       fprintf(debugFP, "results = '%s'\n", p);
1463       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464       fprintf(debugFP, "\n");
1465     }
1466     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1467     nextGame = q - p;
1468     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469     safeStrCpy(q, p, strlen(p) + 2);
1470     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1474         q[nextGame] = '*';
1475     }
1476     fseek(tf, -(strlen(p)+4), SEEK_END);
1477     c = fgetc(tf);
1478     if(c != '"') // depending on DOS or Unix line endings we can be one off
1479          fseek(tf, -(strlen(p)+2), SEEK_END);
1480     else fseek(tf, -(strlen(p)+3), SEEK_END);
1481     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482     DisplayMessage(buf, "");
1483     free(p); appData.results = q;
1484     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486       int round = appData.defaultMatchGames * appData.tourneyType;
1487       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1488          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489         UnloadEngine(&first);  // next game belongs to other pairing;
1490         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1491     }
1492     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1493 }
1494
1495 void
1496 MatchEvent (int mode)
1497 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1498         int dummy;
1499         if(matchMode) { // already in match mode: switch it off
1500             abortMatch = TRUE;
1501             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1502             return;
1503         }
1504 //      if(gameMode != BeginningOfGame) {
1505 //          DisplayError(_("You can only start a match from the initial position."), 0);
1506 //          return;
1507 //      }
1508         abortMatch = FALSE;
1509         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510         /* Set up machine vs. machine match */
1511         nextGame = 0;
1512         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513         if(appData.tourneyFile[0]) {
1514             ReserveGame(-1, 0);
1515             if(nextGame > appData.matchGames) {
1516                 char buf[MSG_SIZ];
1517                 if(strchr(appData.results, '*') == NULL) {
1518                     FILE *f;
1519                     appData.tourneyCycles++;
1520                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1521                         fclose(f);
1522                         NextTourneyGame(-1, &dummy);
1523                         ReserveGame(-1, 0);
1524                         if(nextGame <= appData.matchGames) {
1525                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1526                             matchMode = mode;
1527                             ScheduleDelayedEvent(NextMatchGame, 10000);
1528                             return;
1529                         }
1530                     }
1531                 }
1532                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533                 DisplayError(buf, 0);
1534                 appData.tourneyFile[0] = 0;
1535                 return;
1536             }
1537         } else
1538         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1539             DisplayFatalError(_("Can't have a match with no chess programs"),
1540                               0, 2);
1541             return;
1542         }
1543         matchMode = mode;
1544         matchGame = roundNr = 1;
1545         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1546         NextMatchGame();
1547 }
1548
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1550
1551 void
1552 InitBackEnd3 P((void))
1553 {
1554     GameMode initialMode;
1555     char buf[MSG_SIZ];
1556     int err, len;
1557
1558     InitChessProgram(&first, startedFromSetupPosition);
1559
1560     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1561         free(programVersion);
1562         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1565     }
1566
1567     if (appData.icsActive) {
1568 #ifdef WIN32
1569         /* [DM] Make a console window if needed [HGM] merged ifs */
1570         ConsoleCreate();
1571 #endif
1572         err = establish();
1573         if (err != 0)
1574           {
1575             if (*appData.icsCommPort != NULLCHAR)
1576               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577                              appData.icsCommPort);
1578             else
1579               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580                         appData.icsHost, appData.icsPort);
1581
1582             if( (len >= MSG_SIZ) && appData.debugMode )
1583               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585             DisplayFatalError(buf, err, 1);
1586             return;
1587         }
1588         SetICSMode();
1589         telnetISR =
1590           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1591         fromUserISR =
1592           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595     } else if (appData.noChessProgram) {
1596         SetNCPMode();
1597     } else {
1598         SetGNUMode();
1599     }
1600
1601     if (*appData.cmailGameName != NULLCHAR) {
1602         SetCmailMode();
1603         OpenLoopback(&cmailPR);
1604         cmailISR =
1605           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1606     }
1607
1608     ThawUI();
1609     DisplayMessage("", "");
1610     if (StrCaseCmp(appData.initialMode, "") == 0) {
1611       initialMode = BeginningOfGame;
1612       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1616         ModeHighlight();
1617       }
1618     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619       initialMode = TwoMachinesPlay;
1620     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621       initialMode = AnalyzeFile;
1622     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623       initialMode = AnalyzeMode;
1624     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625       initialMode = MachinePlaysWhite;
1626     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627       initialMode = MachinePlaysBlack;
1628     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629       initialMode = EditGame;
1630     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631       initialMode = EditPosition;
1632     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633       initialMode = Training;
1634     } else {
1635       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636       if( (len >= MSG_SIZ) && appData.debugMode )
1637         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638
1639       DisplayFatalError(buf, 0, 2);
1640       return;
1641     }
1642
1643     if (appData.matchMode) {
1644         if(appData.tourneyFile[0]) { // start tourney from command line
1645             FILE *f;
1646             if(f = fopen(appData.tourneyFile, "r")) {
1647                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1648                 fclose(f);
1649                 appData.clockMode = TRUE;
1650                 SetGNUMode();
1651             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1652         }
1653         MatchEvent(TRUE);
1654     } else if (*appData.cmailGameName != NULLCHAR) {
1655         /* Set up cmail mode */
1656         ReloadCmailMsgEvent(TRUE);
1657     } else {
1658         /* Set up other modes */
1659         if (initialMode == AnalyzeFile) {
1660           if (*appData.loadGameFile == NULLCHAR) {
1661             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1662             return;
1663           }
1664         }
1665         if (*appData.loadGameFile != NULLCHAR) {
1666             (void) LoadGameFromFile(appData.loadGameFile,
1667                                     appData.loadGameIndex,
1668                                     appData.loadGameFile, TRUE);
1669         } else if (*appData.loadPositionFile != NULLCHAR) {
1670             (void) LoadPositionFromFile(appData.loadPositionFile,
1671                                         appData.loadPositionIndex,
1672                                         appData.loadPositionFile);
1673             /* [HGM] try to make self-starting even after FEN load */
1674             /* to allow automatic setup of fairy variants with wtm */
1675             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676                 gameMode = BeginningOfGame;
1677                 setboardSpoiledMachineBlack = 1;
1678             }
1679             /* [HGM] loadPos: make that every new game uses the setup */
1680             /* from file as long as we do not switch variant          */
1681             if(!blackPlaysFirst) {
1682                 startedFromPositionFile = TRUE;
1683                 CopyBoard(filePosition, boards[0]);
1684             }
1685         }
1686         if (initialMode == AnalyzeMode) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1693             return;
1694           }
1695           AnalyzeModeEvent();
1696         } else if (initialMode == AnalyzeFile) {
1697           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698           ShowThinkingEvent();
1699           AnalyzeFileEvent();
1700           AnalysisPeriodicEvent(1);
1701         } else if (initialMode == MachinePlaysWhite) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           MachineWhiteEvent();
1713         } else if (initialMode == MachinePlaysBlack) {
1714           if (appData.noChessProgram) {
1715             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1716                               0, 2);
1717             return;
1718           }
1719           if (appData.icsActive) {
1720             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1721                               0, 2);
1722             return;
1723           }
1724           MachineBlackEvent();
1725         } else if (initialMode == TwoMachinesPlay) {
1726           if (appData.noChessProgram) {
1727             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1728                               0, 2);
1729             return;
1730           }
1731           if (appData.icsActive) {
1732             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1733                               0, 2);
1734             return;
1735           }
1736           TwoMachinesEvent();
1737         } else if (initialMode == EditGame) {
1738           EditGameEvent();
1739         } else if (initialMode == EditPosition) {
1740           EditPositionEvent();
1741         } else if (initialMode == Training) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744             return;
1745           }
1746           TrainingEvent();
1747         }
1748     }
1749 }
1750
1751 void
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1753 {
1754     DisplayBook(current+1);
1755
1756     MoveHistorySet( movelist, first, last, current, pvInfoList );
1757
1758     EvalGraphSet( first, last, current, pvInfoList );
1759
1760     MakeEngineOutputTitle();
1761 }
1762
1763 /*
1764  * Establish will establish a contact to a remote host.port.
1765  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766  *  used to talk to the host.
1767  * Returns 0 if okay, error code if not.
1768  */
1769 int
1770 establish ()
1771 {
1772     char buf[MSG_SIZ];
1773
1774     if (*appData.icsCommPort != NULLCHAR) {
1775         /* Talk to the host through a serial comm port */
1776         return OpenCommPort(appData.icsCommPort, &icsPR);
1777
1778     } else if (*appData.gateway != NULLCHAR) {
1779         if (*appData.remoteShell == NULLCHAR) {
1780             /* Use the rcmd protocol to run telnet program on a gateway host */
1781             snprintf(buf, sizeof(buf), "%s %s %s",
1782                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1783             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1784
1785         } else {
1786             /* Use the rsh program to run telnet program on a gateway host */
1787             if (*appData.remoteUser == NULLCHAR) {
1788                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789                         appData.gateway, appData.telnetProgram,
1790                         appData.icsHost, appData.icsPort);
1791             } else {
1792                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793                         appData.remoteShell, appData.gateway,
1794                         appData.remoteUser, appData.telnetProgram,
1795                         appData.icsHost, appData.icsPort);
1796             }
1797             return StartChildProcess(buf, "", &icsPR);
1798
1799         }
1800     } else if (appData.useTelnet) {
1801         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1802
1803     } else {
1804         /* TCP socket interface differs somewhat between
1805            Unix and NT; handle details in the front end.
1806            */
1807         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1808     }
1809 }
1810
1811 void
1812 EscapeExpand (char *p, char *q)
1813 {       // [HGM] initstring: routine to shape up string arguments
1814         while(*p++ = *q++) if(p[-1] == '\\')
1815             switch(*q++) {
1816                 case 'n': p[-1] = '\n'; break;
1817                 case 'r': p[-1] = '\r'; break;
1818                 case 't': p[-1] = '\t'; break;
1819                 case '\\': p[-1] = '\\'; break;
1820                 case 0: *p = 0; return;
1821                 default: p[-1] = q[-1]; break;
1822             }
1823 }
1824
1825 void
1826 show_bytes (FILE *fp, char *buf, int count)
1827 {
1828     while (count--) {
1829         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830             fprintf(fp, "\\%03o", *buf & 0xff);
1831         } else {
1832             putc(*buf, fp);
1833         }
1834         buf++;
1835     }
1836     fflush(fp);
1837 }
1838
1839 /* Returns an errno value */
1840 int
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1842 {
1843     char buf[8192], *p, *q, *buflim;
1844     int left, newcount, outcount;
1845
1846     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847         *appData.gateway != NULLCHAR) {
1848         if (appData.debugMode) {
1849             fprintf(debugFP, ">ICS: ");
1850             show_bytes(debugFP, message, count);
1851             fprintf(debugFP, "\n");
1852         }
1853         return OutputToProcess(pr, message, count, outError);
1854     }
1855
1856     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1857     p = message;
1858     q = buf;
1859     left = count;
1860     newcount = 0;
1861     while (left) {
1862         if (q >= buflim) {
1863             if (appData.debugMode) {
1864                 fprintf(debugFP, ">ICS: ");
1865                 show_bytes(debugFP, buf, newcount);
1866                 fprintf(debugFP, "\n");
1867             }
1868             outcount = OutputToProcess(pr, buf, newcount, outError);
1869             if (outcount < newcount) return -1; /* to be sure */
1870             q = buf;
1871             newcount = 0;
1872         }
1873         if (*p == '\n') {
1874             *q++ = '\r';
1875             newcount++;
1876         } else if (((unsigned char) *p) == TN_IAC) {
1877             *q++ = (char) TN_IAC;
1878             newcount ++;
1879         }
1880         *q++ = *p++;
1881         newcount++;
1882         left--;
1883     }
1884     if (appData.debugMode) {
1885         fprintf(debugFP, ">ICS: ");
1886         show_bytes(debugFP, buf, newcount);
1887         fprintf(debugFP, "\n");
1888     }
1889     outcount = OutputToProcess(pr, buf, newcount, outError);
1890     if (outcount < newcount) return -1; /* to be sure */
1891     return count;
1892 }
1893
1894 void
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1896 {
1897     int outError, outCount;
1898     static int gotEof = 0;
1899     static FILE *ini;
1900
1901     /* Pass data read from player on to ICS */
1902     if (count > 0) {
1903         gotEof = 0;
1904         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905         if (outCount < count) {
1906             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1907         }
1908         if(have_sent_ICS_logon == 2) {
1909           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910             fprintf(ini, "%s", message);
1911             have_sent_ICS_logon = 3;
1912           } else
1913             have_sent_ICS_logon = 1;
1914         } else if(have_sent_ICS_logon == 3) {
1915             fprintf(ini, "%s", message);
1916             fclose(ini);
1917           have_sent_ICS_logon = 1;
1918         }
1919     } else if (count < 0) {
1920         RemoveInputSource(isr);
1921         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922     } else if (gotEof++ > 0) {
1923         RemoveInputSource(isr);
1924         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1925     }
1926 }
1927
1928 void
1929 KeepAlive ()
1930 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933     SendToICS("date\n");
1934     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1935 }
1936
1937 /* added routine for printf style output to ics */
1938 void
1939 ics_printf (char *format, ...)
1940 {
1941     char buffer[MSG_SIZ];
1942     va_list args;
1943
1944     va_start(args, format);
1945     vsnprintf(buffer, sizeof(buffer), format, args);
1946     buffer[sizeof(buffer)-1] = '\0';
1947     SendToICS(buffer);
1948     va_end(args);
1949 }
1950
1951 void
1952 SendToICS (char *s)
1953 {
1954     int count, outCount, outError;
1955
1956     if (icsPR == NoProc) return;
1957
1958     count = strlen(s);
1959     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960     if (outCount < count) {
1961         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1962     }
1963 }
1964
1965 /* This is used for sending logon scripts to the ICS. Sending
1966    without a delay causes problems when using timestamp on ICC
1967    (at least on my machine). */
1968 void
1969 SendToICSDelayed (char *s, long msdelay)
1970 {
1971     int count, outCount, outError;
1972
1973     if (icsPR == NoProc) return;
1974
1975     count = strlen(s);
1976     if (appData.debugMode) {
1977         fprintf(debugFP, ">ICS: ");
1978         show_bytes(debugFP, s, count);
1979         fprintf(debugFP, "\n");
1980     }
1981     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1982                                       msdelay);
1983     if (outCount < count) {
1984         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1985     }
1986 }
1987
1988
1989 /* Remove all highlighting escape sequences in s
1990    Also deletes any suffix starting with '('
1991    */
1992 char *
1993 StripHighlightAndTitle (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             if (*s == '(' || *s == '[') {
2005                 *p = NULLCHAR;
2006                 return retbuf;
2007             }
2008             *p++ = *s++;
2009         }
2010     }
2011     *p = NULLCHAR;
2012     return retbuf;
2013 }
2014
2015 /* Remove all highlighting escape sequences in s */
2016 char *
2017 StripHighlight (char *s)
2018 {
2019     static char retbuf[MSG_SIZ];
2020     char *p = retbuf;
2021
2022     while (*s != NULLCHAR) {
2023         while (*s == '\033') {
2024             while (*s != NULLCHAR && !isalpha(*s)) s++;
2025             if (*s != NULLCHAR) s++;
2026         }
2027         while (*s != NULLCHAR && *s != '\033') {
2028             *p++ = *s++;
2029         }
2030     }
2031     *p = NULLCHAR;
2032     return retbuf;
2033 }
2034
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2037 char *
2038 VariantName (VariantClass v)
2039 {
2040     if(v == VariantUnknown || *engineVariant) return engineVariant;
2041     return variantNames[v];
2042 }
2043
2044
2045 /* Identify a variant from the strings the chess servers use or the
2046    PGN Variant tag names we use. */
2047 VariantClass
2048 StringToVariant (char *e)
2049 {
2050     char *p;
2051     int wnum = -1;
2052     VariantClass v = VariantNormal;
2053     int i, found = FALSE;
2054     char buf[MSG_SIZ];
2055     int len;
2056
2057     if (!e) return v;
2058
2059     /* [HGM] skip over optional board-size prefixes */
2060     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062         while( *e++ != '_');
2063     }
2064
2065     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2066         v = VariantNormal;
2067         found = TRUE;
2068     } else
2069     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070       if (StrCaseStr(e, variantNames[i])) {
2071         v = (VariantClass) i;
2072         found = TRUE;
2073         break;
2074       }
2075     }
2076
2077     if (!found) {
2078       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079           || StrCaseStr(e, "wild/fr")
2080           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081         v = VariantFischeRandom;
2082       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083                  (i = 1, p = StrCaseStr(e, "w"))) {
2084         p += i;
2085         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2086         if (isdigit(*p)) {
2087           wnum = atoi(p);
2088         } else {
2089           wnum = -1;
2090         }
2091         switch (wnum) {
2092         case 0: /* FICS only, actually */
2093         case 1:
2094           /* Castling legal even if K starts on d-file */
2095           v = VariantWildCastle;
2096           break;
2097         case 2:
2098         case 3:
2099         case 4:
2100           /* Castling illegal even if K & R happen to start in
2101              normal positions. */
2102           v = VariantNoCastle;
2103           break;
2104         case 5:
2105         case 7:
2106         case 8:
2107         case 10:
2108         case 11:
2109         case 12:
2110         case 13:
2111         case 14:
2112         case 15:
2113         case 18:
2114         case 19:
2115           /* Castling legal iff K & R start in normal positions */
2116           v = VariantNormal;
2117           break;
2118         case 6:
2119         case 20:
2120         case 21:
2121           /* Special wilds for position setup; unclear what to do here */
2122           v = VariantLoadable;
2123           break;
2124         case 9:
2125           /* Bizarre ICC game */
2126           v = VariantTwoKings;
2127           break;
2128         case 16:
2129           v = VariantKriegspiel;
2130           break;
2131         case 17:
2132           v = VariantLosers;
2133           break;
2134         case 22:
2135           v = VariantFischeRandom;
2136           break;
2137         case 23:
2138           v = VariantCrazyhouse;
2139           break;
2140         case 24:
2141           v = VariantBughouse;
2142           break;
2143         case 25:
2144           v = Variant3Check;
2145           break;
2146         case 26:
2147           /* Not quite the same as FICS suicide! */
2148           v = VariantGiveaway;
2149           break;
2150         case 27:
2151           v = VariantAtomic;
2152           break;
2153         case 28:
2154           v = VariantShatranj;
2155           break;
2156
2157         /* Temporary names for future ICC types.  The name *will* change in
2158            the next xboard/WinBoard release after ICC defines it. */
2159         case 29:
2160           v = Variant29;
2161           break;
2162         case 30:
2163           v = Variant30;
2164           break;
2165         case 31:
2166           v = Variant31;
2167           break;
2168         case 32:
2169           v = Variant32;
2170           break;
2171         case 33:
2172           v = Variant33;
2173           break;
2174         case 34:
2175           v = Variant34;
2176           break;
2177         case 35:
2178           v = Variant35;
2179           break;
2180         case 36:
2181           v = Variant36;
2182           break;
2183         case 37:
2184           v = VariantShogi;
2185           break;
2186         case 38:
2187           v = VariantXiangqi;
2188           break;
2189         case 39:
2190           v = VariantCourier;
2191           break;
2192         case 40:
2193           v = VariantGothic;
2194           break;
2195         case 41:
2196           v = VariantCapablanca;
2197           break;
2198         case 42:
2199           v = VariantKnightmate;
2200           break;
2201         case 43:
2202           v = VariantFairy;
2203           break;
2204         case 44:
2205           v = VariantCylinder;
2206           break;
2207         case 45:
2208           v = VariantFalcon;
2209           break;
2210         case 46:
2211           v = VariantCapaRandom;
2212           break;
2213         case 47:
2214           v = VariantBerolina;
2215           break;
2216         case 48:
2217           v = VariantJanus;
2218           break;
2219         case 49:
2220           v = VariantSuper;
2221           break;
2222         case 50:
2223           v = VariantGreat;
2224           break;
2225         case -1:
2226           /* Found "wild" or "w" in the string but no number;
2227              must assume it's normal chess. */
2228           v = VariantNormal;
2229           break;
2230         default:
2231           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232           if( (len >= MSG_SIZ) && appData.debugMode )
2233             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2234
2235           DisplayError(buf, 0);
2236           v = VariantUnknown;
2237           break;
2238         }
2239       }
2240     }
2241     if (appData.debugMode) {
2242       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243               e, wnum, VariantName(v));
2244     }
2245     return v;
2246 }
2247
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2250
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252    advance *index beyond it, and set leftover_start to the new value of
2253    *index; else return FALSE.  If pattern contains the character '*', it
2254    matches any sequence of characters not containing '\r', '\n', or the
2255    character following the '*' (if any), and the matched sequence(s) are
2256    copied into star_match.
2257    */
2258 int
2259 looking_at ( char *buf, int *index, char *pattern)
2260 {
2261     char *bufp = &buf[*index], *patternp = pattern;
2262     int star_count = 0;
2263     char *matchp = star_match[0];
2264
2265     for (;;) {
2266         if (*patternp == NULLCHAR) {
2267             *index = leftover_start = bufp - buf;
2268             *matchp = NULLCHAR;
2269             return TRUE;
2270         }
2271         if (*bufp == NULLCHAR) return FALSE;
2272         if (*patternp == '*') {
2273             if (*bufp == *(patternp + 1)) {
2274                 *matchp = NULLCHAR;
2275                 matchp = star_match[++star_count];
2276                 patternp += 2;
2277                 bufp++;
2278                 continue;
2279             } else if (*bufp == '\n' || *bufp == '\r') {
2280                 patternp++;
2281                 if (*patternp == NULLCHAR)
2282                   continue;
2283                 else
2284                   return FALSE;
2285             } else {
2286                 *matchp++ = *bufp++;
2287                 continue;
2288             }
2289         }
2290         if (*patternp != *bufp) return FALSE;
2291         patternp++;
2292         bufp++;
2293     }
2294 }
2295
2296 void
2297 SendToPlayer (char *data, int length)
2298 {
2299     int error, outCount;
2300     outCount = OutputToProcess(NoProc, data, length, &error);
2301     if (outCount < length) {
2302         DisplayFatalError(_("Error writing to display"), error, 1);
2303     }
2304 }
2305
2306 void
2307 PackHolding (char packed[], char *holding)
2308 {
2309     char *p = holding;
2310     char *q = packed;
2311     int runlength = 0;
2312     int curr = 9999;
2313     do {
2314         if (*p == curr) {
2315             runlength++;
2316         } else {
2317             switch (runlength) {
2318               case 0:
2319                 break;
2320               case 1:
2321                 *q++ = curr;
2322                 break;
2323               case 2:
2324                 *q++ = curr;
2325                 *q++ = curr;
2326                 break;
2327               default:
2328                 sprintf(q, "%d", runlength);
2329                 while (*q) q++;
2330                 *q++ = curr;
2331                 break;
2332             }
2333             runlength = 1;
2334             curr = *p;
2335         }
2336     } while (*p++);
2337     *q = NULLCHAR;
2338 }
2339
2340 /* Telnet protocol requests from the front end */
2341 void
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2343 {
2344     unsigned char msg[3];
2345     int outCount, outError;
2346
2347     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2348
2349     if (appData.debugMode) {
2350         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2351         switch (ddww) {
2352           case TN_DO:
2353             ddwwStr = "DO";
2354             break;
2355           case TN_DONT:
2356             ddwwStr = "DONT";
2357             break;
2358           case TN_WILL:
2359             ddwwStr = "WILL";
2360             break;
2361           case TN_WONT:
2362             ddwwStr = "WONT";
2363             break;
2364           default:
2365             ddwwStr = buf1;
2366             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367             break;
2368         }
2369         switch (option) {
2370           case TN_ECHO:
2371             optionStr = "ECHO";
2372             break;
2373           default:
2374             optionStr = buf2;
2375             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2376             break;
2377         }
2378         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2379     }
2380     msg[0] = TN_IAC;
2381     msg[1] = ddww;
2382     msg[2] = option;
2383     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2384     if (outCount < 3) {
2385         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2386     }
2387 }
2388
2389 void
2390 DoEcho ()
2391 {
2392     if (!appData.icsActive) return;
2393     TelnetRequest(TN_DO, TN_ECHO);
2394 }
2395
2396 void
2397 DontEcho ()
2398 {
2399     if (!appData.icsActive) return;
2400     TelnetRequest(TN_DONT, TN_ECHO);
2401 }
2402
2403 void
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2405 {
2406     /* put the holdings sent to us by the server on the board holdings area */
2407     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2408     char p;
2409     ChessSquare piece;
2410
2411     if(gameInfo.holdingsWidth < 2)  return;
2412     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413         return; // prevent overwriting by pre-board holdings
2414
2415     if( (int)lowestPiece >= BlackPawn ) {
2416         holdingsColumn = 0;
2417         countsColumn = 1;
2418         holdingsStartRow = BOARD_HEIGHT-1;
2419         direction = -1;
2420     } else {
2421         holdingsColumn = BOARD_WIDTH-1;
2422         countsColumn = BOARD_WIDTH-2;
2423         holdingsStartRow = 0;
2424         direction = 1;
2425     }
2426
2427     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428         board[i][holdingsColumn] = EmptySquare;
2429         board[i][countsColumn]   = (ChessSquare) 0;
2430     }
2431     while( (p=*holdings++) != NULLCHAR ) {
2432         piece = CharToPiece( ToUpper(p) );
2433         if(piece == EmptySquare) continue;
2434         /*j = (int) piece - (int) WhitePawn;*/
2435         j = PieceToNumber(piece);
2436         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437         if(j < 0) continue;               /* should not happen */
2438         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440         board[holdingsStartRow+j*direction][countsColumn]++;
2441     }
2442 }
2443
2444
2445 void
2446 VariantSwitch (Board board, VariantClass newVariant)
2447 {
2448    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449    static Board oldBoard;
2450
2451    startedFromPositionFile = FALSE;
2452    if(gameInfo.variant == newVariant) return;
2453
2454    /* [HGM] This routine is called each time an assignment is made to
2455     * gameInfo.variant during a game, to make sure the board sizes
2456     * are set to match the new variant. If that means adding or deleting
2457     * holdings, we shift the playing board accordingly
2458     * This kludge is needed because in ICS observe mode, we get boards
2459     * of an ongoing game without knowing the variant, and learn about the
2460     * latter only later. This can be because of the move list we requested,
2461     * in which case the game history is refilled from the beginning anyway,
2462     * but also when receiving holdings of a crazyhouse game. In the latter
2463     * case we want to add those holdings to the already received position.
2464     */
2465
2466
2467    if (appData.debugMode) {
2468      fprintf(debugFP, "Switch board from %s to %s\n",
2469              VariantName(gameInfo.variant), VariantName(newVariant));
2470      setbuf(debugFP, NULL);
2471    }
2472    shuffleOpenings = 0;       /* [HGM] shuffle */
2473    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2474    switch(newVariant)
2475      {
2476      case VariantShogi:
2477        newWidth = 9;  newHeight = 9;
2478        gameInfo.holdingsSize = 7;
2479      case VariantBughouse:
2480      case VariantCrazyhouse:
2481        newHoldingsWidth = 2; break;
2482      case VariantGreat:
2483        newWidth = 10;
2484      case VariantSuper:
2485        newHoldingsWidth = 2;
2486        gameInfo.holdingsSize = 8;
2487        break;
2488      case VariantGothic:
2489      case VariantCapablanca:
2490      case VariantCapaRandom:
2491        newWidth = 10;
2492      default:
2493        newHoldingsWidth = gameInfo.holdingsSize = 0;
2494      };
2495
2496    if(newWidth  != gameInfo.boardWidth  ||
2497       newHeight != gameInfo.boardHeight ||
2498       newHoldingsWidth != gameInfo.holdingsWidth ) {
2499
2500      /* shift position to new playing area, if needed */
2501      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502        for(i=0; i<BOARD_HEIGHT; i++)
2503          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2505              board[i][j];
2506        for(i=0; i<newHeight; i++) {
2507          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2509        }
2510      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511        for(i=0; i<BOARD_HEIGHT; i++)
2512          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2514              board[i][j];
2515      }
2516      board[HOLDINGS_SET] = 0;
2517      gameInfo.boardWidth  = newWidth;
2518      gameInfo.boardHeight = newHeight;
2519      gameInfo.holdingsWidth = newHoldingsWidth;
2520      gameInfo.variant = newVariant;
2521      InitDrawingSizes(-2, 0);
2522    } else gameInfo.variant = newVariant;
2523    CopyBoard(oldBoard, board);   // remember correctly formatted board
2524      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2525    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2526 }
2527
2528 static int loggedOn = FALSE;
2529
2530 /*-- Game start info cache: --*/
2531 int gs_gamenum;
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\   ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2539
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2542
2543 // [HGM] seekgraph
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2547 #define SQUARE 0x80
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2556
2557 void
2558 PlotSeekAd (int i)
2559 {
2560         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562         if(r < minRating+100 && r >=0 ) r = minRating+100;
2563         if(r > maxRating) r = maxRating;
2564         if(tc < 1.f) tc = 1.f;
2565         if(tc > 95.f) tc = 95.f;
2566         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567         y = ((double)r - minRating)/(maxRating - minRating)
2568             * (h-vMargin-squareSize/8-1) + vMargin;
2569         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570         if(strstr(seekAdList[i], " u ")) color = 1;
2571         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572            !strstr(seekAdList[i], "bullet") &&
2573            !strstr(seekAdList[i], "blitz") &&
2574            !strstr(seekAdList[i], "standard") ) color = 2;
2575         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2577 }
2578
2579 void
2580 PlotSingleSeekAd (int i)
2581 {
2582         PlotSeekAd(i);
2583 }
2584
2585 void
2586 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2587 {
2588         char buf[MSG_SIZ], *ext = "";
2589         VariantClass v = StringToVariant(type);
2590         if(strstr(type, "wild")) {
2591             ext = type + 4; // append wild number
2592             if(v == VariantFischeRandom) type = "chess960"; else
2593             if(v == VariantLoadable) type = "setup"; else
2594             type = VariantName(v);
2595         }
2596         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602             seekNrList[nrOfSeekAds] = nr;
2603             zList[nrOfSeekAds] = 0;
2604             seekAdList[nrOfSeekAds++] = StrSave(buf);
2605             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2606         }
2607 }
2608
2609 void
2610 EraseSeekDot (int i)
2611 {
2612     int x = xList[i], y = yList[i], d=squareSize/4, k;
2613     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615     // now replot every dot that overlapped
2616     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617         int xx = xList[k], yy = yList[k];
2618         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619             DrawSeekDot(xx, yy, colorList[k]);
2620     }
2621 }
2622
2623 void
2624 RemoveSeekAd (int nr)
2625 {
2626         int i;
2627         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2628             EraseSeekDot(i);
2629             if(seekAdList[i]) free(seekAdList[i]);
2630             seekAdList[i] = seekAdList[--nrOfSeekAds];
2631             seekNrList[i] = seekNrList[nrOfSeekAds];
2632             ratingList[i] = ratingList[nrOfSeekAds];
2633             colorList[i]  = colorList[nrOfSeekAds];
2634             tcList[i] = tcList[nrOfSeekAds];
2635             xList[i]  = xList[nrOfSeekAds];
2636             yList[i]  = yList[nrOfSeekAds];
2637             zList[i]  = zList[nrOfSeekAds];
2638             seekAdList[nrOfSeekAds] = NULL;
2639             break;
2640         }
2641 }
2642
2643 Boolean
2644 MatchSoughtLine (char *line)
2645 {
2646     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647     int nr, base, inc, u=0; char dummy;
2648
2649     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2651        (u=1) &&
2652        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2654         // match: compact and save the line
2655         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2656         return TRUE;
2657     }
2658     return FALSE;
2659 }
2660
2661 int
2662 DrawSeekGraph ()
2663 {
2664     int i;
2665     if(!seekGraphUp) return FALSE;
2666     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2668
2669     DrawSeekBackground(0, 0, w, h);
2670     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2674         yy = h-1-yy;
2675         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2676         if(i%500 == 0) {
2677             char buf[MSG_SIZ];
2678             snprintf(buf, MSG_SIZ, "%d", i);
2679             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2680         }
2681     }
2682     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683     for(i=1; i<100; i+=(i<10?1:5)) {
2684         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2690         }
2691     }
2692     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2693     return TRUE;
2694 }
2695
2696 int
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2698 {
2699     static int lastDown = 0, displayed = 0, lastSecond;
2700     if(y < 0) return FALSE;
2701     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703         if(!seekGraphUp) return FALSE;
2704         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705         DrawPosition(TRUE, NULL);
2706         return TRUE;
2707     }
2708     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709         if(click == Release || moving) return FALSE;
2710         nrOfSeekAds = 0;
2711         soughtPending = TRUE;
2712         SendToICS(ics_prefix);
2713         SendToICS("sought\n"); // should this be "sought all"?
2714     } else { // issue challenge based on clicked ad
2715         int dist = 10000; int i, closest = 0, second = 0;
2716         for(i=0; i<nrOfSeekAds; i++) {
2717             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2718             if(d < dist) { dist = d; closest = i; }
2719             second += (d - zList[i] < 120); // count in-range ads
2720             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2721         }
2722         if(dist < 120) {
2723             char buf[MSG_SIZ];
2724             second = (second > 1);
2725             if(displayed != closest || second != lastSecond) {
2726                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727                 lastSecond = second; displayed = closest;
2728             }
2729             if(click == Press) {
2730                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2731                 lastDown = closest;
2732                 return TRUE;
2733             } // on press 'hit', only show info
2734             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736             SendToICS(ics_prefix);
2737             SendToICS(buf);
2738             return TRUE; // let incoming board of started game pop down the graph
2739         } else if(click == Release) { // release 'miss' is ignored
2740             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741             if(moving == 2) { // right up-click
2742                 nrOfSeekAds = 0; // refresh graph
2743                 soughtPending = TRUE;
2744                 SendToICS(ics_prefix);
2745                 SendToICS("sought\n"); // should this be "sought all"?
2746             }
2747             return TRUE;
2748         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749         // press miss or release hit 'pop down' seek graph
2750         seekGraphUp = FALSE;
2751         DrawPosition(TRUE, NULL);
2752     }
2753     return TRUE;
2754 }
2755
2756 void
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2758 {
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2768
2769     static int started = STARTED_NONE;
2770     static char parse[20000];
2771     static int parse_pos = 0;
2772     static char buf[BUF_SIZE + 1];
2773     static int firstTime = TRUE, intfSet = FALSE;
2774     static ColorClass prevColor = ColorNormal;
2775     static int savingComment = FALSE;
2776     static int cmatch = 0; // continuation sequence match
2777     char *bp;
2778     char str[MSG_SIZ];
2779     int i, oldi;
2780     int buf_len;
2781     int next_out;
2782     int tkind;
2783     int backup;    /* [DM] For zippy color lines */
2784     char *p;
2785     char talker[MSG_SIZ]; // [HGM] chat
2786     int channel;
2787
2788     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2789
2790     if (appData.debugMode) {
2791       if (!error) {
2792         fprintf(debugFP, "<ICS: ");
2793         show_bytes(debugFP, data, count);
2794         fprintf(debugFP, "\n");
2795       }
2796     }
2797
2798     if (appData.debugMode) { int f = forwardMostMove;
2799         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2802     }
2803     if (count > 0) {
2804         /* If last read ended with a partial line that we couldn't parse,
2805            prepend it to the new read and try again. */
2806         if (leftover_len > 0) {
2807             for (i=0; i<leftover_len; i++)
2808               buf[i] = buf[leftover_start + i];
2809         }
2810
2811     /* copy new characters into the buffer */
2812     bp = buf + leftover_len;
2813     buf_len=leftover_len;
2814     for (i=0; i<count; i++)
2815     {
2816         // ignore these
2817         if (data[i] == '\r')
2818             continue;
2819
2820         // join lines split by ICS?
2821         if (!appData.noJoin)
2822         {
2823             /*
2824                 Joining just consists of finding matches against the
2825                 continuation sequence, and discarding that sequence
2826                 if found instead of copying it.  So, until a match
2827                 fails, there's nothing to do since it might be the
2828                 complete sequence, and thus, something we don't want
2829                 copied.
2830             */
2831             if (data[i] == cont_seq[cmatch])
2832             {
2833                 cmatch++;
2834                 if (cmatch == strlen(cont_seq))
2835                 {
2836                     cmatch = 0; // complete match.  just reset the counter
2837
2838                     /*
2839                         it's possible for the ICS to not include the space
2840                         at the end of the last word, making our [correct]
2841                         join operation fuse two separate words.  the server
2842                         does this when the space occurs at the width setting.
2843                     */
2844                     if (!buf_len || buf[buf_len-1] != ' ')
2845                     {
2846                         *bp++ = ' ';
2847                         buf_len++;
2848                     }
2849                 }
2850                 continue;
2851             }
2852             else if (cmatch)
2853             {
2854                 /*
2855                     match failed, so we have to copy what matched before
2856                     falling through and copying this character.  In reality,
2857                     this will only ever be just the newline character, but
2858                     it doesn't hurt to be precise.
2859                 */
2860                 strncpy(bp, cont_seq, cmatch);
2861                 bp += cmatch;
2862                 buf_len += cmatch;
2863                 cmatch = 0;
2864             }
2865         }
2866
2867         // copy this char
2868         *bp++ = data[i];
2869         buf_len++;
2870     }
2871
2872         buf[buf_len] = NULLCHAR;
2873 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2874         next_out = 0;
2875         leftover_start = 0;
2876
2877         i = 0;
2878         while (i < buf_len) {
2879             /* Deal with part of the TELNET option negotiation
2880                protocol.  We refuse to do anything beyond the
2881                defaults, except that we allow the WILL ECHO option,
2882                which ICS uses to turn off password echoing when we are
2883                directly connected to it.  We reject this option
2884                if localLineEditing mode is on (always on in xboard)
2885                and we are talking to port 23, which might be a real
2886                telnet server that will try to keep WILL ECHO on permanently.
2887              */
2888             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890                 unsigned char option;
2891                 oldi = i;
2892                 switch ((unsigned char) buf[++i]) {
2893                   case TN_WILL:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<WILL ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       case TN_ECHO:
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "ECHO ");
2900                         /* Reply only if this is a change, according
2901                            to the protocol rules. */
2902                         if (remoteEchoOption) break;
2903                         if (appData.localLineEditing &&
2904                             atoi(appData.icsPort) == TN_PORT) {
2905                             TelnetRequest(TN_DONT, TN_ECHO);
2906                         } else {
2907                             EchoOff();
2908                             TelnetRequest(TN_DO, TN_ECHO);
2909                             remoteEchoOption = TRUE;
2910                         }
2911                         break;
2912                       default:
2913                         if (appData.debugMode)
2914                           fprintf(debugFP, "%d ", option);
2915                         /* Whatever this is, we don't want it. */
2916                         TelnetRequest(TN_DONT, option);
2917                         break;
2918                     }
2919                     break;
2920                   case TN_WONT:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WONT ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (!remoteEchoOption) break;
2930                         EchoOn();
2931                         TelnetRequest(TN_DONT, TN_ECHO);
2932                         remoteEchoOption = FALSE;
2933                         break;
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", (unsigned char) option);
2937                         /* Whatever this is, it must already be turned
2938                            off, because we never agree to turn on
2939                            anything non-default, so according to the
2940                            protocol rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_DO:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<DO ");
2947                     switch (option = (unsigned char) buf[++i]) {
2948                       default:
2949                         /* Whatever this is, we refuse to do it. */
2950                         if (appData.debugMode)
2951                           fprintf(debugFP, "%d ", option);
2952                         TelnetRequest(TN_WONT, option);
2953                         break;
2954                     }
2955                     break;
2956                   case TN_DONT:
2957                     if (appData.debugMode)
2958                       fprintf(debugFP, "\n<DONT ");
2959                     switch (option = (unsigned char) buf[++i]) {
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we are already not doing
2964                            it, because we never agree to do anything
2965                            non-default, so according to the protocol
2966                            rules, we don't reply. */
2967                         break;
2968                     }
2969                     break;
2970                   case TN_IAC:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<IAC ");
2973                     /* Doubled IAC; pass it through */
2974                     i--;
2975                     break;
2976                   default:
2977                     if (appData.debugMode)
2978                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979                     /* Drop all other telnet commands on the floor */
2980                     break;
2981                 }
2982                 if (oldi > next_out)
2983                   SendToPlayer(&buf[next_out], oldi - next_out);
2984                 if (++i > next_out)
2985                   next_out = i;
2986                 continue;
2987             }
2988
2989             /* OK, this at least will *usually* work */
2990             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2991                 loggedOn = TRUE;
2992             }
2993
2994             if (loggedOn && !intfSet) {
2995                 if (ics_type == ICS_ICC) {
2996                   snprintf(str, MSG_SIZ,
2997                           "/set-quietly interface %s\n/set-quietly style 12\n",
2998                           programVersion);
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3001                 } else if (ics_type == ICS_CHESSNET) {
3002                   snprintf(str, MSG_SIZ, "/style 12\n");
3003                 } else {
3004                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005                   strcat(str, programVersion);
3006                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3009 #ifdef WIN32
3010                   strcat(str, "$iset nohighlight 1\n");
3011 #endif
3012                   strcat(str, "$iset lock 1\n$style 12\n");
3013                 }
3014                 SendToICS(str);
3015                 NotifyFrontendLogin();
3016                 intfSet = TRUE;
3017             }
3018
3019             if (started == STARTED_COMMENT) {
3020                 /* Accumulate characters in comment */
3021                 parse[parse_pos++] = buf[i];
3022                 if (buf[i] == '\n') {
3023                     parse[parse_pos] = NULLCHAR;
3024                     if(chattingPartner>=0) {
3025                         char mess[MSG_SIZ];
3026                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027                         OutputChatMessage(chattingPartner, mess);
3028                         chattingPartner = -1;
3029                         next_out = i+1; // [HGM] suppress printing in ICS window
3030                     } else
3031                     if(!suppressKibitz) // [HGM] kibitz
3032                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034                         int nrDigit = 0, nrAlph = 0, j;
3035                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037                         parse[parse_pos] = NULLCHAR;
3038                         // try to be smart: if it does not look like search info, it should go to
3039                         // ICS interaction window after all, not to engine-output window.
3040                         for(j=0; j<parse_pos; j++) { // count letters and digits
3041                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3043                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3044                         }
3045                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046                             int depth=0; float score;
3047                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049                                 pvInfoList[forwardMostMove-1].depth = depth;
3050                                 pvInfoList[forwardMostMove-1].score = 100*score;
3051                             }
3052                             OutputKibitz(suppressKibitz, parse);
3053                         } else {
3054                             char tmp[MSG_SIZ];
3055                             if(gameMode == IcsObserving) // restore original ICS messages
3056                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3058                             else
3059                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061                             SendToPlayer(tmp, strlen(tmp));
3062                         }
3063                         next_out = i+1; // [HGM] suppress printing in ICS window
3064                     }
3065                     started = STARTED_NONE;
3066                 } else {
3067                     /* Don't match patterns against characters in comment */
3068                     i++;
3069                     continue;
3070                 }
3071             }
3072             if (started == STARTED_CHATTER) {
3073                 if (buf[i] != '\n') {
3074                     /* Don't match patterns against characters in chatter */
3075                     i++;
3076                     continue;
3077                 }
3078                 started = STARTED_NONE;
3079                 if(suppressKibitz) next_out = i+1;
3080             }
3081
3082             /* Kludge to deal with rcmd protocol */
3083             if (firstTime && looking_at(buf, &i, "\001*")) {
3084                 DisplayFatalError(&buf[1], 0, 1);
3085                 continue;
3086             } else {
3087                 firstTime = FALSE;
3088             }
3089
3090             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3091                 ics_type = ICS_ICC;
3092                 ics_prefix = "/";
3093                 if (appData.debugMode)
3094                   fprintf(debugFP, "ics_type %d\n", ics_type);
3095                 continue;
3096             }
3097             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098                 ics_type = ICS_FICS;
3099                 ics_prefix = "$";
3100                 if (appData.debugMode)
3101                   fprintf(debugFP, "ics_type %d\n", ics_type);
3102                 continue;
3103             }
3104             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105                 ics_type = ICS_CHESSNET;
3106                 ics_prefix = "/";
3107                 if (appData.debugMode)
3108                   fprintf(debugFP, "ics_type %d\n", ics_type);
3109                 continue;
3110             }
3111
3112             if (!loggedOn &&
3113                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3115                  looking_at(buf, &i, "will be \"*\""))) {
3116               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3117               continue;
3118             }
3119
3120             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3121               char buf[MSG_SIZ];
3122               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123               DisplayIcsInteractionTitle(buf);
3124               have_set_title = TRUE;
3125             }
3126
3127             /* skip finger notes */
3128             if (started == STARTED_NONE &&
3129                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130                  (buf[i] == '1' && buf[i+1] == '0')) &&
3131                 buf[i+2] == ':' && buf[i+3] == ' ') {
3132               started = STARTED_CHATTER;
3133               i += 3;
3134               continue;
3135             }
3136
3137             oldi = i;
3138             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139             if(appData.seekGraph) {
3140                 if(soughtPending && MatchSoughtLine(buf+i)) {
3141                     i = strstr(buf+i, "rated") - buf;
3142                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                     next_out = leftover_start = i;
3144                     started = STARTED_CHATTER;
3145                     suppressKibitz = TRUE;
3146                     continue;
3147                 }
3148                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149                         && looking_at(buf, &i, "* ads displayed")) {
3150                     soughtPending = FALSE;
3151                     seekGraphUp = TRUE;
3152                     DrawSeekGraph();
3153                     continue;
3154                 }
3155                 if(appData.autoRefresh) {
3156                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157                         int s = (ics_type == ICS_ICC); // ICC format differs
3158                         if(seekGraphUp)
3159                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161                         looking_at(buf, &i, "*% "); // eat prompt
3162                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = i; // suppress
3165                         continue;
3166                     }
3167                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168                         char *p = star_match[0];
3169                         while(*p) {
3170                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3171                             while(*p && *p++ != ' '); // next
3172                         }
3173                         looking_at(buf, &i, "*% "); // eat prompt
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         continue;
3177                     }
3178                 }
3179             }
3180
3181             /* skip formula vars */
3182             if (started == STARTED_NONE &&
3183                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184               started = STARTED_CHATTER;
3185               i += 3;
3186               continue;
3187             }
3188
3189             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190             if (appData.autoKibitz && started == STARTED_NONE &&
3191                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3192                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3197                         suppressKibitz = TRUE;
3198                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = i;
3200                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201                                 && (gameMode == IcsPlayingWhite)) ||
3202                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3204                             started = STARTED_CHATTER; // own kibitz we simply discard
3205                         else {
3206                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207                             parse_pos = 0; parse[0] = NULLCHAR;
3208                             savingComment = TRUE;
3209                             suppressKibitz = gameMode != IcsObserving ? 2 :
3210                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3211                         }
3212                         continue;
3213                 } else
3214                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216                          && atoi(star_match[0])) {
3217                     // suppress the acknowledgements of our own autoKibitz
3218                     char *p;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221                     SendToPlayer(star_match[0], strlen(star_match[0]));
3222                     if(looking_at(buf, &i, "*% ")) // eat prompt
3223                         suppressKibitz = FALSE;
3224                     next_out = i;
3225                     continue;
3226                 }
3227             } // [HGM] kibitz: end of patch
3228
3229             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3230
3231             // [HGM] chat: intercept tells by users for which we have an open chat window
3232             channel = -1;
3233             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234                                            looking_at(buf, &i, "* whispers:") ||
3235                                            looking_at(buf, &i, "* kibitzes:") ||
3236                                            looking_at(buf, &i, "* shouts:") ||
3237                                            looking_at(buf, &i, "* c-shouts:") ||
3238                                            looking_at(buf, &i, "--> * ") ||
3239                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3243                 int p;
3244                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245                 chattingPartner = -1;
3246
3247                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250                     talker[0] = '['; strcat(talker, "] ");
3251                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252                     chattingPartner = p; break;
3253                     }
3254                 } else
3255                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256                 for(p=0; p<MAX_CHAT; p++) {
3257                     if(!strcmp("kibitzes", chatPartner[p])) {
3258                         talker[0] = '['; strcat(talker, "] ");
3259                         chattingPartner = p; break;
3260                     }
3261                 } else
3262                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263                 for(p=0; p<MAX_CHAT; p++) {
3264                     if(!strcmp("whispers", chatPartner[p])) {
3265                         talker[0] = '['; strcat(talker, "] ");
3266                         chattingPartner = p; break;
3267                     }
3268                 } else
3269                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270                   if(buf[i-8] == '-' && buf[i-3] == 't')
3271                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272                     if(!strcmp("c-shouts", chatPartner[p])) {
3273                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274                         chattingPartner = p; break;
3275                     }
3276                   }
3277                   if(chattingPartner < 0)
3278                   for(p=0; p<MAX_CHAT; p++) {
3279                     if(!strcmp("shouts", chatPartner[p])) {
3280                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283                         chattingPartner = p; break;
3284                     }
3285                   }
3286                 }
3287                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289                     talker[0] = 0; Colorize(ColorTell, FALSE);
3290                     chattingPartner = p; break;
3291                 }
3292                 if(chattingPartner<0) i = oldi; else {
3293                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296                     started = STARTED_COMMENT;
3297                     parse_pos = 0; parse[0] = NULLCHAR;
3298                     savingComment = 3 + chattingPartner; // counts as TRUE
3299                     suppressKibitz = TRUE;
3300                     continue;
3301                 }
3302             } // [HGM] chat: end of patch
3303
3304           backup = i;
3305             if (appData.zippyTalk || appData.zippyPlay) {
3306                 /* [DM] Backup address for color zippy lines */
3307 #if ZIPPY
3308                if (loggedOn == TRUE)
3309                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3311 #endif
3312             } // [DM] 'else { ' deleted
3313                 if (
3314                     /* Regular tells and says */
3315                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3317                     looking_at(buf, &i, "* says: ") ||
3318                     /* Don't color "message" or "messages" output */
3319                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320                     looking_at(buf, &i, "*. * at *:*: ") ||
3321                     looking_at(buf, &i, "--* (*:*): ") ||
3322                     /* Message notifications (same color as tells) */
3323                     looking_at(buf, &i, "* has left a message ") ||
3324                     looking_at(buf, &i, "* just sent you a message:\n") ||
3325                     /* Whispers and kibitzes */
3326                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327                     looking_at(buf, &i, "* kibitzes: ") ||
3328                     /* Channel tells */
3329                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3330
3331                   if (tkind == 1 && strchr(star_match[0], ':')) {
3332                       /* Avoid "tells you:" spoofs in channels */
3333                      tkind = 3;
3334                   }
3335                   if (star_match[0][0] == NULLCHAR ||
3336                       strchr(star_match[0], ' ') ||
3337                       (tkind == 3 && strchr(star_match[1], ' '))) {
3338                     /* Reject bogus matches */
3339                     i = oldi;
3340                   } else {
3341                     if (appData.colorize) {
3342                       if (oldi > next_out) {
3343                         SendToPlayer(&buf[next_out], oldi - next_out);
3344                         next_out = oldi;
3345                       }
3346                       switch (tkind) {
3347                       case 1:
3348                         Colorize(ColorTell, FALSE);
3349                         curColor = ColorTell;
3350                         break;
3351                       case 2:
3352                         Colorize(ColorKibitz, FALSE);
3353                         curColor = ColorKibitz;
3354                         break;
3355                       case 3:
3356                         p = strrchr(star_match[1], '(');
3357                         if (p == NULL) {
3358                           p = star_match[1];
3359                         } else {
3360                           p++;
3361                         }
3362                         if (atoi(p) == 1) {
3363                           Colorize(ColorChannel1, FALSE);
3364                           curColor = ColorChannel1;
3365                         } else {
3366                           Colorize(ColorChannel, FALSE);
3367                           curColor = ColorChannel;
3368                         }
3369                         break;
3370                       case 5:
3371                         curColor = ColorNormal;
3372                         break;
3373                       }
3374                     }
3375                     if (started == STARTED_NONE && appData.autoComment &&
3376                         (gameMode == IcsObserving ||
3377                          gameMode == IcsPlayingWhite ||
3378                          gameMode == IcsPlayingBlack)) {
3379                       parse_pos = i - oldi;
3380                       memcpy(parse, &buf[oldi], parse_pos);
3381                       parse[parse_pos] = NULLCHAR;
3382                       started = STARTED_COMMENT;
3383                       savingComment = TRUE;
3384                     } else {
3385                       started = STARTED_CHATTER;
3386                       savingComment = FALSE;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                   }
3391                 }
3392
3393                 if (looking_at(buf, &i, "* s-shouts: ") ||
3394                     looking_at(buf, &i, "* c-shouts: ")) {
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorSShout, FALSE);
3401                         curColor = ColorSShout;
3402                     }
3403                     loggedOn = TRUE;
3404                     started = STARTED_CHATTER;
3405                     continue;
3406                 }
3407
3408                 if (looking_at(buf, &i, "--->")) {
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* shouts: ") ||
3414                     looking_at(buf, &i, "--> ")) {
3415                     if (appData.colorize) {
3416                         if (oldi > next_out) {
3417                             SendToPlayer(&buf[next_out], oldi - next_out);
3418                             next_out = oldi;
3419                         }
3420                         Colorize(ColorShout, FALSE);
3421                         curColor = ColorShout;
3422                     }
3423                     loggedOn = TRUE;
3424                     started = STARTED_CHATTER;
3425                     continue;
3426                 }
3427
3428                 if (looking_at( buf, &i, "Challenge:")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorChallenge, FALSE);
3435                         curColor = ColorChallenge;
3436                     }
3437                     loggedOn = TRUE;
3438                     continue;
3439                 }
3440
3441                 if (looking_at(buf, &i, "* offers you") ||
3442                     looking_at(buf, &i, "* offers to be") ||
3443                     looking_at(buf, &i, "* would like to") ||
3444                     looking_at(buf, &i, "* requests to") ||
3445                     looking_at(buf, &i, "Your opponent offers") ||
3446                     looking_at(buf, &i, "Your opponent requests")) {
3447
3448                     if (appData.colorize) {
3449                         if (oldi > next_out) {
3450                             SendToPlayer(&buf[next_out], oldi - next_out);
3451                             next_out = oldi;
3452                         }
3453                         Colorize(ColorRequest, FALSE);
3454                         curColor = ColorRequest;
3455                     }
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* (*) seeking")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorSeek, FALSE);
3466                         curColor = ColorSeek;
3467                     }
3468                     continue;
3469             }
3470
3471           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3472
3473             if (looking_at(buf, &i, "\\   ")) {
3474                 if (prevColor != ColorNormal) {
3475                     if (oldi > next_out) {
3476                         SendToPlayer(&buf[next_out], oldi - next_out);
3477                         next_out = oldi;
3478                     }
3479                     Colorize(prevColor, TRUE);
3480                     curColor = prevColor;
3481                 }
3482                 if (savingComment) {
3483                     parse_pos = i - oldi;
3484                     memcpy(parse, &buf[oldi], parse_pos);
3485                     parse[parse_pos] = NULLCHAR;
3486                     started = STARTED_COMMENT;
3487                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488                         chattingPartner = savingComment - 3; // kludge to remember the box
3489                 } else {
3490                     started = STARTED_CHATTER;
3491                 }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "Black Strength :") ||
3496                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497                 looking_at(buf, &i, "<10>") ||
3498                 looking_at(buf, &i, "#@#")) {
3499                 /* Wrong board style */
3500                 loggedOn = TRUE;
3501                 SendToICS(ics_prefix);
3502                 SendToICS("set style 12\n");
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i, "login:")) {
3509               if (!have_sent_ICS_logon) {
3510                 if(ICSInitScript())
3511                   have_sent_ICS_logon = 1;
3512                 else // no init script was found
3513                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3516               }
3517                 continue;
3518             }
3519
3520             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521                 (looking_at(buf, &i, "\n<12> ") ||
3522                  looking_at(buf, &i, "<12> "))) {
3523                 loggedOn = TRUE;
3524                 if (oldi > next_out) {
3525                     SendToPlayer(&buf[next_out], oldi - next_out);
3526                 }
3527                 next_out = i;
3528                 started = STARTED_BOARD;
3529                 parse_pos = 0;
3530                 continue;
3531             }
3532
3533             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534                 looking_at(buf, &i, "<b1> ")) {
3535                 if (oldi > next_out) {
3536                     SendToPlayer(&buf[next_out], oldi - next_out);
3537                 }
3538                 next_out = i;
3539                 started = STARTED_HOLDINGS;
3540                 parse_pos = 0;
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3545                 loggedOn = TRUE;
3546                 /* Header for a move list -- first line */
3547
3548                 switch (ics_getting_history) {
3549                   case H_FALSE:
3550                     switch (gameMode) {
3551                       case IcsIdle:
3552                       case BeginningOfGame:
3553                         /* User typed "moves" or "oldmoves" while we
3554                            were idle.  Pretend we asked for these
3555                            moves and soak them up so user can step
3556                            through them and/or save them.
3557                            */
3558                         Reset(FALSE, TRUE);
3559                         gameMode = IcsObserving;
3560                         ModeHighlight();
3561                         ics_gamenum = -1;
3562                         ics_getting_history = H_GOT_UNREQ_HEADER;
3563                         break;
3564                       case EditGame: /*?*/
3565                       case EditPosition: /*?*/
3566                         /* Should above feature work in these modes too? */
3567                         /* For now it doesn't */
3568                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3569                         break;
3570                       default:
3571                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3572                         break;
3573                     }
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Is this the right one? */
3577                     if (gameInfo.white && gameInfo.black &&
3578                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3579                         strcmp(gameInfo.black, star_match[2]) == 0) {
3580                         /* All is well */
3581                         ics_getting_history = H_GOT_REQ_HEADER;
3582                     }
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                   case H_GOT_UNREQ_HEADER:
3586                   case H_GOT_UNWANTED_HEADER:
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: two headers"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                 }
3593
3594                 /* Save player ratings into gameInfo if needed */
3595                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597                     (gameInfo.whiteRating == -1 ||
3598                      gameInfo.blackRating == -1)) {
3599
3600                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3601                     gameInfo.blackRating = string_to_rating(star_match[3]);
3602                     if (appData.debugMode)
3603                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604                               gameInfo.whiteRating, gameInfo.blackRating);
3605                 }
3606                 continue;
3607             }
3608
3609             if (looking_at(buf, &i,
3610               "* * match, initial time: * minute*, increment: * second")) {
3611                 /* Header for a move list -- second line */
3612                 /* Initial board will follow if this is a wild game */
3613                 if (gameInfo.event != NULL) free(gameInfo.event);
3614                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615                 gameInfo.event = StrSave(str);
3616                 /* [HGM] we switched variant. Translate boards if needed. */
3617                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3618                 continue;
3619             }
3620
3621             if (looking_at(buf, &i, "Move  ")) {
3622                 /* Beginning of a move list */
3623                 switch (ics_getting_history) {
3624                   case H_FALSE:
3625                     /* Normally should not happen */
3626                     /* Maybe user hit reset while we were parsing */
3627                     break;
3628                   case H_REQUESTED:
3629                     /* Happens if we are ignoring a move list that is not
3630                      * the one we just requested.  Common if the user
3631                      * tries to observe two games without turning off
3632                      * getMoveList */
3633                     break;
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: nested"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                   case H_GOT_REQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES;
3642                     parse_pos = 0;
3643                     if (oldi > next_out) {
3644                         SendToPlayer(&buf[next_out], oldi - next_out);
3645                     }
3646                     break;
3647                   case H_GOT_UNREQ_HEADER:
3648                     ics_getting_history = H_GETTING_MOVES;
3649                     started = STARTED_MOVES_NOHIDE;
3650                     parse_pos = 0;
3651                     break;
3652                   case H_GOT_UNWANTED_HEADER:
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655                 }
3656                 continue;
3657             }
3658
3659             if (looking_at(buf, &i, "% ") ||
3660                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663                     soughtPending = FALSE;
3664                     seekGraphUp = TRUE;
3665                     DrawSeekGraph();
3666                 }
3667                 if(suppressKibitz) next_out = i;
3668                 savingComment = FALSE;
3669                 suppressKibitz = 0;
3670                 switch (started) {
3671                   case STARTED_MOVES:
3672                   case STARTED_MOVES_NOHIDE:
3673                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674                     parse[parse_pos + i - oldi] = NULLCHAR;
3675                     ParseGameHistory(parse);
3676 #if ZIPPY
3677                     if (appData.zippyPlay && first.initDone) {
3678                         FeedMovesToProgram(&first, forwardMostMove);
3679                         if (gameMode == IcsPlayingWhite) {
3680                             if (WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("black\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, TRUE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, TRUE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         } else if (gameMode == IcsPlayingBlack) {
3704                             if (!WhiteOnMove(forwardMostMove)) {
3705                                 if (first.sendTime) {
3706                                   if (first.useColors) {
3707                                     SendToProgram("white\n", &first);
3708                                   }
3709                                   SendTimeRemaining(&first, FALSE);
3710                                 }
3711                                 if (first.useColors) {
3712                                   SendToProgram("black\n", &first);
3713                                 }
3714                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715                                 first.maybeThinking = TRUE;
3716                             } else {
3717                                 if (first.usePlayother) {
3718                                   if (first.sendTime) {
3719                                     SendTimeRemaining(&first, FALSE);
3720                                   }
3721                                   SendToProgram("playother\n", &first);
3722                                   firstMove = FALSE;
3723                                 } else {
3724                                   firstMove = TRUE;
3725                                 }
3726                             }
3727                         }
3728                     }
3729 #endif
3730                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3731                         /* Moves came from oldmoves or moves command
3732                            while we weren't doing anything else.
3733                            */
3734                         currentMove = forwardMostMove;
3735                         ClearHighlights();/*!!could figure this out*/
3736                         flipView = appData.flipView;
3737                         DrawPosition(TRUE, boards[currentMove]);
3738                         DisplayBothClocks();
3739                         snprintf(str, MSG_SIZ, "%s %s %s",
3740                                 gameInfo.white, _("vs."),  gameInfo.black);
3741                         DisplayTitle(str);
3742                         gameMode = IcsIdle;
3743                     } else {
3744                         /* Moves were history of an active game */
3745                         if (gameInfo.resultDetails != NULL) {
3746                             free(gameInfo.resultDetails);
3747                             gameInfo.resultDetails = NULL;
3748                         }
3749                     }
3750                     HistorySet(parseList, backwardMostMove,
3751                                forwardMostMove, currentMove-1);
3752                     DisplayMove(currentMove - 1);
3753                     if (started == STARTED_MOVES) next_out = i;
3754                     started = STARTED_NONE;
3755                     ics_getting_history = H_FALSE;
3756                     break;
3757
3758                   case STARTED_OBSERVE:
3759                     started = STARTED_NONE;
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                     break;
3763
3764                   default:
3765                     break;
3766                 }
3767                 if(bookHit) { // [HGM] book: simulate book reply
3768                     static char bookMove[MSG_SIZ]; // a bit generous?
3769
3770                     programStats.nodes = programStats.depth = programStats.time =
3771                     programStats.score = programStats.got_only_move = 0;
3772                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3773
3774                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775                     strcat(bookMove, bookHit);
3776                     HandleMachineMove(bookMove, &first);
3777                 }
3778                 continue;
3779             }
3780
3781             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782                  started == STARTED_HOLDINGS ||
3783                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784                 /* Accumulate characters in move list or board */
3785                 parse[parse_pos++] = buf[i];
3786             }
3787
3788             /* Start of game messages.  Mostly we detect start of game
3789                when the first board image arrives.  On some versions
3790                of the ICS, though, we need to do a "refresh" after starting
3791                to observe in order to get the current board right away. */
3792             if (looking_at(buf, &i, "Adding game * to observation list")) {
3793                 started = STARTED_OBSERVE;
3794                 continue;
3795             }
3796
3797             /* Handle auto-observe */
3798             if (appData.autoObserve &&
3799                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3801                 char *player;
3802                 /* Choose the player that was highlighted, if any. */
3803                 if (star_match[0][0] == '\033' ||
3804                     star_match[1][0] != '\033') {
3805                     player = star_match[0];
3806                 } else {
3807                     player = star_match[2];
3808                 }
3809                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810                         ics_prefix, StripHighlightAndTitle(player));
3811                 SendToICS(str);
3812
3813                 /* Save ratings from notify string */
3814                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815                 player1Rating = string_to_rating(star_match[1]);
3816                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817                 player2Rating = string_to_rating(star_match[3]);
3818
3819                 if (appData.debugMode)
3820                   fprintf(debugFP,
3821                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3822                           player1Name, player1Rating,
3823                           player2Name, player2Rating);
3824
3825                 continue;
3826             }
3827
3828             /* Deal with automatic examine mode after a game,
3829                and with IcsObserving -> IcsExamining transition */
3830             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831                 looking_at(buf, &i, "has made you an examiner of game *")) {
3832
3833                 int gamenum = atoi(star_match[0]);
3834                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835                     gamenum == ics_gamenum) {
3836                     /* We were already playing or observing this game;
3837                        no need to refetch history */
3838                     gameMode = IcsExamining;
3839                     if (pausing) {
3840                         pauseExamForwardMostMove = forwardMostMove;
3841                     } else if (currentMove < forwardMostMove) {
3842                         ForwardInner(forwardMostMove);
3843                     }
3844                 } else {
3845                     /* I don't think this case really can happen */
3846                     SendToICS(ics_prefix);
3847                     SendToICS("refresh\n");
3848                 }
3849                 continue;
3850             }
3851
3852             /* Error messages */
3853 //          if (ics_user_moved) {
3854             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855                 if (looking_at(buf, &i, "Illegal move") ||
3856                     looking_at(buf, &i, "Not a legal move") ||
3857                     looking_at(buf, &i, "Your king is in check") ||
3858                     looking_at(buf, &i, "It isn't your turn") ||
3859                     looking_at(buf, &i, "It is not your move")) {
3860                     /* Illegal move */
3861                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862                         currentMove = forwardMostMove-1;
3863                         DisplayMove(currentMove - 1); /* before DMError */
3864                         DrawPosition(FALSE, boards[currentMove]);
3865                         SwitchClocks(forwardMostMove-1); // [HGM] race
3866                         DisplayBothClocks();
3867                     }
3868                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3869                     ics_user_moved = 0;
3870                     continue;
3871                 }
3872             }
3873
3874             if (looking_at(buf, &i, "still have time") ||
3875                 looking_at(buf, &i, "not out of time") ||
3876                 looking_at(buf, &i, "either player is out of time") ||
3877                 looking_at(buf, &i, "has timeseal; checking")) {
3878                 /* We must have called his flag a little too soon */
3879                 whiteFlag = blackFlag = FALSE;
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "added * seconds to") ||
3884                 looking_at(buf, &i, "seconds were added to")) {
3885                 /* Update the clocks */
3886                 SendToICS(ics_prefix);
3887                 SendToICS("refresh\n");
3888                 continue;
3889             }
3890
3891             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892                 ics_clock_paused = TRUE;
3893                 StopClocks();
3894                 continue;
3895             }
3896
3897             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898                 ics_clock_paused = FALSE;
3899                 StartClocks();
3900                 continue;
3901             }
3902
3903             /* Grab player ratings from the Creating: message.
3904                Note we have to check for the special case when
3905                the ICS inserts things like [white] or [black]. */
3906             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3908                 /* star_matches:
3909                    0    player 1 name (not necessarily white)
3910                    1    player 1 rating
3911                    2    empty, white, or black (IGNORED)
3912                    3    player 2 name (not necessarily black)
3913                    4    player 2 rating
3914
3915                    The names/ratings are sorted out when the game
3916                    actually starts (below).
3917                 */
3918                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919                 player1Rating = string_to_rating(star_match[1]);
3920                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921                 player2Rating = string_to_rating(star_match[4]);
3922
3923                 if (appData.debugMode)
3924                   fprintf(debugFP,
3925                           "Ratings from 'Creating:' %s %d, %s %d\n",
3926                           player1Name, player1Rating,
3927                           player2Name, player2Rating);
3928
3929                 continue;
3930             }
3931
3932             /* Improved generic start/end-of-game messages */
3933             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935                 /* If tkind == 0: */
3936                 /* star_match[0] is the game number */
3937                 /*           [1] is the white player's name */
3938                 /*           [2] is the black player's name */
3939                 /* For end-of-game: */
3940                 /*           [3] is the reason for the game end */
3941                 /*           [4] is a PGN end game-token, preceded by " " */
3942                 /* For start-of-game: */
3943                 /*           [3] begins with "Creating" or "Continuing" */
3944                 /*           [4] is " *" or empty (don't care). */
3945                 int gamenum = atoi(star_match[0]);
3946                 char *whitename, *blackname, *why, *endtoken;
3947                 ChessMove endtype = EndOfFile;
3948
3949                 if (tkind == 0) {
3950                   whitename = star_match[1];
3951                   blackname = star_match[2];
3952                   why = star_match[3];
3953                   endtoken = star_match[4];
3954                 } else {
3955                   whitename = star_match[1];
3956                   blackname = star_match[3];
3957                   why = star_match[5];
3958                   endtoken = star_match[6];
3959                 }
3960
3961                 /* Game start messages */
3962                 if (strncmp(why, "Creating ", 9) == 0 ||
3963                     strncmp(why, "Continuing ", 11) == 0) {
3964                     gs_gamenum = gamenum;
3965                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3968 #if ZIPPY
3969                     if (appData.zippyPlay) {
3970                         ZippyGameStart(whitename, blackname);
3971                     }
3972 #endif /*ZIPPY*/
3973                     partnerBoardValid = FALSE; // [HGM] bughouse
3974                     continue;
3975                 }
3976
3977                 /* Game end messages */
3978                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979                     ics_gamenum != gamenum) {
3980                     continue;
3981                 }
3982                 while (endtoken[0] == ' ') endtoken++;
3983                 switch (endtoken[0]) {
3984                   case '*':
3985                   default:
3986                     endtype = GameUnfinished;
3987                     break;
3988                   case '0':
3989                     endtype = BlackWins;
3990                     break;
3991                   case '1':
3992                     if (endtoken[1] == '/')
3993                       endtype = GameIsDrawn;
3994                     else
3995                       endtype = WhiteWins;
3996                     break;
3997                 }
3998                 GameEnds(endtype, why, GE_ICS);
3999 #if ZIPPY
4000                 if (appData.zippyPlay && first.initDone) {
4001                     ZippyGameEnd(endtype, why);
4002                     if (first.pr == NoProc) {
4003                       /* Start the next process early so that we'll
4004                          be ready for the next challenge */
4005                       StartChessProgram(&first);
4006                     }
4007                     /* Send "new" early, in case this command takes
4008                        a long time to finish, so that we'll be ready
4009                        for the next challenge. */
4010                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4011                     Reset(TRUE, TRUE);
4012                 }
4013 #endif /*ZIPPY*/
4014                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4015                 continue;
4016             }
4017
4018             if (looking_at(buf, &i, "Removing game * from observation") ||
4019                 looking_at(buf, &i, "no longer observing game *") ||
4020                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021                 if (gameMode == IcsObserving &&
4022                     atoi(star_match[0]) == ics_gamenum)
4023                   {
4024                       /* icsEngineAnalyze */
4025                       if (appData.icsEngineAnalyze) {
4026                             ExitAnalyzeMode();
4027                             ModeHighlight();
4028                       }
4029                       StopClocks();
4030                       gameMode = IcsIdle;
4031                       ics_gamenum = -1;
4032                       ics_user_moved = FALSE;
4033                   }
4034                 continue;
4035             }
4036
4037             if (looking_at(buf, &i, "no longer examining game *")) {
4038                 if (gameMode == IcsExamining &&
4039                     atoi(star_match[0]) == ics_gamenum)
4040                   {
4041                       gameMode = IcsIdle;
4042                       ics_gamenum = -1;
4043                       ics_user_moved = FALSE;
4044                   }
4045                 continue;
4046             }
4047
4048             /* Advance leftover_start past any newlines we find,
4049                so only partial lines can get reparsed */
4050             if (looking_at(buf, &i, "\n")) {
4051                 prevColor = curColor;
4052                 if (curColor != ColorNormal) {
4053                     if (oldi > next_out) {
4054                         SendToPlayer(&buf[next_out], oldi - next_out);
4055                         next_out = oldi;
4056                     }
4057                     Colorize(ColorNormal, FALSE);
4058                     curColor = ColorNormal;
4059                 }
4060                 if (started == STARTED_BOARD) {
4061                     started = STARTED_NONE;
4062                     parse[parse_pos] = NULLCHAR;
4063                     ParseBoard12(parse);
4064                     ics_user_moved = 0;
4065
4066                     /* Send premove here */
4067                     if (appData.premove) {
4068                       char str[MSG_SIZ];
4069                       if (currentMove == 0 &&
4070                           gameMode == IcsPlayingWhite &&
4071                           appData.premoveWhite) {
4072                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073                         if (appData.debugMode)
4074                           fprintf(debugFP, "Sending premove:\n");
4075                         SendToICS(str);
4076                       } else if (currentMove == 1 &&
4077                                  gameMode == IcsPlayingBlack &&
4078                                  appData.premoveBlack) {
4079                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080                         if (appData.debugMode)
4081                           fprintf(debugFP, "Sending premove:\n");
4082                         SendToICS(str);
4083                       } else if (gotPremove) {
4084                         gotPremove = 0;
4085                         ClearPremoveHighlights();
4086                         if (appData.debugMode)
4087                           fprintf(debugFP, "Sending premove:\n");
4088                           UserMoveEvent(premoveFromX, premoveFromY,
4089                                         premoveToX, premoveToY,
4090                                         premovePromoChar);
4091                       }
4092                     }
4093
4094                     /* Usually suppress following prompt */
4095                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097                         if (looking_at(buf, &i, "*% ")) {
4098                             savingComment = FALSE;
4099                             suppressKibitz = 0;
4100                         }
4101                     }
4102                     next_out = i;
4103                 } else if (started == STARTED_HOLDINGS) {
4104                     int gamenum;
4105                     char new_piece[MSG_SIZ];
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     if (appData.debugMode)
4109                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110                                                         parse, currentMove);
4111                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4112                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113                         if (gameInfo.variant == VariantNormal) {
4114                           /* [HGM] We seem to switch variant during a game!
4115                            * Presumably no holdings were displayed, so we have
4116                            * to move the position two files to the right to
4117                            * create room for them!
4118                            */
4119                           VariantClass newVariant;
4120                           switch(gameInfo.boardWidth) { // base guess on board width
4121                                 case 9:  newVariant = VariantShogi; break;
4122                                 case 10: newVariant = VariantGreat; break;
4123                                 default: newVariant = VariantCrazyhouse; break;
4124                           }
4125                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126                           /* Get a move list just to see the header, which
4127                              will tell us whether this is really bug or zh */
4128                           if (ics_getting_history == H_FALSE) {
4129                             ics_getting_history = H_REQUESTED;
4130                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4131                             SendToICS(str);
4132                           }
4133                         }
4134                         new_piece[0] = NULLCHAR;
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to board holdings area */
4141                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4144 #if ZIPPY
4145                         if (appData.zippyPlay && first.initDone) {
4146                             ZippyHoldings(white_holding, black_holding,
4147                                           new_piece);
4148                         }
4149 #endif /*ZIPPY*/
4150                         if (tinyLayout || smallLayout) {
4151                             char wh[16], bh[16];
4152                             PackHolding(wh, white_holding);
4153                             PackHolding(bh, black_holding);
4154                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155                                     gameInfo.white, gameInfo.black);
4156                         } else {
4157                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158                                     gameInfo.white, white_holding, _("vs."),
4159                                     gameInfo.black, black_holding);
4160                         }
4161                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162                         DrawPosition(FALSE, boards[currentMove]);
4163                         DisplayTitle(str);
4164                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165                         sscanf(parse, "game %d white [%s black [%s <- %s",
4166                                &gamenum, white_holding, black_holding,
4167                                new_piece);
4168                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4169                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4170                         /* [HGM] copy holdings to partner-board holdings area */
4171                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4176                       }
4177                     }
4178                     /* Suppress following prompt */
4179                     if (looking_at(buf, &i, "*% ")) {
4180                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181                         savingComment = FALSE;
4182                         suppressKibitz = 0;
4183                     }
4184                     next_out = i;
4185                 }
4186                 continue;
4187             }
4188
4189             i++;                /* skip unparsed character and loop back */
4190         }
4191
4192         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 //          SendToPlayer(&buf[next_out], i - next_out);
4195             started != STARTED_HOLDINGS && leftover_start > next_out) {
4196             SendToPlayer(&buf[next_out], leftover_start - next_out);
4197             next_out = i;
4198         }
4199
4200         leftover_len = buf_len - leftover_start;
4201         /* if buffer ends with something we couldn't parse,
4202            reparse it after appending the next read */
4203
4204     } else if (count == 0) {
4205         RemoveInputSource(isr);
4206         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4207     } else {
4208         DisplayFatalError(_("Error reading from ICS"), error, 1);
4209     }
4210 }
4211
4212
4213 /* Board style 12 looks like this:
4214
4215    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4216
4217  * The "<12> " is stripped before it gets to this routine.  The two
4218  * trailing 0's (flip state and clock ticking) are later addition, and
4219  * some chess servers may not have them, or may have only the first.
4220  * Additional trailing fields may be added in the future.
4221  */
4222
4223 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4224
4225 #define RELATION_OBSERVING_PLAYED    0
4226 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE      1
4228 #define RELATION_PLAYING_NOTMYMOVE  -1
4229 #define RELATION_EXAMINING           2
4230 #define RELATION_ISOLATED_BOARD     -3
4231 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4232
4233 void
4234 ParseBoard12 (char *string)
4235 {
4236 #if ZIPPY
4237     int i, takeback;
4238     char *bookHit = NULL; // [HGM] book
4239 #endif
4240     GameMode newGameMode;
4241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244     char to_play, board_chars[200];
4245     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246     char black[32], white[32];
4247     Board board;
4248     int prevMove = currentMove;
4249     int ticking = 2;
4250     ChessMove moveType;
4251     int fromX, fromY, toX, toY;
4252     char promoChar;
4253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254     Boolean weird = FALSE, reqFlag = FALSE;
4255
4256     fromX = fromY = toX = toY = -1;
4257
4258     newGame = FALSE;
4259
4260     if (appData.debugMode)
4261       fprintf(debugFP, "Parsing board: %s\n", string);
4262
4263     move_str[0] = NULLCHAR;
4264     elapsed_time[0] = NULLCHAR;
4265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4266         int  i = 0, j;
4267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268             if(string[i] == ' ') { ranks++; files = 0; }
4269             else files++;
4270             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4271             i++;
4272         }
4273         for(j = 0; j <i; j++) board_chars[j] = string[j];
4274         board_chars[i] = '\0';
4275         string += i + 1;
4276     }
4277     n = sscanf(string, PATTERN, &to_play, &double_push,
4278                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279                &gamenum, white, black, &relation, &basetime, &increment,
4280                &white_stren, &black_stren, &white_time, &black_time,
4281                &moveNum, str, elapsed_time, move_str, &ics_flip,
4282                &ticking);
4283
4284     if (n < 21) {
4285         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286         DisplayError(str, 0);
4287         return;
4288     }
4289
4290     /* Convert the move number to internal form */
4291     moveNum = (moveNum - 1) * 2;
4292     if (to_play == 'B') moveNum++;
4293     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4295                         0, 1);
4296       return;
4297     }
4298
4299     switch (relation) {
4300       case RELATION_OBSERVING_PLAYED:
4301       case RELATION_OBSERVING_STATIC:
4302         if (gamenum == -1) {
4303             /* Old ICC buglet */
4304             relation = RELATION_OBSERVING_STATIC;
4305         }
4306         newGameMode = IcsObserving;
4307         break;
4308       case RELATION_PLAYING_MYMOVE:
4309       case RELATION_PLAYING_NOTMYMOVE:
4310         newGameMode =
4311           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312             IcsPlayingWhite : IcsPlayingBlack;
4313         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4314         break;
4315       case RELATION_EXAMINING:
4316         newGameMode = IcsExamining;
4317         break;
4318       case RELATION_ISOLATED_BOARD:
4319       default:
4320         /* Just display this board.  If user was doing something else,
4321            we will forget about it until the next board comes. */
4322         newGameMode = IcsIdle;
4323         break;
4324       case RELATION_STARTING_POSITION:
4325         newGameMode = gameMode;
4326         break;
4327     }
4328
4329     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334       static int lastBgGame = -1;
4335       char *toSqr;
4336       for (k = 0; k < ranks; k++) {
4337         for (j = 0; j < files; j++)
4338           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339         if(gameInfo.holdingsWidth > 1) {
4340              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4342         }
4343       }
4344       CopyBoard(partnerBoard, board);
4345       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349       if(toSqr = strchr(str, '-')) {
4350         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4356       if(twoBoards) {
4357           DisplayWhiteClock(white_time*fac, to_play == 'W');
4358           DisplayBlackClock(black_time*fac, to_play != 'W');
4359           activePartner = to_play;
4360           if(gamenum != lastBgGame) {
4361               char buf[MSG_SIZ];
4362               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4363               DisplayTitle(buf);
4364           }
4365           lastBgGame = gamenum;
4366           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370       if(!twoBoards) DisplayMessage(partnerStatus, "");
4371         partnerBoardValid = TRUE;
4372       return;
4373     }
4374
4375     if(appData.dualBoard && appData.bgObserve) {
4376         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377             SendToICS(ics_prefix), SendToICS("pobserve\n");
4378         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4379             char buf[MSG_SIZ];
4380             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4381             SendToICS(buf);
4382         }
4383     }
4384
4385     /* Modify behavior for initial board display on move listing
4386        of wild games.
4387        */
4388     switch (ics_getting_history) {
4389       case H_FALSE:
4390       case H_REQUESTED:
4391         break;
4392       case H_GOT_REQ_HEADER:
4393       case H_GOT_UNREQ_HEADER:
4394         /* This is the initial position of the current game */
4395         gamenum = ics_gamenum;
4396         moveNum = 0;            /* old ICS bug workaround */
4397         if (to_play == 'B') {
4398           startedFromSetupPosition = TRUE;
4399           blackPlaysFirst = TRUE;
4400           moveNum = 1;
4401           if (forwardMostMove == 0) forwardMostMove = 1;
4402           if (backwardMostMove == 0) backwardMostMove = 1;
4403           if (currentMove == 0) currentMove = 1;
4404         }
4405         newGameMode = gameMode;
4406         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4407         break;
4408       case H_GOT_UNWANTED_HEADER:
4409         /* This is an initial board that we don't want */
4410         return;
4411       case H_GETTING_MOVES:
4412         /* Should not happen */
4413         DisplayError(_("Error gathering move list: extra board"), 0);
4414         ics_getting_history = H_FALSE;
4415         return;
4416     }
4417
4418    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4421      /* [HGM] We seem to have switched variant unexpectedly
4422       * Try to guess new variant from board size
4423       */
4424           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4429           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432           /* Get a move list just to see the header, which
4433              will tell us whether this is really bug or zh */
4434           if (ics_getting_history == H_FALSE) {
4435             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437             SendToICS(str);
4438           }
4439     }
4440
4441     /* Take action if this is the first board of a new game, or of a
4442        different game than is currently being displayed.  */
4443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444         relation == RELATION_ISOLATED_BOARD) {
4445
4446         /* Forget the old game and get the history (if any) of the new one */
4447         if (gameMode != BeginningOfGame) {
4448           Reset(TRUE, TRUE);
4449         }
4450         newGame = TRUE;
4451         if (appData.autoRaiseBoard) BoardToTop();
4452         prevMove = -3;
4453         if (gamenum == -1) {
4454             newGameMode = IcsIdle;
4455         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456                    appData.getMoveList && !reqFlag) {
4457             /* Need to get game history */
4458             ics_getting_history = H_REQUESTED;
4459             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4460             SendToICS(str);
4461         }
4462
4463         /* Initially flip the board to have black on the bottom if playing
4464            black or if the ICS flip flag is set, but let the user change
4465            it with the Flip View button. */
4466         flipView = appData.autoFlipView ?
4467           (newGameMode == IcsPlayingBlack) || ics_flip :
4468           appData.flipView;
4469
4470         /* Done with values from previous mode; copy in new ones */
4471         gameMode = newGameMode;
4472         ModeHighlight();
4473         ics_gamenum = gamenum;
4474         if (gamenum == gs_gamenum) {
4475             int klen = strlen(gs_kind);
4476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478             gameInfo.event = StrSave(str);
4479         } else {
4480             gameInfo.event = StrSave("ICS game");
4481         }
4482         gameInfo.site = StrSave(appData.icsHost);
4483         gameInfo.date = PGNDate();
4484         gameInfo.round = StrSave("-");
4485         gameInfo.white = StrSave(white);
4486         gameInfo.black = StrSave(black);
4487         timeControl = basetime * 60 * 1000;
4488         timeControl_2 = 0;
4489         timeIncrement = increment * 1000;
4490         movesPerSession = 0;
4491         gameInfo.timeControl = TimeControlTagValue();
4492         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496     setbuf(debugFP, NULL);
4497   }
4498
4499         gameInfo.outOfBook = NULL;
4500
4501         /* Do we have the ratings? */
4502         if (strcmp(player1Name, white) == 0 &&
4503             strcmp(player2Name, black) == 0) {
4504             if (appData.debugMode)
4505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506                       player1Rating, player2Rating);
4507             gameInfo.whiteRating = player1Rating;
4508             gameInfo.blackRating = player2Rating;
4509         } else if (strcmp(player2Name, white) == 0 &&
4510                    strcmp(player1Name, black) == 0) {
4511             if (appData.debugMode)
4512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513                       player2Rating, player1Rating);
4514             gameInfo.whiteRating = player2Rating;
4515             gameInfo.blackRating = player1Rating;
4516         }
4517         player1Name[0] = player2Name[0] = NULLCHAR;
4518
4519         /* Silence shouts if requested */
4520         if (appData.quietPlay &&
4521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522             SendToICS(ics_prefix);
4523             SendToICS("set shout 0\n");
4524         }
4525     }
4526
4527     /* Deal with midgame name changes */
4528     if (!newGame) {
4529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530             if (gameInfo.white) free(gameInfo.white);
4531             gameInfo.white = StrSave(white);
4532         }
4533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534             if (gameInfo.black) free(gameInfo.black);
4535             gameInfo.black = StrSave(black);
4536         }
4537     }
4538
4539     /* Throw away game result if anything actually changes in examine mode */
4540     if (gameMode == IcsExamining && !newGame) {
4541         gameInfo.result = GameUnfinished;
4542         if (gameInfo.resultDetails != NULL) {
4543             free(gameInfo.resultDetails);
4544             gameInfo.resultDetails = NULL;
4545         }
4546     }
4547
4548     /* In pausing && IcsExamining mode, we ignore boards coming
4549        in if they are in a different variation than we are. */
4550     if (pauseExamInvalid) return;
4551     if (pausing && gameMode == IcsExamining) {
4552         if (moveNum <= pauseExamForwardMostMove) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557     }
4558
4559   if (appData.debugMode) {
4560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4561   }
4562     /* Parse the board */
4563     for (k = 0; k < ranks; k++) {
4564       for (j = 0; j < files; j++)
4565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566       if(gameInfo.holdingsWidth > 1) {
4567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4569       }
4570     }
4571     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572       board[5][BOARD_RGHT+1] = WhiteAngel;
4573       board[6][BOARD_RGHT+1] = WhiteMarshall;
4574       board[1][0] = BlackMarshall;
4575       board[2][0] = BlackAngel;
4576       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4577     }
4578     CopyBoard(boards[moveNum], board);
4579     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4580     if (moveNum == 0) {
4581         startedFromSetupPosition =
4582           !CompareBoards(board, initialPosition);
4583         if(startedFromSetupPosition)
4584             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4585     }
4586
4587     /* [HGM] Set castling rights. Take the outermost Rooks,
4588        to make it also work for FRC opening positions. Note that board12
4589        is really defective for later FRC positions, as it has no way to
4590        indicate which Rook can castle if they are on the same side of King.
4591        For the initial position we grant rights to the outermost Rooks,
4592        and remember thos rights, and we then copy them on positions
4593        later in an FRC game. This means WB might not recognize castlings with
4594        Rooks that have moved back to their original position as illegal,
4595        but in ICS mode that is not its job anyway.
4596     */
4597     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4599
4600         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601             if(board[0][i] == WhiteRook) j = i;
4602         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604             if(board[0][i] == WhiteRook) j = i;
4605         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4612
4613         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618             if(board[BOARD_HEIGHT-1][k] == bKing)
4619                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620         if(gameInfo.variant == VariantTwoKings) {
4621             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4624         }
4625     } else { int r;
4626         r = boards[moveNum][CASTLING][0] = initialRights[0];
4627         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628         r = boards[moveNum][CASTLING][1] = initialRights[1];
4629         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630         r = boards[moveNum][CASTLING][3] = initialRights[3];
4631         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632         r = boards[moveNum][CASTLING][4] = initialRights[4];
4633         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634         /* wildcastle kludge: always assume King has rights */
4635         r = boards[moveNum][CASTLING][2] = initialRights[2];
4636         r = boards[moveNum][CASTLING][5] = initialRights[5];
4637     }
4638     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639     boards[moveNum][EP_STATUS] = EP_NONE;
4640     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4643
4644
4645     if (ics_getting_history == H_GOT_REQ_HEADER ||
4646         ics_getting_history == H_GOT_UNREQ_HEADER) {
4647         /* This was an initial position from a move list, not
4648            the current position */
4649         return;
4650     }
4651
4652     /* Update currentMove and known move number limits */
4653     newMove = newGame || moveNum > forwardMostMove;
4654
4655     if (newGame) {
4656         forwardMostMove = backwardMostMove = currentMove = moveNum;
4657         if (gameMode == IcsExamining && moveNum == 0) {
4658           /* Workaround for ICS limitation: we are not told the wild
4659              type when starting to examine a game.  But if we ask for
4660              the move list, the move list header will tell us */
4661             ics_getting_history = H_REQUESTED;
4662             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4663             SendToICS(str);
4664         }
4665     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4667 #if ZIPPY
4668         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669         /* [HGM] applied this also to an engine that is silently watching        */
4670         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672             gameInfo.variant == currentlyInitializedVariant) {
4673           takeback = forwardMostMove - moveNum;
4674           for (i = 0; i < takeback; i++) {
4675             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676             SendToProgram("undo\n", &first);
4677           }
4678         }
4679 #endif
4680
4681         forwardMostMove = moveNum;
4682         if (!pausing || currentMove > forwardMostMove)
4683           currentMove = forwardMostMove;
4684     } else {
4685         /* New part of history that is not contiguous with old part */
4686         if (pausing && gameMode == IcsExamining) {
4687             pauseExamInvalid = TRUE;
4688             forwardMostMove = pauseExamForwardMostMove;
4689             return;
4690         }
4691         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4692 #if ZIPPY
4693             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694                 // [HGM] when we will receive the move list we now request, it will be
4695                 // fed to the engine from the first move on. So if the engine is not
4696                 // in the initial position now, bring it there.
4697                 InitChessProgram(&first, 0);
4698             }
4699 #endif
4700             ics_getting_history = H_REQUESTED;
4701             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4702             SendToICS(str);
4703         }
4704         forwardMostMove = backwardMostMove = currentMove = moveNum;
4705     }
4706
4707     /* Update the clocks */
4708     if (strchr(elapsed_time, '.')) {
4709       /* Time is in ms */
4710       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4712     } else {
4713       /* Time is in seconds */
4714       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4716     }
4717
4718
4719 #if ZIPPY
4720     if (appData.zippyPlay && newGame &&
4721         gameMode != IcsObserving && gameMode != IcsIdle &&
4722         gameMode != IcsExamining)
4723       ZippyFirstBoard(moveNum, basetime, increment);
4724 #endif
4725
4726     /* Put the move on the move list, first converting
4727        to canonical algebraic form. */
4728     if (moveNum > 0) {
4729   if (appData.debugMode) {
4730     int f = forwardMostMove;
4731     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735     fprintf(debugFP, "moveNum = %d\n", moveNum);
4736     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737     setbuf(debugFP, NULL);
4738   }
4739         if (moveNum <= backwardMostMove) {
4740             /* We don't know what the board looked like before
4741                this move.  Punt. */
4742           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746         } else if (strcmp(move_str, "none") == 0) {
4747             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748             /* Again, we don't know what the board looked like;
4749                this is really the start of the game. */
4750             parseList[moveNum - 1][0] = NULLCHAR;
4751             moveList[moveNum - 1][0] = NULLCHAR;
4752             backwardMostMove = moveNum;
4753             startedFromSetupPosition = TRUE;
4754             fromX = fromY = toX = toY = -1;
4755         } else {
4756           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757           //                 So we parse the long-algebraic move string in stead of the SAN move
4758           int valid; char buf[MSG_SIZ], *prom;
4759
4760           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762           // str looks something like "Q/a1-a2"; kill the slash
4763           if(str[1] == '/')
4764             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767                 strcat(buf, prom); // long move lacks promo specification!
4768           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769                 if(appData.debugMode)
4770                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771                 safeStrCpy(move_str, buf, MSG_SIZ);
4772           }
4773           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774                                 &fromX, &fromY, &toX, &toY, &promoChar)
4775                || ParseOneMove(buf, moveNum - 1, &moveType,
4776                                 &fromX, &fromY, &toX, &toY, &promoChar);
4777           // end of long SAN patch
4778           if (valid) {
4779             (void) CoordsToAlgebraic(boards[moveNum - 1],
4780                                      PosFlags(moveNum - 1),
4781                                      fromY, fromX, toY, toX, promoChar,
4782                                      parseList[moveNum-1]);
4783             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4784               case MT_NONE:
4785               case MT_STALEMATE:
4786               default:
4787                 break;
4788               case MT_CHECK:
4789                 if(gameInfo.variant != VariantShogi)
4790                     strcat(parseList[moveNum - 1], "+");
4791                 break;
4792               case MT_CHECKMATE:
4793               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794                 strcat(parseList[moveNum - 1], "#");
4795                 break;
4796             }
4797             strcat(parseList[moveNum - 1], " ");
4798             strcat(parseList[moveNum - 1], elapsed_time);
4799             /* currentMoveString is set as a side-effect of ParseOneMove */
4800             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802             strcat(moveList[moveNum - 1], "\n");
4803
4804             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807                 ChessSquare old, new = boards[moveNum][k][j];
4808                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810                   if(old == new) continue;
4811                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812                   else if(new == WhiteWazir || new == BlackWazir) {
4813                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815                       else boards[moveNum][k][j] = old; // preserve type of Gold
4816                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4818               }
4819           } else {
4820             /* Move from ICS was illegal!?  Punt. */
4821             if (appData.debugMode) {
4822               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4824             }
4825             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826             strcat(parseList[moveNum - 1], " ");
4827             strcat(parseList[moveNum - 1], elapsed_time);
4828             moveList[moveNum - 1][0] = NULLCHAR;
4829             fromX = fromY = toX = toY = -1;
4830           }
4831         }
4832   if (appData.debugMode) {
4833     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834     setbuf(debugFP, NULL);
4835   }
4836
4837 #if ZIPPY
4838         /* Send move to chess program (BEFORE animating it). */
4839         if (appData.zippyPlay && !newGame && newMove &&
4840            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4841
4842             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4846                             move_str);
4847                     DisplayError(str, 0);
4848                 } else {
4849                     if (first.sendTime) {
4850                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4851                     }
4852                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853                     if (firstMove && !bookHit) {
4854                         firstMove = FALSE;
4855                         if (first.useColors) {
4856                           SendToProgram(gameMode == IcsPlayingWhite ?
4857                                         "white\ngo\n" :
4858                                         "black\ngo\n", &first);
4859                         } else {
4860                           SendToProgram("go\n", &first);
4861                         }
4862                         first.maybeThinking = TRUE;
4863                     }
4864                 }
4865             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866               if (moveList[moveNum - 1][0] == NULLCHAR) {
4867                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868                 DisplayError(str, 0);
4869               } else {
4870                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871                 SendMoveToProgram(moveNum - 1, &first);
4872               }
4873             }
4874         }
4875 #endif
4876     }
4877
4878     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879         /* If move comes from a remote source, animate it.  If it
4880            isn't remote, it will have already been animated. */
4881         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4883         }
4884         if (!pausing && appData.highlightLastMove) {
4885             SetHighlights(fromX, fromY, toX, toY);
4886         }
4887     }
4888
4889     /* Start the clocks */
4890     whiteFlag = blackFlag = FALSE;
4891     appData.clockMode = !(basetime == 0 && increment == 0);
4892     if (ticking == 0) {
4893       ics_clock_paused = TRUE;
4894       StopClocks();
4895     } else if (ticking == 1) {
4896       ics_clock_paused = FALSE;
4897     }
4898     if (gameMode == IcsIdle ||
4899         relation == RELATION_OBSERVING_STATIC ||
4900         relation == RELATION_EXAMINING ||
4901         ics_clock_paused)
4902       DisplayBothClocks();
4903     else
4904       StartClocks();
4905
4906     /* Display opponents and material strengths */
4907     if (gameInfo.variant != VariantBughouse &&
4908         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909         if (tinyLayout || smallLayout) {
4910             if(gameInfo.variant == VariantNormal)
4911               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4913                     basetime, increment);
4914             else
4915               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4917                     basetime, increment, (int) gameInfo.variant);
4918         } else {
4919             if(gameInfo.variant == VariantNormal)
4920               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922                     basetime, increment);
4923             else
4924               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926                     basetime, increment, VariantName(gameInfo.variant));
4927         }
4928         DisplayTitle(str);
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4931   }
4932     }
4933
4934
4935     /* Display the board */
4936     if (!pausing && !appData.noGUI) {
4937
4938       if (appData.premove)
4939           if (!gotPremove ||
4940              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942               ClearPremoveHighlights();
4943
4944       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946       DrawPosition(j, boards[currentMove]);
4947
4948       DisplayMove(moveNum - 1);
4949       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4952         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4953       }
4954     }
4955
4956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4957 #if ZIPPY
4958     if(bookHit) { // [HGM] book: simulate book reply
4959         static char bookMove[MSG_SIZ]; // a bit generous?
4960
4961         programStats.nodes = programStats.depth = programStats.time =
4962         programStats.score = programStats.got_only_move = 0;
4963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4964
4965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966         strcat(bookMove, bookHit);
4967         HandleMachineMove(bookMove, &first);
4968     }
4969 #endif
4970 }
4971
4972 void
4973 GetMoveListEvent ()
4974 {
4975     char buf[MSG_SIZ];
4976     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977         ics_getting_history = H_REQUESTED;
4978         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4979         SendToICS(buf);
4980     }
4981 }
4982
4983 void
4984 SendToBoth (char *msg)
4985 {   // to make it easy to keep two engines in step in dual analysis
4986     SendToProgram(msg, &first);
4987     if(second.analyzing) SendToProgram(msg, &second);
4988 }
4989
4990 void
4991 AnalysisPeriodicEvent (int force)
4992 {
4993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994          && !force) || !appData.periodicUpdates)
4995       return;
4996
4997     /* Send . command to Crafty to collect stats */
4998     SendToBoth(".\n");
4999
5000     /* Don't send another until we get a response (this makes
5001        us stop sending to old Crafty's which don't understand
5002        the "." command (sending illegal cmds resets node count & time,
5003        which looks bad)) */
5004     programStats.ok_to_send = 0;
5005 }
5006
5007 void
5008 ics_update_width (int new_width)
5009 {
5010         ics_printf("set width %d\n", new_width);
5011 }
5012
5013 void
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5015 {
5016     char buf[MSG_SIZ];
5017
5018     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChu) {
5020             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5021             SendToProgram(buf, cps);
5022             return;
5023         }
5024         // null move in variant where engine does not understand it (for analysis purposes)
5025         SendBoard(cps, moveNum + 1); // send position after move in stead.
5026         return;
5027     }
5028     if (cps->useUsermove) {
5029       SendToProgram("usermove ", cps);
5030     }
5031     if (cps->useSAN) {
5032       char *space;
5033       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5034         int len = space - parseList[moveNum];
5035         memcpy(buf, parseList[moveNum], len);
5036         buf[len++] = '\n';
5037         buf[len] = NULLCHAR;
5038       } else {
5039         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5040       }
5041       SendToProgram(buf, cps);
5042     } else {
5043       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5044         AlphaRank(moveList[moveNum], 4);
5045         SendToProgram(moveList[moveNum], cps);
5046         AlphaRank(moveList[moveNum], 4); // and back
5047       } else
5048       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5049        * the engine. It would be nice to have a better way to identify castle
5050        * moves here. */
5051       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5052                                                                          && cps->useOOCastle) {
5053         int fromX = moveList[moveNum][0] - AAA;
5054         int fromY = moveList[moveNum][1] - ONE;
5055         int toX = moveList[moveNum][2] - AAA;
5056         int toY = moveList[moveNum][3] - ONE;
5057         if((boards[moveNum][fromY][fromX] == WhiteKing
5058             && boards[moveNum][toY][toX] == WhiteRook)
5059            || (boards[moveNum][fromY][fromX] == BlackKing
5060                && boards[moveNum][toY][toX] == BlackRook)) {
5061           if(toX > fromX) SendToProgram("O-O\n", cps);
5062           else SendToProgram("O-O-O\n", cps);
5063         }
5064         else SendToProgram(moveList[moveNum], cps);
5065       } else
5066       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5071           SendToProgram(buf, cps);
5072       } else
5073       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5074         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5075           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5076           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5077                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5078         } else
5079           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5080                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5081         SendToProgram(buf, cps);
5082       }
5083       else SendToProgram(moveList[moveNum], cps);
5084       /* End of additions by Tord */
5085     }
5086
5087     /* [HGM] setting up the opening has brought engine in force mode! */
5088     /*       Send 'go' if we are in a mode where machine should play. */
5089     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5090         (gameMode == TwoMachinesPlay   ||
5091 #if ZIPPY
5092          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5093 #endif
5094          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5095         SendToProgram("go\n", cps);
5096   if (appData.debugMode) {
5097     fprintf(debugFP, "(extra)\n");
5098   }
5099     }
5100     setboardSpoiledMachineBlack = 0;
5101 }
5102
5103 void
5104 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5105 {
5106     char user_move[MSG_SIZ];
5107     char suffix[4];
5108
5109     if(gameInfo.variant == VariantSChess && promoChar) {
5110         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5111         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5112     } else suffix[0] = NULLCHAR;
5113
5114     switch (moveType) {
5115       default:
5116         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5117                 (int)moveType, fromX, fromY, toX, toY);
5118         DisplayError(user_move + strlen("say "), 0);
5119         break;
5120       case WhiteKingSideCastle:
5121       case BlackKingSideCastle:
5122       case WhiteQueenSideCastleWild:
5123       case BlackQueenSideCastleWild:
5124       /* PUSH Fabien */
5125       case WhiteHSideCastleFR:
5126       case BlackHSideCastleFR:
5127       /* POP Fabien */
5128         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5129         break;
5130       case WhiteQueenSideCastle:
5131       case BlackQueenSideCastle:
5132       case WhiteKingSideCastleWild:
5133       case BlackKingSideCastleWild:
5134       /* PUSH Fabien */
5135       case WhiteASideCastleFR:
5136       case BlackASideCastleFR:
5137       /* POP Fabien */
5138         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5139         break;
5140       case WhiteNonPromotion:
5141       case BlackNonPromotion:
5142         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5143         break;
5144       case WhitePromotion:
5145       case BlackPromotion:
5146         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5147            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5148           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5149                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5150                 PieceToChar(WhiteFerz));
5151         else if(gameInfo.variant == VariantGreat)
5152           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5153                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5154                 PieceToChar(WhiteMan));
5155         else
5156           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5157                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5158                 promoChar);
5159         break;
5160       case WhiteDrop:
5161       case BlackDrop:
5162       drop:
5163         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5164                  ToUpper(PieceToChar((ChessSquare) fromX)),
5165                  AAA + toX, ONE + toY);
5166         break;
5167       case IllegalMove:  /* could be a variant we don't quite understand */
5168         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5169       case NormalMove:
5170       case WhiteCapturesEnPassant:
5171       case BlackCapturesEnPassant:
5172         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5173                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5174         break;
5175     }
5176     SendToICS(user_move);
5177     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5178         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5179 }
5180
5181 void
5182 UploadGameEvent ()
5183 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5184     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5185     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5186     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5187       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5188       return;
5189     }
5190     if(gameMode != IcsExamining) { // is this ever not the case?
5191         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5192
5193         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5194           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5195         } else { // on FICS we must first go to general examine mode
5196           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5197         }
5198         if(gameInfo.variant != VariantNormal) {
5199             // try figure out wild number, as xboard names are not always valid on ICS
5200             for(i=1; i<=36; i++) {
5201               snprintf(buf, MSG_SIZ, "wild/%d", i);
5202                 if(StringToVariant(buf) == gameInfo.variant) break;
5203             }
5204             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5205             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5206             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5207         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5208         SendToICS(ics_prefix);
5209         SendToICS(buf);
5210         if(startedFromSetupPosition || backwardMostMove != 0) {
5211           fen = PositionToFEN(backwardMostMove, NULL, 1);
5212           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5213             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5214             SendToICS(buf);
5215           } else { // FICS: everything has to set by separate bsetup commands
5216             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5217             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5218             SendToICS(buf);
5219             if(!WhiteOnMove(backwardMostMove)) {
5220                 SendToICS("bsetup tomove black\n");
5221             }
5222             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5223             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5224             SendToICS(buf);
5225             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5226             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5227             SendToICS(buf);
5228             i = boards[backwardMostMove][EP_STATUS];
5229             if(i >= 0) { // set e.p.
5230               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5231                 SendToICS(buf);
5232             }
5233             bsetup++;
5234           }
5235         }
5236       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5237             SendToICS("bsetup done\n"); // switch to normal examining.
5238     }
5239     for(i = backwardMostMove; i<last; i++) {
5240         char buf[20];
5241         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5242         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5243             int len = strlen(moveList[i]);
5244             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5245             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5246         }
5247         SendToICS(buf);
5248     }
5249     SendToICS(ics_prefix);
5250     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5251 }
5252
5253 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5254
5255 void
5256 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5257 {
5258     if (rf == DROP_RANK) {
5259       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5260       sprintf(move, "%c@%c%c\n",
5261                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5262     } else {
5263         if (promoChar == 'x' || promoChar == NULLCHAR) {
5264           sprintf(move, "%c%c%c%c\n",
5265                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5266           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5267         } else {
5268             sprintf(move, "%c%c%c%c%c\n",
5269                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5270         }
5271     }
5272 }
5273
5274 void
5275 ProcessICSInitScript (FILE *f)
5276 {
5277     char buf[MSG_SIZ];
5278
5279     while (fgets(buf, MSG_SIZ, f)) {
5280         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5281     }
5282
5283     fclose(f);
5284 }
5285
5286
5287 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5288 static ClickType lastClickType;
5289
5290 void
5291 Sweep (int step)
5292 {
5293     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5294     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5295     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5296     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5297     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5298     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5299     do {
5300         promoSweep -= step;
5301         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5302         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5303         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5304         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5305         if(!step) step = -1;
5306     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5307             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5308             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5309     if(toX >= 0) {
5310         int victim = boards[currentMove][toY][toX];
5311         boards[currentMove][toY][toX] = promoSweep;
5312         DrawPosition(FALSE, boards[currentMove]);
5313         boards[currentMove][toY][toX] = victim;
5314     } else
5315     ChangeDragPiece(promoSweep);
5316 }
5317
5318 int
5319 PromoScroll (int x, int y)
5320 {
5321   int step = 0;
5322
5323   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5324   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5325   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5326   if(!step) return FALSE;
5327   lastX = x; lastY = y;
5328   if((promoSweep < BlackPawn) == flipView) step = -step;
5329   if(step > 0) selectFlag = 1;
5330   if(!selectFlag) Sweep(step);
5331   return FALSE;
5332 }
5333
5334 void
5335 NextPiece (int step)
5336 {
5337     ChessSquare piece = boards[currentMove][toY][toX];
5338     do {
5339         pieceSweep -= step;
5340         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5341         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5342         if(!step) step = -1;
5343     } while(PieceToChar(pieceSweep) == '.');
5344     boards[currentMove][toY][toX] = pieceSweep;
5345     DrawPosition(FALSE, boards[currentMove]);
5346     boards[currentMove][toY][toX] = piece;
5347 }
5348 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5349 void
5350 AlphaRank (char *move, int n)
5351 {
5352 //    char *p = move, c; int x, y;
5353
5354     if (appData.debugMode) {
5355         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5356     }
5357
5358     if(move[1]=='*' &&
5359        move[2]>='0' && move[2]<='9' &&
5360        move[3]>='a' && move[3]<='x'    ) {
5361         move[1] = '@';
5362         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5363         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5364     } else
5365     if(move[0]>='0' && move[0]<='9' &&
5366        move[1]>='a' && move[1]<='x' &&
5367        move[2]>='0' && move[2]<='9' &&
5368        move[3]>='a' && move[3]<='x'    ) {
5369         /* input move, Shogi -> normal */
5370         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5371         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5372         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5373         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5374     } else
5375     if(move[1]=='@' &&
5376        move[3]>='0' && move[3]<='9' &&
5377        move[2]>='a' && move[2]<='x'    ) {
5378         move[1] = '*';
5379         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5380         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5381     } else
5382     if(
5383        move[0]>='a' && move[0]<='x' &&
5384        move[3]>='0' && move[3]<='9' &&
5385        move[2]>='a' && move[2]<='x'    ) {
5386          /* output move, normal -> Shogi */
5387         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5388         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5389         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5390         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5391         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5392     }
5393     if (appData.debugMode) {
5394         fprintf(debugFP, "   out = '%s'\n", move);
5395     }
5396 }
5397
5398 char yy_textstr[8000];
5399
5400 /* Parser for moves from gnuchess, ICS, or user typein box */
5401 Boolean
5402 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5403 {
5404     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5405
5406     switch (*moveType) {
5407       case WhitePromotion:
5408       case BlackPromotion:
5409       case WhiteNonPromotion:
5410       case BlackNonPromotion:
5411       case NormalMove:
5412       case FirstLeg:
5413       case WhiteCapturesEnPassant:
5414       case BlackCapturesEnPassant:
5415       case WhiteKingSideCastle:
5416       case WhiteQueenSideCastle:
5417       case BlackKingSideCastle:
5418       case BlackQueenSideCastle:
5419       case WhiteKingSideCastleWild:
5420       case WhiteQueenSideCastleWild:
5421       case BlackKingSideCastleWild:
5422       case BlackQueenSideCastleWild:
5423       /* Code added by Tord: */
5424       case WhiteHSideCastleFR:
5425       case WhiteASideCastleFR:
5426       case BlackHSideCastleFR:
5427       case BlackASideCastleFR:
5428       /* End of code added by Tord */
5429       case IllegalMove:         /* bug or odd chess variant */
5430         *fromX = currentMoveString[0] - AAA;
5431         *fromY = currentMoveString[1] - ONE;
5432         *toX = currentMoveString[2] - AAA;
5433         *toY = currentMoveString[3] - ONE;
5434         *promoChar = currentMoveString[4];
5435         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5436             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5437     if (appData.debugMode) {
5438         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5439     }
5440             *fromX = *fromY = *toX = *toY = 0;
5441             return FALSE;
5442         }
5443         if (appData.testLegality) {
5444           return (*moveType != IllegalMove);
5445         } else {
5446           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5447                          // [HGM] lion: if this is a double move we are less critical
5448                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5449         }
5450
5451       case WhiteDrop:
5452       case BlackDrop:
5453         *fromX = *moveType == WhiteDrop ?
5454           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5455           (int) CharToPiece(ToLower(currentMoveString[0]));
5456         *fromY = DROP_RANK;
5457         *toX = currentMoveString[2] - AAA;
5458         *toY = currentMoveString[3] - ONE;
5459         *promoChar = NULLCHAR;
5460         return TRUE;
5461
5462       case AmbiguousMove:
5463       case ImpossibleMove:
5464       case EndOfFile:
5465       case ElapsedTime:
5466       case Comment:
5467       case PGNTag:
5468       case NAG:
5469       case WhiteWins:
5470       case BlackWins:
5471       case GameIsDrawn:
5472       default:
5473     if (appData.debugMode) {
5474         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5475     }
5476         /* bug? */
5477         *fromX = *fromY = *toX = *toY = 0;
5478         *promoChar = NULLCHAR;
5479         return FALSE;
5480     }
5481 }
5482
5483 Boolean pushed = FALSE;
5484 char *lastParseAttempt;
5485
5486 void
5487 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5488 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5489   int fromX, fromY, toX, toY; char promoChar;
5490   ChessMove moveType;
5491   Boolean valid;
5492   int nr = 0;
5493
5494   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5495   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5496     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5497     pushed = TRUE;
5498   }
5499   endPV = forwardMostMove;
5500   do {
5501     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5502     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5503     lastParseAttempt = pv;
5504     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5505     if(!valid && nr == 0 &&
5506        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5507         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5508         // Hande case where played move is different from leading PV move
5509         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5510         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5511         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5512         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5513           endPV += 2; // if position different, keep this
5514           moveList[endPV-1][0] = fromX + AAA;
5515           moveList[endPV-1][1] = fromY + ONE;
5516           moveList[endPV-1][2] = toX + AAA;
5517           moveList[endPV-1][3] = toY + ONE;
5518           parseList[endPV-1][0] = NULLCHAR;
5519           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5520         }
5521       }
5522     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5523     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5524     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5525     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5526         valid++; // allow comments in PV
5527         continue;
5528     }
5529     nr++;
5530     if(endPV+1 > framePtr) break; // no space, truncate
5531     if(!valid) break;
5532     endPV++;
5533     CopyBoard(boards[endPV], boards[endPV-1]);
5534     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5535     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5536     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5537     CoordsToAlgebraic(boards[endPV - 1],
5538                              PosFlags(endPV - 1),
5539                              fromY, fromX, toY, toX, promoChar,
5540                              parseList[endPV - 1]);
5541   } while(valid);
5542   if(atEnd == 2) return; // used hidden, for PV conversion
5543   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5544   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5545   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5546                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5547   DrawPosition(TRUE, boards[currentMove]);
5548 }
5549
5550 int
5551 MultiPV (ChessProgramState *cps)
5552 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5553         int i;
5554         for(i=0; i<cps->nrOptions; i++)
5555             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5556                 return i;
5557         return -1;
5558 }
5559
5560 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5561
5562 Boolean
5563 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5564 {
5565         int startPV, multi, lineStart, origIndex = index;
5566         char *p, buf2[MSG_SIZ];
5567         ChessProgramState *cps = (pane ? &second : &first);
5568
5569         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5570         lastX = x; lastY = y;
5571         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5572         lineStart = startPV = index;
5573         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5574         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5575         index = startPV;
5576         do{ while(buf[index] && buf[index] != '\n') index++;
5577         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5578         buf[index] = 0;
5579         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5580                 int n = cps->option[multi].value;
5581                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5582                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5583                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5584                 cps->option[multi].value = n;
5585                 *start = *end = 0;
5586                 return FALSE;
5587         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5588                 ExcludeClick(origIndex - lineStart);
5589                 return FALSE;
5590         }
5591         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5592         *start = startPV; *end = index-1;
5593         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5594         return TRUE;
5595 }
5596
5597 char *
5598 PvToSAN (char *pv)
5599 {
5600         static char buf[10*MSG_SIZ];
5601         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5602         *buf = NULLCHAR;
5603         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5604         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5605         for(i = forwardMostMove; i<endPV; i++){
5606             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5607             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5608             k += strlen(buf+k);
5609         }
5610         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5611         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5612         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5613         endPV = savedEnd;
5614         return buf;
5615 }
5616
5617 Boolean
5618 LoadPV (int x, int y)
5619 { // called on right mouse click to load PV
5620   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5621   lastX = x; lastY = y;
5622   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5623   extendGame = FALSE;
5624   return TRUE;
5625 }
5626
5627 void
5628 UnLoadPV ()
5629 {
5630   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5631   if(endPV < 0) return;
5632   if(appData.autoCopyPV) CopyFENToClipboard();
5633   endPV = -1;
5634   if(extendGame && currentMove > forwardMostMove) {
5635         Boolean saveAnimate = appData.animate;
5636         if(pushed) {
5637             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5638                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5639             } else storedGames--; // abandon shelved tail of original game
5640         }
5641         pushed = FALSE;
5642         forwardMostMove = currentMove;
5643         currentMove = oldFMM;
5644         appData.animate = FALSE;
5645         ToNrEvent(forwardMostMove);
5646         appData.animate = saveAnimate;
5647   }
5648   currentMove = forwardMostMove;
5649   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5650   ClearPremoveHighlights();
5651   DrawPosition(TRUE, boards[currentMove]);
5652 }
5653
5654 void
5655 MovePV (int x, int y, int h)
5656 { // step through PV based on mouse coordinates (called on mouse move)
5657   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5658
5659   // we must somehow check if right button is still down (might be released off board!)
5660   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5661   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5662   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5663   if(!step) return;
5664   lastX = x; lastY = y;
5665
5666   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5667   if(endPV < 0) return;
5668   if(y < margin) step = 1; else
5669   if(y > h - margin) step = -1;
5670   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5671   currentMove += step;
5672   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5673   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5674                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5675   DrawPosition(FALSE, boards[currentMove]);
5676 }
5677
5678
5679 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5680 // All positions will have equal probability, but the current method will not provide a unique
5681 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5682 #define DARK 1
5683 #define LITE 2
5684 #define ANY 3
5685
5686 int squaresLeft[4];
5687 int piecesLeft[(int)BlackPawn];
5688 int seed, nrOfShuffles;
5689
5690 void
5691 GetPositionNumber ()
5692 {       // sets global variable seed
5693         int i;
5694
5695         seed = appData.defaultFrcPosition;
5696         if(seed < 0) { // randomize based on time for negative FRC position numbers
5697                 for(i=0; i<50; i++) seed += random();
5698                 seed = random() ^ random() >> 8 ^ random() << 8;
5699                 if(seed<0) seed = -seed;
5700         }
5701 }
5702
5703 int
5704 put (Board board, int pieceType, int rank, int n, int shade)
5705 // put the piece on the (n-1)-th empty squares of the given shade
5706 {
5707         int i;
5708
5709         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5710                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5711                         board[rank][i] = (ChessSquare) pieceType;
5712                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5713                         squaresLeft[ANY]--;
5714                         piecesLeft[pieceType]--;
5715                         return i;
5716                 }
5717         }
5718         return -1;
5719 }
5720
5721
5722 void
5723 AddOnePiece (Board board, int pieceType, int rank, int shade)
5724 // calculate where the next piece goes, (any empty square), and put it there
5725 {
5726         int i;
5727
5728         i = seed % squaresLeft[shade];
5729         nrOfShuffles *= squaresLeft[shade];
5730         seed /= squaresLeft[shade];
5731         put(board, pieceType, rank, i, shade);
5732 }
5733
5734 void
5735 AddTwoPieces (Board board, int pieceType, int rank)
5736 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5737 {
5738         int i, n=squaresLeft[ANY], j=n-1, k;
5739
5740         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5741         i = seed % k;  // pick one
5742         nrOfShuffles *= k;
5743         seed /= k;
5744         while(i >= j) i -= j--;
5745         j = n - 1 - j; i += j;
5746         put(board, pieceType, rank, j, ANY);
5747         put(board, pieceType, rank, i, ANY);
5748 }
5749
5750 void
5751 SetUpShuffle (Board board, int number)
5752 {
5753         int i, p, first=1;
5754
5755         GetPositionNumber(); nrOfShuffles = 1;
5756
5757         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5758         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5759         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5760
5761         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5762
5763         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5764             p = (int) board[0][i];
5765             if(p < (int) BlackPawn) piecesLeft[p] ++;
5766             board[0][i] = EmptySquare;
5767         }
5768
5769         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5770             // shuffles restricted to allow normal castling put KRR first
5771             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5772                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5773             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5774                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5775             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5776                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5777             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5778                 put(board, WhiteRook, 0, 0, ANY);
5779             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5780         }
5781
5782         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5783             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5784             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5785                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5786                 while(piecesLeft[p] >= 2) {
5787                     AddOnePiece(board, p, 0, LITE);
5788                     AddOnePiece(board, p, 0, DARK);
5789                 }
5790                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5791             }
5792
5793         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5794             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5795             // but we leave King and Rooks for last, to possibly obey FRC restriction
5796             if(p == (int)WhiteRook) continue;
5797             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5798             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5799         }
5800
5801         // now everything is placed, except perhaps King (Unicorn) and Rooks
5802
5803         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5804             // Last King gets castling rights
5805             while(piecesLeft[(int)WhiteUnicorn]) {
5806                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5807                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5808             }
5809
5810             while(piecesLeft[(int)WhiteKing]) {
5811                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5812                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5813             }
5814
5815
5816         } else {
5817             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5818             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5819         }
5820
5821         // Only Rooks can be left; simply place them all
5822         while(piecesLeft[(int)WhiteRook]) {
5823                 i = put(board, WhiteRook, 0, 0, ANY);
5824                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5825                         if(first) {
5826                                 first=0;
5827                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5828                         }
5829                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5830                 }
5831         }
5832         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5833             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5834         }
5835
5836         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5837 }
5838
5839 int
5840 SetCharTable (char *table, const char * map)
5841 /* [HGM] moved here from winboard.c because of its general usefulness */
5842 /*       Basically a safe strcpy that uses the last character as King */
5843 {
5844     int result = FALSE; int NrPieces;
5845
5846     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5847                     && NrPieces >= 12 && !(NrPieces&1)) {
5848         int i; /* [HGM] Accept even length from 12 to 34 */
5849
5850         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5851         for( i=0; i<NrPieces/2-1; i++ ) {
5852             table[i] = map[i];
5853             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5854         }
5855         table[(int) WhiteKing]  = map[NrPieces/2-1];
5856         table[(int) BlackKing]  = map[NrPieces-1];
5857
5858         result = TRUE;
5859     }
5860
5861     return result;
5862 }
5863
5864 void
5865 Prelude (Board board)
5866 {       // [HGM] superchess: random selection of exo-pieces
5867         int i, j, k; ChessSquare p;
5868         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5869
5870         GetPositionNumber(); // use FRC position number
5871
5872         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5873             SetCharTable(pieceToChar, appData.pieceToCharTable);
5874             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5875                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5876         }
5877
5878         j = seed%4;                 seed /= 4;
5879         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5880         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5881         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5882         j = seed%3 + (seed%3 >= j); seed /= 3;
5883         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5884         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5885         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5886         j = seed%3;                 seed /= 3;
5887         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5888         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5889         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5890         j = seed%2 + (seed%2 >= j); seed /= 2;
5891         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5892         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5893         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5894         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5895         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5896         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5897         put(board, exoPieces[0],    0, 0, ANY);
5898         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5899 }
5900
5901 void
5902 InitPosition (int redraw)
5903 {
5904     ChessSquare (* pieces)[BOARD_FILES];
5905     int i, j, pawnRow=1, pieceRows=1, overrule,
5906     oldx = gameInfo.boardWidth,
5907     oldy = gameInfo.boardHeight,
5908     oldh = gameInfo.holdingsWidth;
5909     static int oldv;
5910
5911     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5912
5913     /* [AS] Initialize pv info list [HGM] and game status */
5914     {
5915         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5916             pvInfoList[i].depth = 0;
5917             boards[i][EP_STATUS] = EP_NONE;
5918             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5919         }
5920
5921         initialRulePlies = 0; /* 50-move counter start */
5922
5923         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5924         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5925     }
5926
5927
5928     /* [HGM] logic here is completely changed. In stead of full positions */
5929     /* the initialized data only consist of the two backranks. The switch */
5930     /* selects which one we will use, which is than copied to the Board   */
5931     /* initialPosition, which for the rest is initialized by Pawns and    */
5932     /* empty squares. This initial position is then copied to boards[0],  */
5933     /* possibly after shuffling, so that it remains available.            */
5934
5935     gameInfo.holdingsWidth = 0; /* default board sizes */
5936     gameInfo.boardWidth    = 8;
5937     gameInfo.boardHeight   = 8;
5938     gameInfo.holdingsSize  = 0;
5939     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5940     for(i=0; i<BOARD_FILES-2; i++)
5941       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5942     initialPosition[EP_STATUS] = EP_NONE;
5943     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5944     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5945          SetCharTable(pieceNickName, appData.pieceNickNames);
5946     else SetCharTable(pieceNickName, "............");
5947     pieces = FIDEArray;
5948
5949     switch (gameInfo.variant) {
5950     case VariantFischeRandom:
5951       shuffleOpenings = TRUE;
5952     default:
5953       break;
5954     case VariantShatranj:
5955       pieces = ShatranjArray;
5956       nrCastlingRights = 0;
5957       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5958       break;
5959     case VariantMakruk:
5960       pieces = makrukArray;
5961       nrCastlingRights = 0;
5962       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5963       break;
5964     case VariantASEAN:
5965       pieces = aseanArray;
5966       nrCastlingRights = 0;
5967       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5968       break;
5969     case VariantTwoKings:
5970       pieces = twoKingsArray;
5971       break;
5972     case VariantGrand:
5973       pieces = GrandArray;
5974       nrCastlingRights = 0;
5975       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5976       gameInfo.boardWidth = 10;
5977       gameInfo.boardHeight = 10;
5978       gameInfo.holdingsSize = 7;
5979       break;
5980     case VariantCapaRandom:
5981       shuffleOpenings = TRUE;
5982     case VariantCapablanca:
5983       pieces = CapablancaArray;
5984       gameInfo.boardWidth = 10;
5985       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5986       break;
5987     case VariantGothic:
5988       pieces = GothicArray;
5989       gameInfo.boardWidth = 10;
5990       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5991       break;
5992     case VariantSChess:
5993       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5994       gameInfo.holdingsSize = 7;
5995       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5996       break;
5997     case VariantJanus:
5998       pieces = JanusArray;
5999       gameInfo.boardWidth = 10;
6000       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6001       nrCastlingRights = 6;
6002         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6003         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6004         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6005         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6006         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6007         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6008       break;
6009     case VariantFalcon:
6010       pieces = FalconArray;
6011       gameInfo.boardWidth = 10;
6012       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6013       break;
6014     case VariantXiangqi:
6015       pieces = XiangqiArray;
6016       gameInfo.boardWidth  = 9;
6017       gameInfo.boardHeight = 10;
6018       nrCastlingRights = 0;
6019       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6020       break;
6021     case VariantShogi:
6022       pieces = ShogiArray;
6023       gameInfo.boardWidth  = 9;
6024       gameInfo.boardHeight = 9;
6025       gameInfo.holdingsSize = 7;
6026       nrCastlingRights = 0;
6027       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6028       break;
6029     case VariantChu:
6030       pieces = ChuArray; pieceRows = 3;
6031       gameInfo.boardWidth  = 12;
6032       gameInfo.boardHeight = 12;
6033       nrCastlingRights = 0;
6034       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6035                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6036       break;
6037     case VariantCourier:
6038       pieces = CourierArray;
6039       gameInfo.boardWidth  = 12;
6040       nrCastlingRights = 0;
6041       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6042       break;
6043     case VariantKnightmate:
6044       pieces = KnightmateArray;
6045       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6046       break;
6047     case VariantSpartan:
6048       pieces = SpartanArray;
6049       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6050       break;
6051     case VariantLion:
6052       pieces = lionArray;
6053       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6054       break;
6055     case VariantFairy:
6056       pieces = fairyArray;
6057       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6058       break;
6059     case VariantGreat:
6060       pieces = GreatArray;
6061       gameInfo.boardWidth = 10;
6062       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6063       gameInfo.holdingsSize = 8;
6064       break;
6065     case VariantSuper:
6066       pieces = FIDEArray;
6067       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6068       gameInfo.holdingsSize = 8;
6069       startedFromSetupPosition = TRUE;
6070       break;
6071     case VariantCrazyhouse:
6072     case VariantBughouse:
6073       pieces = FIDEArray;
6074       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6075       gameInfo.holdingsSize = 5;
6076       break;
6077     case VariantWildCastle:
6078       pieces = FIDEArray;
6079       /* !!?shuffle with kings guaranteed to be on d or e file */
6080       shuffleOpenings = 1;
6081       break;
6082     case VariantNoCastle:
6083       pieces = FIDEArray;
6084       nrCastlingRights = 0;
6085       /* !!?unconstrained back-rank shuffle */
6086       shuffleOpenings = 1;
6087       break;
6088     }
6089
6090     overrule = 0;
6091     if(appData.NrFiles >= 0) {
6092         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6093         gameInfo.boardWidth = appData.NrFiles;
6094     }
6095     if(appData.NrRanks >= 0) {
6096         gameInfo.boardHeight = appData.NrRanks;
6097     }
6098     if(appData.holdingsSize >= 0) {
6099         i = appData.holdingsSize;
6100         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6101         gameInfo.holdingsSize = i;
6102     }
6103     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6104     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6105         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6106
6107     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6108     if(pawnRow < 1) pawnRow = 1;
6109     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6110     if(gameInfo.variant == VariantChu) pawnRow = 3;
6111
6112     /* User pieceToChar list overrules defaults */
6113     if(appData.pieceToCharTable != NULL)
6114         SetCharTable(pieceToChar, appData.pieceToCharTable);
6115
6116     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6117
6118         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6119             s = (ChessSquare) 0; /* account holding counts in guard band */
6120         for( i=0; i<BOARD_HEIGHT; i++ )
6121             initialPosition[i][j] = s;
6122
6123         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6124         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6125         initialPosition[pawnRow][j] = WhitePawn;
6126         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6127         if(gameInfo.variant == VariantXiangqi) {
6128             if(j&1) {
6129                 initialPosition[pawnRow][j] =
6130                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6131                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6132                    initialPosition[2][j] = WhiteCannon;
6133                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6134                 }
6135             }
6136         }
6137         if(gameInfo.variant == VariantChu) {
6138              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6139                initialPosition[pawnRow+1][j] = WhiteCobra,
6140                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6141              for(i=1; i<pieceRows; i++) {
6142                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6143                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6144              }
6145         }
6146         if(gameInfo.variant == VariantGrand) {
6147             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6148                initialPosition[0][j] = WhiteRook;
6149                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6150             }
6151         }
6152         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6153     }
6154     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6155
6156             j=BOARD_LEFT+1;
6157             initialPosition[1][j] = WhiteBishop;
6158             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6159             j=BOARD_RGHT-2;
6160             initialPosition[1][j] = WhiteRook;
6161             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6162     }
6163
6164     if( nrCastlingRights == -1) {
6165         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6166         /*       This sets default castling rights from none to normal corners   */
6167         /* Variants with other castling rights must set them themselves above    */
6168         nrCastlingRights = 6;
6169
6170         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6171         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6172         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6173         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6174         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6175         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6176      }
6177
6178      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6179      if(gameInfo.variant == VariantGreat) { // promotion commoners
6180         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6181         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6182         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6183         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6184      }
6185      if( gameInfo.variant == VariantSChess ) {
6186       initialPosition[1][0] = BlackMarshall;
6187       initialPosition[2][0] = BlackAngel;
6188       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6189       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6190       initialPosition[1][1] = initialPosition[2][1] =
6191       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6192      }
6193   if (appData.debugMode) {
6194     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6195   }
6196     if(shuffleOpenings) {
6197         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6198         startedFromSetupPosition = TRUE;
6199     }
6200     if(startedFromPositionFile) {
6201       /* [HGM] loadPos: use PositionFile for every new game */
6202       CopyBoard(initialPosition, filePosition);
6203       for(i=0; i<nrCastlingRights; i++)
6204           initialRights[i] = filePosition[CASTLING][i];
6205       startedFromSetupPosition = TRUE;
6206     }
6207
6208     CopyBoard(boards[0], initialPosition);
6209
6210     if(oldx != gameInfo.boardWidth ||
6211        oldy != gameInfo.boardHeight ||
6212        oldv != gameInfo.variant ||
6213        oldh != gameInfo.holdingsWidth
6214                                          )
6215             InitDrawingSizes(-2 ,0);
6216
6217     oldv = gameInfo.variant;
6218     if (redraw)
6219       DrawPosition(TRUE, boards[currentMove]);
6220 }
6221
6222 void
6223 SendBoard (ChessProgramState *cps, int moveNum)
6224 {
6225     char message[MSG_SIZ];
6226
6227     if (cps->useSetboard) {
6228       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6229       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6230       SendToProgram(message, cps);
6231       free(fen);
6232
6233     } else {
6234       ChessSquare *bp;
6235       int i, j, left=0, right=BOARD_WIDTH;
6236       /* Kludge to set black to move, avoiding the troublesome and now
6237        * deprecated "black" command.
6238        */
6239       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6240         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6241
6242       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6243
6244       SendToProgram("edit\n", cps);
6245       SendToProgram("#\n", cps);
6246       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6247         bp = &boards[moveNum][i][left];
6248         for (j = left; j < right; j++, bp++) {
6249           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6250           if ((int) *bp < (int) BlackPawn) {
6251             if(j == BOARD_RGHT+1)
6252                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6253             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6254             if(message[0] == '+' || message[0] == '~') {
6255               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6256                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6257                         AAA + j, ONE + i);
6258             }
6259             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6260                 message[1] = BOARD_RGHT   - 1 - j + '1';
6261                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6262             }
6263             SendToProgram(message, cps);
6264           }
6265         }
6266       }
6267
6268       SendToProgram("c\n", cps);
6269       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6270         bp = &boards[moveNum][i][left];
6271         for (j = left; j < right; j++, bp++) {
6272           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6273           if (((int) *bp != (int) EmptySquare)
6274               && ((int) *bp >= (int) BlackPawn)) {
6275             if(j == BOARD_LEFT-2)
6276                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6277             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6278                     AAA + j, ONE + i);
6279             if(message[0] == '+' || message[0] == '~') {
6280               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6281                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6282                         AAA + j, ONE + i);
6283             }
6284             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6285                 message[1] = BOARD_RGHT   - 1 - j + '1';
6286                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6287             }
6288             SendToProgram(message, cps);
6289           }
6290         }
6291       }
6292
6293       SendToProgram(".\n", cps);
6294     }
6295     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6296 }
6297
6298 char exclusionHeader[MSG_SIZ];
6299 int exCnt, excludePtr;
6300 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6301 static Exclusion excluTab[200];
6302 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6303
6304 static void
6305 WriteMap (int s)
6306 {
6307     int j;
6308     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6309     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6310 }
6311
6312 static void
6313 ClearMap ()
6314 {
6315     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6316     excludePtr = 24; exCnt = 0;
6317     WriteMap(0);
6318 }
6319
6320 static void
6321 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6322 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6323     char buf[2*MOVE_LEN], *p;
6324     Exclusion *e = excluTab;
6325     int i;
6326     for(i=0; i<exCnt; i++)
6327         if(e[i].ff == fromX && e[i].fr == fromY &&
6328            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6329     if(i == exCnt) { // was not in exclude list; add it
6330         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6331         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6332             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6333             return; // abort
6334         }
6335         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6336         excludePtr++; e[i].mark = excludePtr++;
6337         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6338         exCnt++;
6339     }
6340     exclusionHeader[e[i].mark] = state;
6341 }
6342
6343 static int
6344 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6345 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6346     char buf[MSG_SIZ];
6347     int j, k;
6348     ChessMove moveType;
6349     if((signed char)promoChar == -1) { // kludge to indicate best move
6350         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6351             return 1; // if unparsable, abort
6352     }
6353     // update exclusion map (resolving toggle by consulting existing state)
6354     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6355     j = k%8; k >>= 3;
6356     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6357     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6358          excludeMap[k] |=   1<<j;
6359     else excludeMap[k] &= ~(1<<j);
6360     // update header
6361     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6362     // inform engine
6363     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6364     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6365     SendToBoth(buf);
6366     return (state == '+');
6367 }
6368
6369 static void
6370 ExcludeClick (int index)
6371 {
6372     int i, j;
6373     Exclusion *e = excluTab;
6374     if(index < 25) { // none, best or tail clicked
6375         if(index < 13) { // none: include all
6376             WriteMap(0); // clear map
6377             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6378             SendToBoth("include all\n"); // and inform engine
6379         } else if(index > 18) { // tail
6380             if(exclusionHeader[19] == '-') { // tail was excluded
6381                 SendToBoth("include all\n");
6382                 WriteMap(0); // clear map completely
6383                 // now re-exclude selected moves
6384                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6385                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6386             } else { // tail was included or in mixed state
6387                 SendToBoth("exclude all\n");
6388                 WriteMap(0xFF); // fill map completely
6389                 // now re-include selected moves
6390                 j = 0; // count them
6391                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6392                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6393                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6394             }
6395         } else { // best
6396             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6397         }
6398     } else {
6399         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6400             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6401             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6402             break;
6403         }
6404     }
6405 }
6406
6407 ChessSquare
6408 DefaultPromoChoice (int white)
6409 {
6410     ChessSquare result;
6411     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6412        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6413         result = WhiteFerz; // no choice
6414     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6415         result= WhiteKing; // in Suicide Q is the last thing we want
6416     else if(gameInfo.variant == VariantSpartan)
6417         result = white ? WhiteQueen : WhiteAngel;
6418     else result = WhiteQueen;
6419     if(!white) result = WHITE_TO_BLACK result;
6420     return result;
6421 }
6422
6423 static int autoQueen; // [HGM] oneclick
6424
6425 int
6426 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6427 {
6428     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6429     /* [HGM] add Shogi promotions */
6430     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6431     ChessSquare piece;
6432     ChessMove moveType;
6433     Boolean premove;
6434
6435     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6436     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6437
6438     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6439       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6440         return FALSE;
6441
6442     piece = boards[currentMove][fromY][fromX];
6443     if(gameInfo.variant == VariantChu) {
6444         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6445         promotionZoneSize = BOARD_HEIGHT/3;
6446         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6447     } else if(gameInfo.variant == VariantShogi) {
6448         promotionZoneSize = BOARD_HEIGHT/3;
6449         highestPromotingPiece = (int)WhiteAlfil;
6450     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6451         promotionZoneSize = 3;
6452     }
6453
6454     // Treat Lance as Pawn when it is not representing Amazon
6455     if(gameInfo.variant != VariantSuper) {
6456         if(piece == WhiteLance) piece = WhitePawn; else
6457         if(piece == BlackLance) piece = BlackPawn;
6458     }
6459
6460     // next weed out all moves that do not touch the promotion zone at all
6461     if((int)piece >= BlackPawn) {
6462         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6463              return FALSE;
6464         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6465     } else {
6466         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6467            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6468     }
6469
6470     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6471
6472     // weed out mandatory Shogi promotions
6473     if(gameInfo.variant == VariantShogi) {
6474         if(piece >= BlackPawn) {
6475             if(toY == 0 && piece == BlackPawn ||
6476                toY == 0 && piece == BlackQueen ||
6477                toY <= 1 && piece == BlackKnight) {
6478                 *promoChoice = '+';
6479                 return FALSE;
6480             }
6481         } else {
6482             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6483                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6484                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6485                 *promoChoice = '+';
6486                 return FALSE;
6487             }
6488         }
6489     }
6490
6491     // weed out obviously illegal Pawn moves
6492     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6493         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6494         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6495         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6496         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6497         // note we are not allowed to test for valid (non-)capture, due to premove
6498     }
6499
6500     // we either have a choice what to promote to, or (in Shogi) whether to promote
6501     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6502        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6503         *promoChoice = PieceToChar(BlackFerz);  // no choice
6504         return FALSE;
6505     }
6506     // no sense asking what we must promote to if it is going to explode...
6507     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6508         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6509         return FALSE;
6510     }
6511     // give caller the default choice even if we will not make it
6512     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6513     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6514     if(        sweepSelect && gameInfo.variant != VariantGreat
6515                            && gameInfo.variant != VariantGrand
6516                            && gameInfo.variant != VariantSuper) return FALSE;
6517     if(autoQueen) return FALSE; // predetermined
6518
6519     // suppress promotion popup on illegal moves that are not premoves
6520     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6521               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6522     if(appData.testLegality && !premove) {
6523         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6524                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6525         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6526             return FALSE;
6527     }
6528
6529     return TRUE;
6530 }
6531
6532 int
6533 InPalace (int row, int column)
6534 {   /* [HGM] for Xiangqi */
6535     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6536          column < (BOARD_WIDTH + 4)/2 &&
6537          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6538     return FALSE;
6539 }
6540
6541 int
6542 PieceForSquare (int x, int y)
6543 {
6544   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6545      return -1;
6546   else
6547      return boards[currentMove][y][x];
6548 }
6549
6550 int
6551 OKToStartUserMove (int x, int y)
6552 {
6553     ChessSquare from_piece;
6554     int white_piece;
6555
6556     if (matchMode) return FALSE;
6557     if (gameMode == EditPosition) return TRUE;
6558
6559     if (x >= 0 && y >= 0)
6560       from_piece = boards[currentMove][y][x];
6561     else
6562       from_piece = EmptySquare;
6563
6564     if (from_piece == EmptySquare) return FALSE;
6565
6566     white_piece = (int)from_piece >= (int)WhitePawn &&
6567       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6568
6569     switch (gameMode) {
6570       case AnalyzeFile:
6571       case TwoMachinesPlay:
6572       case EndOfGame:
6573         return FALSE;
6574
6575       case IcsObserving:
6576       case IcsIdle:
6577         return FALSE;
6578
6579       case MachinePlaysWhite:
6580       case IcsPlayingBlack:
6581         if (appData.zippyPlay) return FALSE;
6582         if (white_piece) {
6583             DisplayMoveError(_("You are playing Black"));
6584             return FALSE;
6585         }
6586         break;
6587
6588       case MachinePlaysBlack:
6589       case IcsPlayingWhite:
6590         if (appData.zippyPlay) return FALSE;
6591         if (!white_piece) {
6592             DisplayMoveError(_("You are playing White"));
6593             return FALSE;
6594         }
6595         break;
6596
6597       case PlayFromGameFile:
6598             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6599       case EditGame:
6600         if (!white_piece && WhiteOnMove(currentMove)) {
6601             DisplayMoveError(_("It is White's turn"));
6602             return FALSE;
6603         }
6604         if (white_piece && !WhiteOnMove(currentMove)) {
6605             DisplayMoveError(_("It is Black's turn"));
6606             return FALSE;
6607         }
6608         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6609             /* Editing correspondence game history */
6610             /* Could disallow this or prompt for confirmation */
6611             cmailOldMove = -1;
6612         }
6613         break;
6614
6615       case BeginningOfGame:
6616         if (appData.icsActive) return FALSE;
6617         if (!appData.noChessProgram) {
6618             if (!white_piece) {
6619                 DisplayMoveError(_("You are playing White"));
6620                 return FALSE;
6621             }
6622         }
6623         break;
6624
6625       case Training:
6626         if (!white_piece && WhiteOnMove(currentMove)) {
6627             DisplayMoveError(_("It is White's turn"));
6628             return FALSE;
6629         }
6630         if (white_piece && !WhiteOnMove(currentMove)) {
6631             DisplayMoveError(_("It is Black's turn"));
6632             return FALSE;
6633         }
6634         break;
6635
6636       default:
6637       case IcsExamining:
6638         break;
6639     }
6640     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6641         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6642         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6643         && gameMode != AnalyzeFile && gameMode != Training) {
6644         DisplayMoveError(_("Displayed position is not current"));
6645         return FALSE;
6646     }
6647     return TRUE;
6648 }
6649
6650 Boolean
6651 OnlyMove (int *x, int *y, Boolean captures)
6652 {
6653     DisambiguateClosure cl;
6654     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6655     switch(gameMode) {
6656       case MachinePlaysBlack:
6657       case IcsPlayingWhite:
6658       case BeginningOfGame:
6659         if(!WhiteOnMove(currentMove)) return FALSE;
6660         break;
6661       case MachinePlaysWhite:
6662       case IcsPlayingBlack:
6663         if(WhiteOnMove(currentMove)) return FALSE;
6664         break;
6665       case EditGame:
6666         break;
6667       default:
6668         return FALSE;
6669     }
6670     cl.pieceIn = EmptySquare;
6671     cl.rfIn = *y;
6672     cl.ffIn = *x;
6673     cl.rtIn = -1;
6674     cl.ftIn = -1;
6675     cl.promoCharIn = NULLCHAR;
6676     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6677     if( cl.kind == NormalMove ||
6678         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6679         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6680         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6681       fromX = cl.ff;
6682       fromY = cl.rf;
6683       *x = cl.ft;
6684       *y = cl.rt;
6685       return TRUE;
6686     }
6687     if(cl.kind != ImpossibleMove) return FALSE;
6688     cl.pieceIn = EmptySquare;
6689     cl.rfIn = -1;
6690     cl.ffIn = -1;
6691     cl.rtIn = *y;
6692     cl.ftIn = *x;
6693     cl.promoCharIn = NULLCHAR;
6694     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6695     if( cl.kind == NormalMove ||
6696         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6697         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6698         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6699       fromX = cl.ff;
6700       fromY = cl.rf;
6701       *x = cl.ft;
6702       *y = cl.rt;
6703       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6704       return TRUE;
6705     }
6706     return FALSE;
6707 }
6708
6709 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6710 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6711 int lastLoadGameUseList = FALSE;
6712 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6713 ChessMove lastLoadGameStart = EndOfFile;
6714 int doubleClick;
6715
6716 void
6717 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6718 {
6719     ChessMove moveType;
6720     ChessSquare pup;
6721     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6722
6723     /* Check if the user is playing in turn.  This is complicated because we
6724        let the user "pick up" a piece before it is his turn.  So the piece he
6725        tried to pick up may have been captured by the time he puts it down!
6726        Therefore we use the color the user is supposed to be playing in this
6727        test, not the color of the piece that is currently on the starting
6728        square---except in EditGame mode, where the user is playing both
6729        sides; fortunately there the capture race can't happen.  (It can
6730        now happen in IcsExamining mode, but that's just too bad.  The user
6731        will get a somewhat confusing message in that case.)
6732        */
6733
6734     switch (gameMode) {
6735       case AnalyzeFile:
6736       case TwoMachinesPlay:
6737       case EndOfGame:
6738       case IcsObserving:
6739       case IcsIdle:
6740         /* We switched into a game mode where moves are not accepted,
6741            perhaps while the mouse button was down. */
6742         return;
6743
6744       case MachinePlaysWhite:
6745         /* User is moving for Black */
6746         if (WhiteOnMove(currentMove)) {
6747             DisplayMoveError(_("It is White's turn"));
6748             return;
6749         }
6750         break;
6751
6752       case MachinePlaysBlack:
6753         /* User is moving for White */
6754         if (!WhiteOnMove(currentMove)) {
6755             DisplayMoveError(_("It is Black's turn"));
6756             return;
6757         }
6758         break;
6759
6760       case PlayFromGameFile:
6761             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6762       case EditGame:
6763       case IcsExamining:
6764       case BeginningOfGame:
6765       case AnalyzeMode:
6766       case Training:
6767         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6768         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6769             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6770             /* User is moving for Black */
6771             if (WhiteOnMove(currentMove)) {
6772                 DisplayMoveError(_("It is White's turn"));
6773                 return;
6774             }
6775         } else {
6776             /* User is moving for White */
6777             if (!WhiteOnMove(currentMove)) {
6778                 DisplayMoveError(_("It is Black's turn"));
6779                 return;
6780             }
6781         }
6782         break;
6783
6784       case IcsPlayingBlack:
6785         /* User is moving for Black */
6786         if (WhiteOnMove(currentMove)) {
6787             if (!appData.premove) {
6788                 DisplayMoveError(_("It is White'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       case IcsPlayingWhite:
6806         /* User is moving for White */
6807         if (!WhiteOnMove(currentMove)) {
6808             if (!appData.premove) {
6809                 DisplayMoveError(_("It is Black's turn"));
6810             } else if (toX >= 0 && toY >= 0) {
6811                 premoveToX = toX;
6812                 premoveToY = toY;
6813                 premoveFromX = fromX;
6814                 premoveFromY = fromY;
6815                 premovePromoChar = promoChar;
6816                 gotPremove = 1;
6817                 if (appData.debugMode)
6818                     fprintf(debugFP, "Got premove: fromX %d,"
6819                             "fromY %d, toX %d, toY %d\n",
6820                             fromX, fromY, toX, toY);
6821             }
6822             return;
6823         }
6824         break;
6825
6826       default:
6827         break;
6828
6829       case EditPosition:
6830         /* EditPosition, empty square, or different color piece;
6831            click-click move is possible */
6832         if (toX == -2 || toY == -2) {
6833             boards[0][fromY][fromX] = EmptySquare;
6834             DrawPosition(FALSE, boards[currentMove]);
6835             return;
6836         } else if (toX >= 0 && toY >= 0) {
6837             boards[0][toY][toX] = boards[0][fromY][fromX];
6838             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6839                 if(boards[0][fromY][0] != EmptySquare) {
6840                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6841                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6842                 }
6843             } else
6844             if(fromX == BOARD_RGHT+1) {
6845                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6846                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6847                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6848                 }
6849             } else
6850             boards[0][fromY][fromX] = gatingPiece;
6851             DrawPosition(FALSE, boards[currentMove]);
6852             return;
6853         }
6854         return;
6855     }
6856
6857     if(toX < 0 || toY < 0) return;
6858     pup = boards[currentMove][toY][toX];
6859
6860     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6861     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6862          if( pup != EmptySquare ) return;
6863          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6864            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6865                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6866            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6867            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6868            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6869            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6870          fromY = DROP_RANK;
6871     }
6872
6873     /* [HGM] always test for legality, to get promotion info */
6874     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6875                                          fromY, fromX, toY, toX, promoChar);
6876
6877     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6878
6879     /* [HGM] but possibly ignore an IllegalMove result */
6880     if (appData.testLegality) {
6881         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6882             DisplayMoveError(_("Illegal move"));
6883             return;
6884         }
6885     }
6886
6887     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6888         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6889              ClearPremoveHighlights(); // was included
6890         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6891         return;
6892     }
6893
6894     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6895 }
6896
6897 /* Common tail of UserMoveEvent and DropMenuEvent */
6898 int
6899 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6900 {
6901     char *bookHit = 0;
6902
6903     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6904         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6905         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6906         if(WhiteOnMove(currentMove)) {
6907             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6908         } else {
6909             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6910         }
6911     }
6912
6913     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6914        move type in caller when we know the move is a legal promotion */
6915     if(moveType == NormalMove && promoChar)
6916         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6917
6918     /* [HGM] <popupFix> The following if has been moved here from
6919        UserMoveEvent(). Because it seemed to belong here (why not allow
6920        piece drops in training games?), and because it can only be
6921        performed after it is known to what we promote. */
6922     if (gameMode == Training) {
6923       /* compare the move played on the board to the next move in the
6924        * game. If they match, display the move and the opponent's response.
6925        * If they don't match, display an error message.
6926        */
6927       int saveAnimate;
6928       Board testBoard;
6929       CopyBoard(testBoard, boards[currentMove]);
6930       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6931
6932       if (CompareBoards(testBoard, boards[currentMove+1])) {
6933         ForwardInner(currentMove+1);
6934
6935         /* Autoplay the opponent's response.
6936          * if appData.animate was TRUE when Training mode was entered,
6937          * the response will be animated.
6938          */
6939         saveAnimate = appData.animate;
6940         appData.animate = animateTraining;
6941         ForwardInner(currentMove+1);
6942         appData.animate = saveAnimate;
6943
6944         /* check for the end of the game */
6945         if (currentMove >= forwardMostMove) {
6946           gameMode = PlayFromGameFile;
6947           ModeHighlight();
6948           SetTrainingModeOff();
6949           DisplayInformation(_("End of game"));
6950         }
6951       } else {
6952         DisplayError(_("Incorrect move"), 0);
6953       }
6954       return 1;
6955     }
6956
6957   /* Ok, now we know that the move is good, so we can kill
6958      the previous line in Analysis Mode */
6959   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6960                                 && currentMove < forwardMostMove) {
6961     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6962     else forwardMostMove = currentMove;
6963   }
6964
6965   ClearMap();
6966
6967   /* If we need the chess program but it's dead, restart it */
6968   ResurrectChessProgram();
6969
6970   /* A user move restarts a paused game*/
6971   if (pausing)
6972     PauseEvent();
6973
6974   thinkOutput[0] = NULLCHAR;
6975
6976   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6977
6978   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6979     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6980     return 1;
6981   }
6982
6983   if (gameMode == BeginningOfGame) {
6984     if (appData.noChessProgram) {
6985       gameMode = EditGame;
6986       SetGameInfo();
6987     } else {
6988       char buf[MSG_SIZ];
6989       gameMode = MachinePlaysBlack;
6990       StartClocks();
6991       SetGameInfo();
6992       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6993       DisplayTitle(buf);
6994       if (first.sendName) {
6995         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6996         SendToProgram(buf, &first);
6997       }
6998       StartClocks();
6999     }
7000     ModeHighlight();
7001   }
7002
7003   /* Relay move to ICS or chess engine */
7004   if (appData.icsActive) {
7005     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7006         gameMode == IcsExamining) {
7007       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7008         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7009         SendToICS("draw ");
7010         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7011       }
7012       // also send plain move, in case ICS does not understand atomic claims
7013       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7014       ics_user_moved = 1;
7015     }
7016   } else {
7017     if (first.sendTime && (gameMode == BeginningOfGame ||
7018                            gameMode == MachinePlaysWhite ||
7019                            gameMode == MachinePlaysBlack)) {
7020       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7021     }
7022     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7023          // [HGM] book: if program might be playing, let it use book
7024         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7025         first.maybeThinking = TRUE;
7026     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7027         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7028         SendBoard(&first, currentMove+1);
7029         if(second.analyzing) {
7030             if(!second.useSetboard) SendToProgram("undo\n", &second);
7031             SendBoard(&second, currentMove+1);
7032         }
7033     } else {
7034         SendMoveToProgram(forwardMostMove-1, &first);
7035         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7036     }
7037     if (currentMove == cmailOldMove + 1) {
7038       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7039     }
7040   }
7041
7042   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7043
7044   switch (gameMode) {
7045   case EditGame:
7046     if(appData.testLegality)
7047     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7048     case MT_NONE:
7049     case MT_CHECK:
7050       break;
7051     case MT_CHECKMATE:
7052     case MT_STAINMATE:
7053       if (WhiteOnMove(currentMove)) {
7054         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7055       } else {
7056         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7057       }
7058       break;
7059     case MT_STALEMATE:
7060       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7061       break;
7062     }
7063     break;
7064
7065   case MachinePlaysBlack:
7066   case MachinePlaysWhite:
7067     /* disable certain menu options while machine is thinking */
7068     SetMachineThinkingEnables();
7069     break;
7070
7071   default:
7072     break;
7073   }
7074
7075   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7076   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7077
7078   if(bookHit) { // [HGM] book: simulate book reply
7079         static char bookMove[MSG_SIZ]; // a bit generous?
7080
7081         programStats.nodes = programStats.depth = programStats.time =
7082         programStats.score = programStats.got_only_move = 0;
7083         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7084
7085         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7086         strcat(bookMove, bookHit);
7087         HandleMachineMove(bookMove, &first);
7088   }
7089   return 1;
7090 }
7091
7092 void
7093 MarkByFEN(char *fen)
7094 {
7095         int r, f;
7096         if(!appData.markers || !appData.highlightDragging) return;
7097         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7098         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7099         while(*fen) {
7100             int s = 0;
7101             marker[r][f] = 0;
7102             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7103             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7104             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7105             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7106             if(*fen == 'T') marker[r][f++] = 0; else
7107             if(*fen == 'Y') marker[r][f++] = 1; else
7108             if(*fen == 'G') marker[r][f++] = 3; else
7109             if(*fen == 'B') marker[r][f++] = 4; else
7110             if(*fen == 'C') marker[r][f++] = 5; else
7111             if(*fen == 'M') marker[r][f++] = 6; else
7112             if(*fen == 'W') marker[r][f++] = 7; else
7113             if(*fen == 'D') marker[r][f++] = 8; else
7114             if(*fen == 'R') marker[r][f++] = 2; else {
7115                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7116               f += s; fen -= s>0;
7117             }
7118             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7119             if(r < 0) break;
7120             fen++;
7121         }
7122         DrawPosition(TRUE, NULL);
7123 }
7124
7125 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7126
7127 void
7128 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7129 {
7130     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7131     Markers *m = (Markers *) closure;
7132     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7133         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7134                          || kind == WhiteCapturesEnPassant
7135                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7136     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7137 }
7138
7139 void
7140 MarkTargetSquares (int clear)
7141 {
7142   int x, y, sum=0;
7143   if(clear) { // no reason to ever suppress clearing
7144     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7145     if(!sum) return; // nothing was cleared,no redraw needed
7146   } else {
7147     int capt = 0;
7148     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7149        !appData.testLegality || gameMode == EditPosition) return;
7150     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7151     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7152       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7153       if(capt)
7154       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7155     }
7156   }
7157   DrawPosition(FALSE, NULL);
7158 }
7159
7160 int
7161 Explode (Board board, int fromX, int fromY, int toX, int toY)
7162 {
7163     if(gameInfo.variant == VariantAtomic &&
7164        (board[toY][toX] != EmptySquare ||                     // capture?
7165         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7166                          board[fromY][fromX] == BlackPawn   )
7167       )) {
7168         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7169         return TRUE;
7170     }
7171     return FALSE;
7172 }
7173
7174 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7175
7176 int
7177 CanPromote (ChessSquare piece, int y)
7178 {
7179         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7180         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7181         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7182            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7183            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7184          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7185         return (piece == BlackPawn && y == 1 ||
7186                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7187                 piece == BlackLance && y == 1 ||
7188                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7189 }
7190
7191 void
7192 HoverEvent (int xPix, int yPix, int x, int y)
7193 {
7194         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7195         int r, f;
7196         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7197         if(!first.highlight) return;
7198         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7199         if(x == oldX && y == oldY) return; // only do something if we enter new square
7200         oldFromX = fromX; oldFromY = fromY;
7201         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7202           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7203             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7204         else if(oldX != x || oldY != y) {
7205           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7206           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7207             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7208           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7209             char buf[MSG_SIZ];
7210             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7211             SendToProgram(buf, &first);
7212           }
7213           oldX = x; oldY = y;
7214 //        SetHighlights(fromX, fromY, x, y);
7215         }
7216 }
7217
7218 void ReportClick(char *action, int x, int y)
7219 {
7220         char buf[MSG_SIZ]; // Inform engine of what user does
7221         int r, f;
7222         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7223           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7224         if(!first.highlight || gameMode == EditPosition) return;
7225         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7226         SendToProgram(buf, &first);
7227 }
7228
7229 void
7230 LeftClick (ClickType clickType, int xPix, int yPix)
7231 {
7232     int x, y;
7233     Boolean saveAnimate;
7234     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7235     char promoChoice = NULLCHAR;
7236     ChessSquare piece;
7237     static TimeMark lastClickTime, prevClickTime;
7238
7239     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7240
7241     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7242
7243     if (clickType == Press) ErrorPopDown();
7244     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7245
7246     x = EventToSquare(xPix, BOARD_WIDTH);
7247     y = EventToSquare(yPix, BOARD_HEIGHT);
7248     if (!flipView && y >= 0) {
7249         y = BOARD_HEIGHT - 1 - y;
7250     }
7251     if (flipView && x >= 0) {
7252         x = BOARD_WIDTH - 1 - x;
7253     }
7254
7255     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7256         defaultPromoChoice = promoSweep;
7257         promoSweep = EmptySquare;   // terminate sweep
7258         promoDefaultAltered = TRUE;
7259         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7260     }
7261
7262     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7263         if(clickType == Release) return; // ignore upclick of click-click destination
7264         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7265         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7266         if(gameInfo.holdingsWidth &&
7267                 (WhiteOnMove(currentMove)
7268                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7269                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7270             // click in right holdings, for determining promotion piece
7271             ChessSquare p = boards[currentMove][y][x];
7272             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7273             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7274             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7275                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7276                 fromX = fromY = -1;
7277                 return;
7278             }
7279         }
7280         DrawPosition(FALSE, boards[currentMove]);
7281         return;
7282     }
7283
7284     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7285     if(clickType == Press
7286             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7287               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7288               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7289         return;
7290
7291     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7292         // could be static click on premove from-square: abort premove
7293         gotPremove = 0;
7294         ClearPremoveHighlights();
7295     }
7296
7297     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7298         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7299
7300     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7301         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7302                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7303         defaultPromoChoice = DefaultPromoChoice(side);
7304     }
7305
7306     autoQueen = appData.alwaysPromoteToQueen;
7307
7308     if (fromX == -1) {
7309       int originalY = y;
7310       gatingPiece = EmptySquare;
7311       if (clickType != Press) {
7312         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7313             DragPieceEnd(xPix, yPix); dragging = 0;
7314             DrawPosition(FALSE, NULL);
7315         }
7316         return;
7317       }
7318       doubleClick = FALSE;
7319       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7320         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7321       }
7322       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7323       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7324          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7325          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7326             /* First square */
7327             if (OKToStartUserMove(fromX, fromY)) {
7328                 second = 0;
7329                 ReportClick("lift", x, y);
7330                 MarkTargetSquares(0);
7331                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7332                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7333                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7334                     promoSweep = defaultPromoChoice;
7335                     selectFlag = 0; lastX = xPix; lastY = yPix;
7336                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7337                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7338                 }
7339                 if (appData.highlightDragging) {
7340                     SetHighlights(fromX, fromY, -1, -1);
7341                 } else {
7342                     ClearHighlights();
7343                 }
7344             } else fromX = fromY = -1;
7345             return;
7346         }
7347     }
7348
7349     /* fromX != -1 */
7350     if (clickType == Press && gameMode != EditPosition) {
7351         ChessSquare fromP;
7352         ChessSquare toP;
7353         int frc;
7354
7355         // ignore off-board to clicks
7356         if(y < 0 || x < 0) return;
7357
7358         /* Check if clicking again on the same color piece */
7359         fromP = boards[currentMove][fromY][fromX];
7360         toP = boards[currentMove][y][x];
7361         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7362         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7363            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7364              WhitePawn <= toP && toP <= WhiteKing &&
7365              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7366              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7367             (BlackPawn <= fromP && fromP <= BlackKing &&
7368              BlackPawn <= toP && toP <= BlackKing &&
7369              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7370              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7371             /* Clicked again on same color piece -- changed his mind */
7372             second = (x == fromX && y == fromY);
7373             killX = killY = -1;
7374             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7375                 second = FALSE; // first double-click rather than scond click
7376                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7377             }
7378             promoDefaultAltered = FALSE;
7379             MarkTargetSquares(1);
7380            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7381             if (appData.highlightDragging) {
7382                 SetHighlights(x, y, -1, -1);
7383             } else {
7384                 ClearHighlights();
7385             }
7386             if (OKToStartUserMove(x, y)) {
7387                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7388                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7389                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7390                  gatingPiece = boards[currentMove][fromY][fromX];
7391                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7392                 fromX = x;
7393                 fromY = y; dragging = 1;
7394                 ReportClick("lift", x, y);
7395                 MarkTargetSquares(0);
7396                 DragPieceBegin(xPix, yPix, FALSE);
7397                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7398                     promoSweep = defaultPromoChoice;
7399                     selectFlag = 0; lastX = xPix; lastY = yPix;
7400                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7401                 }
7402             }
7403            }
7404            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7405            second = FALSE;
7406         }
7407         // ignore clicks on holdings
7408         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7409     }
7410
7411     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7412         DragPieceEnd(xPix, yPix); dragging = 0;
7413         if(clearFlag) {
7414             // a deferred attempt to click-click move an empty square on top of a piece
7415             boards[currentMove][y][x] = EmptySquare;
7416             ClearHighlights();
7417             DrawPosition(FALSE, boards[currentMove]);
7418             fromX = fromY = -1; clearFlag = 0;
7419             return;
7420         }
7421         if (appData.animateDragging) {
7422             /* Undo animation damage if any */
7423             DrawPosition(FALSE, NULL);
7424         }
7425         if (second || sweepSelecting) {
7426             /* Second up/down in same square; just abort move */
7427             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7428             second = sweepSelecting = 0;
7429             fromX = fromY = -1;
7430             gatingPiece = EmptySquare;
7431             MarkTargetSquares(1);
7432             ClearHighlights();
7433             gotPremove = 0;
7434             ClearPremoveHighlights();
7435         } else {
7436             /* First upclick in same square; start click-click mode */
7437             SetHighlights(x, y, -1, -1);
7438         }
7439         return;
7440     }
7441
7442     clearFlag = 0;
7443
7444     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7445         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7446         DisplayMessage(_("only marked squares are legal"),"");
7447         DrawPosition(TRUE, NULL);
7448         return; // ignore to-click
7449     }
7450
7451     /* we now have a different from- and (possibly off-board) to-square */
7452     /* Completed move */
7453     if(!sweepSelecting) {
7454         toX = x;
7455         toY = y;
7456     }
7457
7458     saveAnimate = appData.animate;
7459     if (clickType == Press) {
7460         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7461             // must be Edit Position mode with empty-square selected
7462             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7463             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7464             return;
7465         }
7466         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7467             dragging = 1;
7468             return;
7469         }
7470         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7471             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7472         } else
7473         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7474         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7475           if(appData.sweepSelect) {
7476             ChessSquare piece = boards[currentMove][fromY][fromX];
7477             promoSweep = defaultPromoChoice;
7478             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7479             selectFlag = 0; lastX = xPix; lastY = yPix;
7480             Sweep(0); // Pawn that is going to promote: preview promotion piece
7481             sweepSelecting = 1;
7482             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7483             MarkTargetSquares(1);
7484           }
7485           return; // promo popup appears on up-click
7486         }
7487         /* Finish clickclick move */
7488         if (appData.animate || appData.highlightLastMove) {
7489             SetHighlights(fromX, fromY, toX, toY);
7490         } else {
7491             ClearHighlights();
7492         }
7493     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7494         sweepSelecting = 0;
7495         if (appData.animate || appData.highlightLastMove) {
7496             SetHighlights(fromX, fromY, toX, toY);
7497         } else {
7498             ClearHighlights();
7499         }
7500     } else {
7501 #if 0
7502 // [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
7503         /* Finish drag move */
7504         if (appData.highlightLastMove) {
7505             SetHighlights(fromX, fromY, toX, toY);
7506         } else {
7507             ClearHighlights();
7508         }
7509 #endif
7510         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7511           dragging *= 2;            // flag button-less dragging if we are dragging
7512           MarkTargetSquares(1);
7513           if(x == killX && y == killY) killX = killY = -1; else {
7514             killX = x; killY = y;     //remeber this square as intermediate
7515             ReportClick("put", x, y); // and inform engine
7516             ReportClick("lift", x, y);
7517             MarkTargetSquares(0);
7518             return;
7519           }
7520         }
7521         DragPieceEnd(xPix, yPix); dragging = 0;
7522         /* Don't animate move and drag both */
7523         appData.animate = FALSE;
7524     }
7525
7526     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7527     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7528         ChessSquare piece = boards[currentMove][fromY][fromX];
7529         if(gameMode == EditPosition && piece != EmptySquare &&
7530            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7531             int n;
7532
7533             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7534                 n = PieceToNumber(piece - (int)BlackPawn);
7535                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7536                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7537                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7538             } else
7539             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7540                 n = PieceToNumber(piece);
7541                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7542                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7543                 boards[currentMove][n][BOARD_WIDTH-2]++;
7544             }
7545             boards[currentMove][fromY][fromX] = EmptySquare;
7546         }
7547         ClearHighlights();
7548         fromX = fromY = -1;
7549         MarkTargetSquares(1);
7550         DrawPosition(TRUE, boards[currentMove]);
7551         return;
7552     }
7553
7554     // off-board moves should not be highlighted
7555     if(x < 0 || y < 0) ClearHighlights();
7556     else ReportClick("put", x, y);
7557
7558     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7559
7560     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7561         SetHighlights(fromX, fromY, toX, toY);
7562         MarkTargetSquares(1);
7563         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7564             // [HGM] super: promotion to captured piece selected from holdings
7565             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7566             promotionChoice = TRUE;
7567             // kludge follows to temporarily execute move on display, without promoting yet
7568             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7569             boards[currentMove][toY][toX] = p;
7570             DrawPosition(FALSE, boards[currentMove]);
7571             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7572             boards[currentMove][toY][toX] = q;
7573             DisplayMessage("Click in holdings to choose piece", "");
7574             return;
7575         }
7576         PromotionPopUp();
7577     } else {
7578         int oldMove = currentMove;
7579         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7580         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7581         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7582         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7583            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7584             DrawPosition(TRUE, boards[currentMove]);
7585         MarkTargetSquares(1);
7586         fromX = fromY = -1;
7587     }
7588     appData.animate = saveAnimate;
7589     if (appData.animate || appData.animateDragging) {
7590         /* Undo animation damage if needed */
7591         DrawPosition(FALSE, NULL);
7592     }
7593 }
7594
7595 int
7596 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7597 {   // front-end-free part taken out of PieceMenuPopup
7598     int whichMenu; int xSqr, ySqr;
7599
7600     if(seekGraphUp) { // [HGM] seekgraph
7601         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7602         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7603         return -2;
7604     }
7605
7606     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7607          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7608         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7609         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7610         if(action == Press)   {
7611             originalFlip = flipView;
7612             flipView = !flipView; // temporarily flip board to see game from partners perspective
7613             DrawPosition(TRUE, partnerBoard);
7614             DisplayMessage(partnerStatus, "");
7615             partnerUp = TRUE;
7616         } else if(action == Release) {
7617             flipView = originalFlip;
7618             DrawPosition(TRUE, boards[currentMove]);
7619             partnerUp = FALSE;
7620         }
7621         return -2;
7622     }
7623
7624     xSqr = EventToSquare(x, BOARD_WIDTH);
7625     ySqr = EventToSquare(y, BOARD_HEIGHT);
7626     if (action == Release) {
7627         if(pieceSweep != EmptySquare) {
7628             EditPositionMenuEvent(pieceSweep, toX, toY);
7629             pieceSweep = EmptySquare;
7630         } else UnLoadPV(); // [HGM] pv
7631     }
7632     if (action != Press) return -2; // return code to be ignored
7633     switch (gameMode) {
7634       case IcsExamining:
7635         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7636       case EditPosition:
7637         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7638         if (xSqr < 0 || ySqr < 0) return -1;
7639         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7640         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7641         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7642         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7643         NextPiece(0);
7644         return 2; // grab
7645       case IcsObserving:
7646         if(!appData.icsEngineAnalyze) return -1;
7647       case IcsPlayingWhite:
7648       case IcsPlayingBlack:
7649         if(!appData.zippyPlay) goto noZip;
7650       case AnalyzeMode:
7651       case AnalyzeFile:
7652       case MachinePlaysWhite:
7653       case MachinePlaysBlack:
7654       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7655         if (!appData.dropMenu) {
7656           LoadPV(x, y);
7657           return 2; // flag front-end to grab mouse events
7658         }
7659         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7660            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7661       case EditGame:
7662       noZip:
7663         if (xSqr < 0 || ySqr < 0) return -1;
7664         if (!appData.dropMenu || appData.testLegality &&
7665             gameInfo.variant != VariantBughouse &&
7666             gameInfo.variant != VariantCrazyhouse) return -1;
7667         whichMenu = 1; // drop menu
7668         break;
7669       default:
7670         return -1;
7671     }
7672
7673     if (((*fromX = xSqr) < 0) ||
7674         ((*fromY = ySqr) < 0)) {
7675         *fromX = *fromY = -1;
7676         return -1;
7677     }
7678     if (flipView)
7679       *fromX = BOARD_WIDTH - 1 - *fromX;
7680     else
7681       *fromY = BOARD_HEIGHT - 1 - *fromY;
7682
7683     return whichMenu;
7684 }
7685
7686 void
7687 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7688 {
7689 //    char * hint = lastHint;
7690     FrontEndProgramStats stats;
7691
7692     stats.which = cps == &first ? 0 : 1;
7693     stats.depth = cpstats->depth;
7694     stats.nodes = cpstats->nodes;
7695     stats.score = cpstats->score;
7696     stats.time = cpstats->time;
7697     stats.pv = cpstats->movelist;
7698     stats.hint = lastHint;
7699     stats.an_move_index = 0;
7700     stats.an_move_count = 0;
7701
7702     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7703         stats.hint = cpstats->move_name;
7704         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7705         stats.an_move_count = cpstats->nr_moves;
7706     }
7707
7708     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
7709
7710     SetProgramStats( &stats );
7711 }
7712
7713 void
7714 ClearEngineOutputPane (int which)
7715 {
7716     static FrontEndProgramStats dummyStats;
7717     dummyStats.which = which;
7718     dummyStats.pv = "#";
7719     SetProgramStats( &dummyStats );
7720 }
7721
7722 #define MAXPLAYERS 500
7723
7724 char *
7725 TourneyStandings (int display)
7726 {
7727     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7728     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7729     char result, *p, *names[MAXPLAYERS];
7730
7731     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7732         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7733     names[0] = p = strdup(appData.participants);
7734     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7735
7736     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7737
7738     while(result = appData.results[nr]) {
7739         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7740         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7741         wScore = bScore = 0;
7742         switch(result) {
7743           case '+': wScore = 2; break;
7744           case '-': bScore = 2; break;
7745           case '=': wScore = bScore = 1; break;
7746           case ' ':
7747           case '*': return strdup("busy"); // tourney not finished
7748         }
7749         score[w] += wScore;
7750         score[b] += bScore;
7751         games[w]++;
7752         games[b]++;
7753         nr++;
7754     }
7755     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7756     for(w=0; w<nPlayers; w++) {
7757         bScore = -1;
7758         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7759         ranking[w] = b; points[w] = bScore; score[b] = -2;
7760     }
7761     p = malloc(nPlayers*34+1);
7762     for(w=0; w<nPlayers && w<display; w++)
7763         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7764     free(names[0]);
7765     return p;
7766 }
7767
7768 void
7769 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7770 {       // count all piece types
7771         int p, f, r;
7772         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7773         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7774         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7775                 p = board[r][f];
7776                 pCnt[p]++;
7777                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7778                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7779                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7780                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7781                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7782                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7783         }
7784 }
7785
7786 int
7787 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7788 {
7789         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7790         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7791
7792         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7793         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7794         if(myPawns == 2 && nMine == 3) // KPP
7795             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7796         if(myPawns == 1 && nMine == 2) // KP
7797             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7798         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7799             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7800         if(myPawns) return FALSE;
7801         if(pCnt[WhiteRook+side])
7802             return pCnt[BlackRook-side] ||
7803                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7804                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7805                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7806         if(pCnt[WhiteCannon+side]) {
7807             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7808             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7809         }
7810         if(pCnt[WhiteKnight+side])
7811             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7812         return FALSE;
7813 }
7814
7815 int
7816 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7817 {
7818         VariantClass v = gameInfo.variant;
7819
7820         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7821         if(v == VariantShatranj) return TRUE; // always winnable through baring
7822         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7823         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7824
7825         if(v == VariantXiangqi) {
7826                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7827
7828                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7829                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7830                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7831                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7832                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7833                 if(stale) // we have at least one last-rank P plus perhaps C
7834                     return majors // KPKX
7835                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7836                 else // KCA*E*
7837                     return pCnt[WhiteFerz+side] // KCAK
7838                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7839                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7840                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7841
7842         } else if(v == VariantKnightmate) {
7843                 if(nMine == 1) return FALSE;
7844                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7845         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7846                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7847
7848                 if(nMine == 1) return FALSE; // bare King
7849                 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
7850                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7851                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7852                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7853                 if(pCnt[WhiteKnight+side])
7854                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7855                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7856                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7857                 if(nBishops)
7858                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7859                 if(pCnt[WhiteAlfil+side])
7860                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7861                 if(pCnt[WhiteWazir+side])
7862                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7863         }
7864
7865         return TRUE;
7866 }
7867
7868 int
7869 CompareWithRights (Board b1, Board b2)
7870 {
7871     int rights = 0;
7872     if(!CompareBoards(b1, b2)) return FALSE;
7873     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7874     /* compare castling rights */
7875     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7876            rights++; /* King lost rights, while rook still had them */
7877     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7878         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7879            rights++; /* but at least one rook lost them */
7880     }
7881     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7882            rights++;
7883     if( b1[CASTLING][5] != NoRights ) {
7884         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7885            rights++;
7886     }
7887     return rights == 0;
7888 }
7889
7890 int
7891 Adjudicate (ChessProgramState *cps)
7892 {       // [HGM] some adjudications useful with buggy engines
7893         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7894         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7895         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7896         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7897         int k, drop, count = 0; static int bare = 1;
7898         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7899         Boolean canAdjudicate = !appData.icsActive;
7900
7901         // most tests only when we understand the game, i.e. legality-checking on
7902             if( appData.testLegality )
7903             {   /* [HGM] Some more adjudications for obstinate engines */
7904                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7905                 static int moveCount = 6;
7906                 ChessMove result;
7907                 char *reason = NULL;
7908
7909                 /* Count what is on board. */
7910                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7911
7912                 /* Some material-based adjudications that have to be made before stalemate test */
7913                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7914                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7915                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7916                      if(canAdjudicate && appData.checkMates) {
7917                          if(engineOpponent)
7918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7919                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7920                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7921                          return 1;
7922                      }
7923                 }
7924
7925                 /* Bare King in Shatranj (loses) or Losers (wins) */
7926                 if( nrW == 1 || nrB == 1) {
7927                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7928                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7929                      if(canAdjudicate && appData.checkMates) {
7930                          if(engineOpponent)
7931                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7932                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7933                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7934                          return 1;
7935                      }
7936                   } else
7937                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7938                   {    /* bare King */
7939                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7940                         if(canAdjudicate && appData.checkMates) {
7941                             /* but only adjudicate if adjudication enabled */
7942                             if(engineOpponent)
7943                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7944                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7945                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7946                             return 1;
7947                         }
7948                   }
7949                 } else bare = 1;
7950
7951
7952             // don't wait for engine to announce game end if we can judge ourselves
7953             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7954               case MT_CHECK:
7955                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7956                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7957                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7958                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7959                             checkCnt++;
7960                         if(checkCnt >= 2) {
7961                             reason = "Xboard adjudication: 3rd check";
7962                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7963                             break;
7964                         }
7965                     }
7966                 }
7967               case MT_NONE:
7968               default:
7969                 break;
7970               case MT_STALEMATE:
7971               case MT_STAINMATE:
7972                 reason = "Xboard adjudication: Stalemate";
7973                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7974                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7975                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7976                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7977                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7978                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7979                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7980                                                                         EP_CHECKMATE : EP_WINS);
7981                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7982                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7983                 }
7984                 break;
7985               case MT_CHECKMATE:
7986                 reason = "Xboard adjudication: Checkmate";
7987                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7988                 if(gameInfo.variant == VariantShogi) {
7989                     if(forwardMostMove > backwardMostMove
7990                        && moveList[forwardMostMove-1][1] == '@'
7991                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7992                         reason = "XBoard adjudication: pawn-drop mate";
7993                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7994                     }
7995                 }
7996                 break;
7997             }
7998
7999                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8000                     case EP_STALEMATE:
8001                         result = GameIsDrawn; break;
8002                     case EP_CHECKMATE:
8003                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8004                     case EP_WINS:
8005                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8006                     default:
8007                         result = EndOfFile;
8008                 }
8009                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8010                     if(engineOpponent)
8011                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8012                     GameEnds( result, reason, GE_XBOARD );
8013                     return 1;
8014                 }
8015
8016                 /* Next absolutely insufficient mating material. */
8017                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8018                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8019                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8020
8021                      /* always flag draws, for judging claims */
8022                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8023
8024                      if(canAdjudicate && appData.materialDraws) {
8025                          /* but only adjudicate them if adjudication enabled */
8026                          if(engineOpponent) {
8027                            SendToProgram("force\n", engineOpponent); // suppress reply
8028                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8029                          }
8030                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8031                          return 1;
8032                      }
8033                 }
8034
8035                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8036                 if(gameInfo.variant == VariantXiangqi ?
8037                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8038                  : nrW + nrB == 4 &&
8039                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8040                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8041                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8042                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8043                    ) ) {
8044                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8045                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8046                           if(engineOpponent) {
8047                             SendToProgram("force\n", engineOpponent); // suppress reply
8048                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8049                           }
8050                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8051                           return 1;
8052                      }
8053                 } else moveCount = 6;
8054             }
8055
8056         // Repetition draws and 50-move rule can be applied independently of legality testing
8057
8058                 /* Check for rep-draws */
8059                 count = 0;
8060                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8061                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8062                 for(k = forwardMostMove-2;
8063                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8064                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8065                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8066                     k-=2)
8067                 {   int rights=0;
8068                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8069                         /* compare castling rights */
8070                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8071                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8072                                 rights++; /* King lost rights, while rook still had them */
8073                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8074                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8075                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8076                                    rights++; /* but at least one rook lost them */
8077                         }
8078                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8079                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8080                                 rights++;
8081                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8082                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8083                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8084                                    rights++;
8085                         }
8086                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8087                             && appData.drawRepeats > 1) {
8088                              /* adjudicate after user-specified nr of repeats */
8089                              int result = GameIsDrawn;
8090                              char *details = "XBoard adjudication: repetition draw";
8091                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8092                                 // [HGM] xiangqi: check for forbidden perpetuals
8093                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8094                                 for(m=forwardMostMove; m>k; m-=2) {
8095                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8096                                         ourPerpetual = 0; // the current mover did not always check
8097                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8098                                         hisPerpetual = 0; // the opponent did not always check
8099                                 }
8100                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8101                                                                         ourPerpetual, hisPerpetual);
8102                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8103                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8104                                     details = "Xboard adjudication: perpetual checking";
8105                                 } else
8106                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8107                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8108                                 } else
8109                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8110                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8111                                         result = BlackWins;
8112                                         details = "Xboard adjudication: repetition";
8113                                     }
8114                                 } else // it must be XQ
8115                                 // Now check for perpetual chases
8116                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8117                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8118                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8119                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8120                                         static char resdet[MSG_SIZ];
8121                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8122                                         details = resdet;
8123                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8124                                     } else
8125                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8126                                         break; // Abort repetition-checking loop.
8127                                 }
8128                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8129                              }
8130                              if(engineOpponent) {
8131                                SendToProgram("force\n", engineOpponent); // suppress reply
8132                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8133                              }
8134                              GameEnds( result, details, GE_XBOARD );
8135                              return 1;
8136                         }
8137                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8138                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8139                     }
8140                 }
8141
8142                 /* Now we test for 50-move draws. Determine ply count */
8143                 count = forwardMostMove;
8144                 /* look for last irreversble move */
8145                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8146                     count--;
8147                 /* if we hit starting position, add initial plies */
8148                 if( count == backwardMostMove )
8149                     count -= initialRulePlies;
8150                 count = forwardMostMove - count;
8151                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8152                         // adjust reversible move counter for checks in Xiangqi
8153                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8154                         if(i < backwardMostMove) i = backwardMostMove;
8155                         while(i <= forwardMostMove) {
8156                                 lastCheck = inCheck; // check evasion does not count
8157                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8158                                 if(inCheck || lastCheck) count--; // check does not count
8159                                 i++;
8160                         }
8161                 }
8162                 if( count >= 100)
8163                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8164                          /* this is used to judge if draw claims are legal */
8165                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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: 50-move rule", GE_XBOARD );
8171                          return 1;
8172                 }
8173
8174                 /* if draw offer is pending, treat it as a draw claim
8175                  * when draw condition present, to allow engines a way to
8176                  * claim draws before making their move to avoid a race
8177                  * condition occurring after their move
8178                  */
8179                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8180                          char *p = NULL;
8181                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8182                              p = "Draw claim: 50-move rule";
8183                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8184                              p = "Draw claim: 3-fold repetition";
8185                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8186                              p = "Draw claim: insufficient mating material";
8187                          if( p != NULL && canAdjudicate) {
8188                              if(engineOpponent) {
8189                                SendToProgram("force\n", engineOpponent); // suppress reply
8190                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8191                              }
8192                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8193                              return 1;
8194                          }
8195                 }
8196
8197                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8198                     if(engineOpponent) {
8199                       SendToProgram("force\n", engineOpponent); // suppress reply
8200                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8201                     }
8202                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8203                     return 1;
8204                 }
8205         return 0;
8206 }
8207
8208 char *
8209 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8210 {   // [HGM] book: this routine intercepts moves to simulate book replies
8211     char *bookHit = NULL;
8212
8213     //first determine if the incoming move brings opponent into his book
8214     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8215         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8216     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8217     if(bookHit != NULL && !cps->bookSuspend) {
8218         // make sure opponent is not going to reply after receiving move to book position
8219         SendToProgram("force\n", cps);
8220         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8221     }
8222     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8223     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8224     // now arrange restart after book miss
8225     if(bookHit) {
8226         // after a book hit we never send 'go', and the code after the call to this routine
8227         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8228         char buf[MSG_SIZ], *move = bookHit;
8229         if(cps->useSAN) {
8230             int fromX, fromY, toX, toY;
8231             char promoChar;
8232             ChessMove moveType;
8233             move = buf + 30;
8234             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8235                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8236                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8237                                     PosFlags(forwardMostMove),
8238                                     fromY, fromX, toY, toX, promoChar, move);
8239             } else {
8240                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8241                 bookHit = NULL;
8242             }
8243         }
8244         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8245         SendToProgram(buf, cps);
8246         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8247     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8248         SendToProgram("go\n", cps);
8249         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8250     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8251         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8252             SendToProgram("go\n", cps);
8253         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8254     }
8255     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8256 }
8257
8258 int
8259 LoadError (char *errmess, ChessProgramState *cps)
8260 {   // unloads engine and switches back to -ncp mode if it was first
8261     if(cps->initDone) return FALSE;
8262     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8263     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8264     cps->pr = NoProc;
8265     if(cps == &first) {
8266         appData.noChessProgram = TRUE;
8267         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8268         gameMode = BeginningOfGame; ModeHighlight();
8269         SetNCPMode();
8270     }
8271     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8272     DisplayMessage("", ""); // erase waiting message
8273     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8274     return TRUE;
8275 }
8276
8277 char *savedMessage;
8278 ChessProgramState *savedState;
8279 void
8280 DeferredBookMove (void)
8281 {
8282         if(savedState->lastPing != savedState->lastPong)
8283                     ScheduleDelayedEvent(DeferredBookMove, 10);
8284         else
8285         HandleMachineMove(savedMessage, savedState);
8286 }
8287
8288 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8289 static ChessProgramState *stalledEngine;
8290 static char stashedInputMove[MSG_SIZ];
8291
8292 void
8293 HandleMachineMove (char *message, ChessProgramState *cps)
8294 {
8295     static char firstLeg[20];
8296     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8297     char realname[MSG_SIZ];
8298     int fromX, fromY, toX, toY;
8299     ChessMove moveType;
8300     char promoChar, roar;
8301     char *p, *pv=buf1;
8302     int machineWhite, oldError;
8303     char *bookHit;
8304
8305     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8306         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8307         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8308             DisplayError(_("Invalid pairing from pairing engine"), 0);
8309             return;
8310         }
8311         pairingReceived = 1;
8312         NextMatchGame();
8313         return; // Skim the pairing messages here.
8314     }
8315
8316     oldError = cps->userError; cps->userError = 0;
8317
8318 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8319     /*
8320      * Kludge to ignore BEL characters
8321      */
8322     while (*message == '\007') message++;
8323
8324     /*
8325      * [HGM] engine debug message: ignore lines starting with '#' character
8326      */
8327     if(cps->debug && *message == '#') return;
8328
8329     /*
8330      * Look for book output
8331      */
8332     if (cps == &first && bookRequested) {
8333         if (message[0] == '\t' || message[0] == ' ') {
8334             /* Part of the book output is here; append it */
8335             strcat(bookOutput, message);
8336             strcat(bookOutput, "  \n");
8337             return;
8338         } else if (bookOutput[0] != NULLCHAR) {
8339             /* All of book output has arrived; display it */
8340             char *p = bookOutput;
8341             while (*p != NULLCHAR) {
8342                 if (*p == '\t') *p = ' ';
8343                 p++;
8344             }
8345             DisplayInformation(bookOutput);
8346             bookRequested = FALSE;
8347             /* Fall through to parse the current output */
8348         }
8349     }
8350
8351     /*
8352      * Look for machine move.
8353      */
8354     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8355         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8356     {
8357         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8358             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8359             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8360             stalledEngine = cps;
8361             if(appData.ponderNextMove) { // bring opponent out of ponder
8362                 if(gameMode == TwoMachinesPlay) {
8363                     if(cps->other->pause)
8364                         PauseEngine(cps->other);
8365                     else
8366                         SendToProgram("easy\n", cps->other);
8367                 }
8368             }
8369             StopClocks();
8370             return;
8371         }
8372
8373         /* This method is only useful on engines that support ping */
8374         if (cps->lastPing != cps->lastPong) {
8375           if (gameMode == BeginningOfGame) {
8376             /* Extra move from before last new; ignore */
8377             if (appData.debugMode) {
8378                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8379             }
8380           } else {
8381             if (appData.debugMode) {
8382                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8383                         cps->which, gameMode);
8384             }
8385
8386             SendToProgram("undo\n", cps);
8387           }
8388           return;
8389         }
8390
8391         switch (gameMode) {
8392           case BeginningOfGame:
8393             /* Extra move from before last reset; ignore */
8394             if (appData.debugMode) {
8395                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8396             }
8397             return;
8398
8399           case EndOfGame:
8400           case IcsIdle:
8401           default:
8402             /* Extra move after we tried to stop.  The mode test is
8403                not a reliable way of detecting this problem, but it's
8404                the best we can do on engines that don't support ping.
8405             */
8406             if (appData.debugMode) {
8407                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8408                         cps->which, gameMode);
8409             }
8410             SendToProgram("undo\n", cps);
8411             return;
8412
8413           case MachinePlaysWhite:
8414           case IcsPlayingWhite:
8415             machineWhite = TRUE;
8416             break;
8417
8418           case MachinePlaysBlack:
8419           case IcsPlayingBlack:
8420             machineWhite = FALSE;
8421             break;
8422
8423           case TwoMachinesPlay:
8424             machineWhite = (cps->twoMachinesColor[0] == 'w');
8425             break;
8426         }
8427         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8428             if (appData.debugMode) {
8429                 fprintf(debugFP,
8430                         "Ignoring move out of turn by %s, gameMode %d"
8431                         ", forwardMost %d\n",
8432                         cps->which, gameMode, forwardMostMove);
8433             }
8434             return;
8435         }
8436
8437         if(cps->alphaRank) AlphaRank(machineMove, 4);
8438
8439         // [HGM] lion: (some very limited) support for Alien protocol
8440         killX = killY = -1;
8441         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8442             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8443             return;
8444         } else if(firstLeg[0]) { // there was a previous leg;
8445             // only support case where same piece makes two step (and don't even test that!)
8446             char buf[20], *p = machineMove+1, *q = buf+1, f;
8447             safeStrCpy(buf, machineMove, 20);
8448             while(isdigit(*q)) q++; // find start of to-square
8449             safeStrCpy(machineMove, firstLeg, 20);
8450             while(isdigit(*p)) p++;
8451             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8452             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8453             firstLeg[0] = NULLCHAR;
8454         }
8455
8456         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8457                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8458             /* Machine move could not be parsed; ignore it. */
8459           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8460                     machineMove, _(cps->which));
8461             DisplayMoveError(buf1);
8462             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8463                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8464             if (gameMode == TwoMachinesPlay) {
8465               GameEnds(machineWhite ? BlackWins : WhiteWins,
8466                        buf1, GE_XBOARD);
8467             }
8468             return;
8469         }
8470
8471         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8472         /* So we have to redo legality test with true e.p. status here,  */
8473         /* to make sure an illegal e.p. capture does not slip through,   */
8474         /* to cause a forfeit on a justified illegal-move complaint      */
8475         /* of the opponent.                                              */
8476         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8477            ChessMove moveType;
8478            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8479                              fromY, fromX, toY, toX, promoChar);
8480             if(moveType == IllegalMove) {
8481               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8482                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8483                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8484                            buf1, GE_XBOARD);
8485                 return;
8486            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8487            /* [HGM] Kludge to handle engines that send FRC-style castling
8488               when they shouldn't (like TSCP-Gothic) */
8489            switch(moveType) {
8490              case WhiteASideCastleFR:
8491              case BlackASideCastleFR:
8492                toX+=2;
8493                currentMoveString[2]++;
8494                break;
8495              case WhiteHSideCastleFR:
8496              case BlackHSideCastleFR:
8497                toX--;
8498                currentMoveString[2]--;
8499                break;
8500              default: ; // nothing to do, but suppresses warning of pedantic compilers
8501            }
8502         }
8503         hintRequested = FALSE;
8504         lastHint[0] = NULLCHAR;
8505         bookRequested = FALSE;
8506         /* Program may be pondering now */
8507         cps->maybeThinking = TRUE;
8508         if (cps->sendTime == 2) cps->sendTime = 1;
8509         if (cps->offeredDraw) cps->offeredDraw--;
8510
8511         /* [AS] Save move info*/
8512         pvInfoList[ forwardMostMove ].score = programStats.score;
8513         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8514         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8515
8516         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8517
8518         /* Test suites abort the 'game' after one move */
8519         if(*appData.finger) {
8520            static FILE *f;
8521            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8522            if(!f) f = fopen(appData.finger, "w");
8523            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8524            else { DisplayFatalError("Bad output file", errno, 0); return; }
8525            free(fen);
8526            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8527         }
8528
8529         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8530         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8531             int count = 0;
8532
8533             while( count < adjudicateLossPlies ) {
8534                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8535
8536                 if( count & 1 ) {
8537                     score = -score; /* Flip score for winning side */
8538                 }
8539
8540                 if( score > adjudicateLossThreshold ) {
8541                     break;
8542                 }
8543
8544                 count++;
8545             }
8546
8547             if( count >= adjudicateLossPlies ) {
8548                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8549
8550                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8551                     "Xboard adjudication",
8552                     GE_XBOARD );
8553
8554                 return;
8555             }
8556         }
8557
8558         if(Adjudicate(cps)) {
8559             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8560             return; // [HGM] adjudicate: for all automatic game ends
8561         }
8562
8563 #if ZIPPY
8564         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8565             first.initDone) {
8566           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8567                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8568                 SendToICS("draw ");
8569                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8570           }
8571           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8572           ics_user_moved = 1;
8573           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8574                 char buf[3*MSG_SIZ];
8575
8576                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8577                         programStats.score / 100.,
8578                         programStats.depth,
8579                         programStats.time / 100.,
8580                         (unsigned int)programStats.nodes,
8581                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8582                         programStats.movelist);
8583                 SendToICS(buf);
8584 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8585           }
8586         }
8587 #endif
8588
8589         /* [AS] Clear stats for next move */
8590         ClearProgramStats();
8591         thinkOutput[0] = NULLCHAR;
8592         hiddenThinkOutputState = 0;
8593
8594         bookHit = NULL;
8595         if (gameMode == TwoMachinesPlay) {
8596             /* [HGM] relaying draw offers moved to after reception of move */
8597             /* and interpreting offer as claim if it brings draw condition */
8598             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8599                 SendToProgram("draw\n", cps->other);
8600             }
8601             if (cps->other->sendTime) {
8602                 SendTimeRemaining(cps->other,
8603                                   cps->other->twoMachinesColor[0] == 'w');
8604             }
8605             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8606             if (firstMove && !bookHit) {
8607                 firstMove = FALSE;
8608                 if (cps->other->useColors) {
8609                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8610                 }
8611                 SendToProgram("go\n", cps->other);
8612             }
8613             cps->other->maybeThinking = TRUE;
8614         }
8615
8616         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8617
8618         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8619
8620         if (!pausing && appData.ringBellAfterMoves) {
8621             if(!roar) RingBell();
8622         }
8623
8624         /*
8625          * Reenable menu items that were disabled while
8626          * machine was thinking
8627          */
8628         if (gameMode != TwoMachinesPlay)
8629             SetUserThinkingEnables();
8630
8631         // [HGM] book: after book hit opponent has received move and is now in force mode
8632         // force the book reply into it, and then fake that it outputted this move by jumping
8633         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8634         if(bookHit) {
8635                 static char bookMove[MSG_SIZ]; // a bit generous?
8636
8637                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8638                 strcat(bookMove, bookHit);
8639                 message = bookMove;
8640                 cps = cps->other;
8641                 programStats.nodes = programStats.depth = programStats.time =
8642                 programStats.score = programStats.got_only_move = 0;
8643                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8644
8645                 if(cps->lastPing != cps->lastPong) {
8646                     savedMessage = message; // args for deferred call
8647                     savedState = cps;
8648                     ScheduleDelayedEvent(DeferredBookMove, 10);
8649                     return;
8650                 }
8651                 goto FakeBookMove;
8652         }
8653
8654         return;
8655     }
8656
8657     /* Set special modes for chess engines.  Later something general
8658      *  could be added here; for now there is just one kludge feature,
8659      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8660      *  when "xboard" is given as an interactive command.
8661      */
8662     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8663         cps->useSigint = FALSE;
8664         cps->useSigterm = FALSE;
8665     }
8666     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8667       ParseFeatures(message+8, cps);
8668       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8669     }
8670
8671     if (!strncmp(message, "setup ", 6) && 
8672         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8673                                         ) { // [HGM] allow first engine to define opening position
8674       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8675       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8676       *buf = NULLCHAR;
8677       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8678       if(startedFromSetupPosition) return;
8679       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8680       if(dummy >= 3) {
8681         while(message[s] && message[s++] != ' ');
8682         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8683            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8684             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8685             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8686           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8687           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8688         }
8689       }
8690       ParseFEN(boards[0], &dummy, message+s, FALSE);
8691       DrawPosition(TRUE, boards[0]);
8692       startedFromSetupPosition = TRUE;
8693       return;
8694     }
8695     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8696      * want this, I was asked to put it in, and obliged.
8697      */
8698     if (!strncmp(message, "setboard ", 9)) {
8699         Board initial_position;
8700
8701         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8702
8703         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8704             DisplayError(_("Bad FEN received from engine"), 0);
8705             return ;
8706         } else {
8707            Reset(TRUE, FALSE);
8708            CopyBoard(boards[0], initial_position);
8709            initialRulePlies = FENrulePlies;
8710            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8711            else gameMode = MachinePlaysBlack;
8712            DrawPosition(FALSE, boards[currentMove]);
8713         }
8714         return;
8715     }
8716
8717     /*
8718      * Look for communication commands
8719      */
8720     if (!strncmp(message, "telluser ", 9)) {
8721         if(message[9] == '\\' && message[10] == '\\')
8722             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8723         PlayTellSound();
8724         DisplayNote(message + 9);
8725         return;
8726     }
8727     if (!strncmp(message, "tellusererror ", 14)) {
8728         cps->userError = 1;
8729         if(message[14] == '\\' && message[15] == '\\')
8730             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8731         PlayTellSound();
8732         DisplayError(message + 14, 0);
8733         return;
8734     }
8735     if (!strncmp(message, "tellopponent ", 13)) {
8736       if (appData.icsActive) {
8737         if (loggedOn) {
8738           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8739           SendToICS(buf1);
8740         }
8741       } else {
8742         DisplayNote(message + 13);
8743       }
8744       return;
8745     }
8746     if (!strncmp(message, "tellothers ", 11)) {
8747       if (appData.icsActive) {
8748         if (loggedOn) {
8749           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8750           SendToICS(buf1);
8751         }
8752       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8753       return;
8754     }
8755     if (!strncmp(message, "tellall ", 8)) {
8756       if (appData.icsActive) {
8757         if (loggedOn) {
8758           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8759           SendToICS(buf1);
8760         }
8761       } else {
8762         DisplayNote(message + 8);
8763       }
8764       return;
8765     }
8766     if (strncmp(message, "warning", 7) == 0) {
8767         /* Undocumented feature, use tellusererror in new code */
8768         DisplayError(message, 0);
8769         return;
8770     }
8771     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8772         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8773         strcat(realname, " query");
8774         AskQuestion(realname, buf2, buf1, cps->pr);
8775         return;
8776     }
8777     /* Commands from the engine directly to ICS.  We don't allow these to be
8778      *  sent until we are logged on. Crafty kibitzes have been known to
8779      *  interfere with the login process.
8780      */
8781     if (loggedOn) {
8782         if (!strncmp(message, "tellics ", 8)) {
8783             SendToICS(message + 8);
8784             SendToICS("\n");
8785             return;
8786         }
8787         if (!strncmp(message, "tellicsnoalias ", 15)) {
8788             SendToICS(ics_prefix);
8789             SendToICS(message + 15);
8790             SendToICS("\n");
8791             return;
8792         }
8793         /* The following are for backward compatibility only */
8794         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8795             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8796             SendToICS(ics_prefix);
8797             SendToICS(message);
8798             SendToICS("\n");
8799             return;
8800         }
8801     }
8802     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8803         return;
8804     }
8805     if(!strncmp(message, "highlight ", 10)) {
8806         if(appData.testLegality && appData.markers) return;
8807         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8808         return;
8809     }
8810     if(!strncmp(message, "click ", 6)) {
8811         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8812         if(appData.testLegality || !appData.oneClick) return;
8813         sscanf(message+6, "%c%d%c", &f, &y, &c);
8814         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8815         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8816         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8817         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8818         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8819         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8820             LeftClick(Release, lastLeftX, lastLeftY);
8821         controlKey  = (c == ',');
8822         LeftClick(Press, x, y);
8823         LeftClick(Release, x, y);
8824         first.highlight = f;
8825         return;
8826     }
8827     /*
8828      * If the move is illegal, cancel it and redraw the board.
8829      * Also deal with other error cases.  Matching is rather loose
8830      * here to accommodate engines written before the spec.
8831      */
8832     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8833         strncmp(message, "Error", 5) == 0) {
8834         if (StrStr(message, "name") ||
8835             StrStr(message, "rating") || StrStr(message, "?") ||
8836             StrStr(message, "result") || StrStr(message, "board") ||
8837             StrStr(message, "bk") || StrStr(message, "computer") ||
8838             StrStr(message, "variant") || StrStr(message, "hint") ||
8839             StrStr(message, "random") || StrStr(message, "depth") ||
8840             StrStr(message, "accepted")) {
8841             return;
8842         }
8843         if (StrStr(message, "protover")) {
8844           /* Program is responding to input, so it's apparently done
8845              initializing, and this error message indicates it is
8846              protocol version 1.  So we don't need to wait any longer
8847              for it to initialize and send feature commands. */
8848           FeatureDone(cps, 1);
8849           cps->protocolVersion = 1;
8850           return;
8851         }
8852         cps->maybeThinking = FALSE;
8853
8854         if (StrStr(message, "draw")) {
8855             /* Program doesn't have "draw" command */
8856             cps->sendDrawOffers = 0;
8857             return;
8858         }
8859         if (cps->sendTime != 1 &&
8860             (StrStr(message, "time") || StrStr(message, "otim"))) {
8861           /* Program apparently doesn't have "time" or "otim" command */
8862           cps->sendTime = 0;
8863           return;
8864         }
8865         if (StrStr(message, "analyze")) {
8866             cps->analysisSupport = FALSE;
8867             cps->analyzing = FALSE;
8868 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8869             EditGameEvent(); // [HGM] try to preserve loaded game
8870             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8871             DisplayError(buf2, 0);
8872             return;
8873         }
8874         if (StrStr(message, "(no matching move)st")) {
8875           /* Special kludge for GNU Chess 4 only */
8876           cps->stKludge = TRUE;
8877           SendTimeControl(cps, movesPerSession, timeControl,
8878                           timeIncrement, appData.searchDepth,
8879                           searchTime);
8880           return;
8881         }
8882         if (StrStr(message, "(no matching move)sd")) {
8883           /* Special kludge for GNU Chess 4 only */
8884           cps->sdKludge = TRUE;
8885           SendTimeControl(cps, movesPerSession, timeControl,
8886                           timeIncrement, appData.searchDepth,
8887                           searchTime);
8888           return;
8889         }
8890         if (!StrStr(message, "llegal")) {
8891             return;
8892         }
8893         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8894             gameMode == IcsIdle) return;
8895         if (forwardMostMove <= backwardMostMove) return;
8896         if (pausing) PauseEvent();
8897       if(appData.forceIllegal) {
8898             // [HGM] illegal: machine refused move; force position after move into it
8899           SendToProgram("force\n", cps);
8900           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8901                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8902                 // when black is to move, while there might be nothing on a2 or black
8903                 // might already have the move. So send the board as if white has the move.
8904                 // But first we must change the stm of the engine, as it refused the last move
8905                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8906                 if(WhiteOnMove(forwardMostMove)) {
8907                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8908                     SendBoard(cps, forwardMostMove); // kludgeless board
8909                 } else {
8910                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8911                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8912                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8913                 }
8914           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8915             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8916                  gameMode == TwoMachinesPlay)
8917               SendToProgram("go\n", cps);
8918             return;
8919       } else
8920         if (gameMode == PlayFromGameFile) {
8921             /* Stop reading this game file */
8922             gameMode = EditGame;
8923             ModeHighlight();
8924         }
8925         /* [HGM] illegal-move claim should forfeit game when Xboard */
8926         /* only passes fully legal moves                            */
8927         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8928             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8929                                 "False illegal-move claim", GE_XBOARD );
8930             return; // do not take back move we tested as valid
8931         }
8932         currentMove = forwardMostMove-1;
8933         DisplayMove(currentMove-1); /* before DisplayMoveError */
8934         SwitchClocks(forwardMostMove-1); // [HGM] race
8935         DisplayBothClocks();
8936         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8937                 parseList[currentMove], _(cps->which));
8938         DisplayMoveError(buf1);
8939         DrawPosition(FALSE, boards[currentMove]);
8940
8941         SetUserThinkingEnables();
8942         return;
8943     }
8944     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8945         /* Program has a broken "time" command that
8946            outputs a string not ending in newline.
8947            Don't use it. */
8948         cps->sendTime = 0;
8949     }
8950
8951     /*
8952      * If chess program startup fails, exit with an error message.
8953      * Attempts to recover here are futile. [HGM] Well, we try anyway
8954      */
8955     if ((StrStr(message, "unknown host") != NULL)
8956         || (StrStr(message, "No remote directory") != NULL)
8957         || (StrStr(message, "not found") != NULL)
8958         || (StrStr(message, "No such file") != NULL)
8959         || (StrStr(message, "can't alloc") != NULL)
8960         || (StrStr(message, "Permission denied") != NULL)) {
8961
8962         cps->maybeThinking = FALSE;
8963         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8964                 _(cps->which), cps->program, cps->host, message);
8965         RemoveInputSource(cps->isr);
8966         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8967             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8968             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8969         }
8970         return;
8971     }
8972
8973     /*
8974      * Look for hint output
8975      */
8976     if (sscanf(message, "Hint: %s", buf1) == 1) {
8977         if (cps == &first && hintRequested) {
8978             hintRequested = FALSE;
8979             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8980                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8981                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8982                                     PosFlags(forwardMostMove),
8983                                     fromY, fromX, toY, toX, promoChar, buf1);
8984                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8985                 DisplayInformation(buf2);
8986             } else {
8987                 /* Hint move could not be parsed!? */
8988               snprintf(buf2, sizeof(buf2),
8989                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8990                         buf1, _(cps->which));
8991                 DisplayError(buf2, 0);
8992             }
8993         } else {
8994           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8995         }
8996         return;
8997     }
8998
8999     /*
9000      * Ignore other messages if game is not in progress
9001      */
9002     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9003         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9004
9005     /*
9006      * look for win, lose, draw, or draw offer
9007      */
9008     if (strncmp(message, "1-0", 3) == 0) {
9009         char *p, *q, *r = "";
9010         p = strchr(message, '{');
9011         if (p) {
9012             q = strchr(p, '}');
9013             if (q) {
9014                 *q = NULLCHAR;
9015                 r = p + 1;
9016             }
9017         }
9018         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9019         return;
9020     } else if (strncmp(message, "0-1", 3) == 0) {
9021         char *p, *q, *r = "";
9022         p = strchr(message, '{');
9023         if (p) {
9024             q = strchr(p, '}');
9025             if (q) {
9026                 *q = NULLCHAR;
9027                 r = p + 1;
9028             }
9029         }
9030         /* Kludge for Arasan 4.1 bug */
9031         if (strcmp(r, "Black resigns") == 0) {
9032             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9033             return;
9034         }
9035         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9036         return;
9037     } else if (strncmp(message, "1/2", 3) == 0) {
9038         char *p, *q, *r = "";
9039         p = strchr(message, '{');
9040         if (p) {
9041             q = strchr(p, '}');
9042             if (q) {
9043                 *q = NULLCHAR;
9044                 r = p + 1;
9045             }
9046         }
9047
9048         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9049         return;
9050
9051     } else if (strncmp(message, "White resign", 12) == 0) {
9052         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9053         return;
9054     } else if (strncmp(message, "Black resign", 12) == 0) {
9055         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9056         return;
9057     } else if (strncmp(message, "White matches", 13) == 0 ||
9058                strncmp(message, "Black matches", 13) == 0   ) {
9059         /* [HGM] ignore GNUShogi noises */
9060         return;
9061     } else if (strncmp(message, "White", 5) == 0 &&
9062                message[5] != '(' &&
9063                StrStr(message, "Black") == NULL) {
9064         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9065         return;
9066     } else if (strncmp(message, "Black", 5) == 0 &&
9067                message[5] != '(') {
9068         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9069         return;
9070     } else if (strcmp(message, "resign") == 0 ||
9071                strcmp(message, "computer resigns") == 0) {
9072         switch (gameMode) {
9073           case MachinePlaysBlack:
9074           case IcsPlayingBlack:
9075             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9076             break;
9077           case MachinePlaysWhite:
9078           case IcsPlayingWhite:
9079             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9080             break;
9081           case TwoMachinesPlay:
9082             if (cps->twoMachinesColor[0] == 'w')
9083               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9084             else
9085               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9086             break;
9087           default:
9088             /* can't happen */
9089             break;
9090         }
9091         return;
9092     } else if (strncmp(message, "opponent mates", 14) == 0) {
9093         switch (gameMode) {
9094           case MachinePlaysBlack:
9095           case IcsPlayingBlack:
9096             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9097             break;
9098           case MachinePlaysWhite:
9099           case IcsPlayingWhite:
9100             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9101             break;
9102           case TwoMachinesPlay:
9103             if (cps->twoMachinesColor[0] == 'w')
9104               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9105             else
9106               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9107             break;
9108           default:
9109             /* can't happen */
9110             break;
9111         }
9112         return;
9113     } else if (strncmp(message, "computer mates", 14) == 0) {
9114         switch (gameMode) {
9115           case MachinePlaysBlack:
9116           case IcsPlayingBlack:
9117             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9118             break;
9119           case MachinePlaysWhite:
9120           case IcsPlayingWhite:
9121             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9122             break;
9123           case TwoMachinesPlay:
9124             if (cps->twoMachinesColor[0] == 'w')
9125               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9126             else
9127               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9128             break;
9129           default:
9130             /* can't happen */
9131             break;
9132         }
9133         return;
9134     } else if (strncmp(message, "checkmate", 9) == 0) {
9135         if (WhiteOnMove(forwardMostMove)) {
9136             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9137         } else {
9138             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9139         }
9140         return;
9141     } else if (strstr(message, "Draw") != NULL ||
9142                strstr(message, "game is a draw") != NULL) {
9143         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9144         return;
9145     } else if (strstr(message, "offer") != NULL &&
9146                strstr(message, "draw") != NULL) {
9147 #if ZIPPY
9148         if (appData.zippyPlay && first.initDone) {
9149             /* Relay offer to ICS */
9150             SendToICS(ics_prefix);
9151             SendToICS("draw\n");
9152         }
9153 #endif
9154         cps->offeredDraw = 2; /* valid until this engine moves twice */
9155         if (gameMode == TwoMachinesPlay) {
9156             if (cps->other->offeredDraw) {
9157                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9158             /* [HGM] in two-machine mode we delay relaying draw offer      */
9159             /* until after we also have move, to see if it is really claim */
9160             }
9161         } else if (gameMode == MachinePlaysWhite ||
9162                    gameMode == MachinePlaysBlack) {
9163           if (userOfferedDraw) {
9164             DisplayInformation(_("Machine accepts your draw offer"));
9165             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9166           } else {
9167             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9168           }
9169         }
9170     }
9171
9172
9173     /*
9174      * Look for thinking output
9175      */
9176     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9177           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9178                                 ) {
9179         int plylev, mvleft, mvtot, curscore, time;
9180         char mvname[MOVE_LEN];
9181         u64 nodes; // [DM]
9182         char plyext;
9183         int ignore = FALSE;
9184         int prefixHint = FALSE;
9185         mvname[0] = NULLCHAR;
9186
9187         switch (gameMode) {
9188           case MachinePlaysBlack:
9189           case IcsPlayingBlack:
9190             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9191             break;
9192           case MachinePlaysWhite:
9193           case IcsPlayingWhite:
9194             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9195             break;
9196           case AnalyzeMode:
9197           case AnalyzeFile:
9198             break;
9199           case IcsObserving: /* [DM] icsEngineAnalyze */
9200             if (!appData.icsEngineAnalyze) ignore = TRUE;
9201             break;
9202           case TwoMachinesPlay:
9203             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9204                 ignore = TRUE;
9205             }
9206             break;
9207           default:
9208             ignore = TRUE;
9209             break;
9210         }
9211
9212         if (!ignore) {
9213             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9214             buf1[0] = NULLCHAR;
9215             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9216                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9217
9218                 if (plyext != ' ' && plyext != '\t') {
9219                     time *= 100;
9220                 }
9221
9222                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9223                 if( cps->scoreIsAbsolute &&
9224                     ( gameMode == MachinePlaysBlack ||
9225                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9226                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9227                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9228                      !WhiteOnMove(currentMove)
9229                     ) )
9230                 {
9231                     curscore = -curscore;
9232                 }
9233
9234                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9235
9236                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9237                         char buf[MSG_SIZ];
9238                         FILE *f;
9239                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9240                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9241                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9242                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9243                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9244                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9245                                 fclose(f);
9246                         }
9247                         else
9248                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9249                           DisplayError(_("failed writing PV"), 0);
9250                 }
9251
9252                 tempStats.depth = plylev;
9253                 tempStats.nodes = nodes;
9254                 tempStats.time = time;
9255                 tempStats.score = curscore;
9256                 tempStats.got_only_move = 0;
9257
9258                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9259                         int ticklen;
9260
9261                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9262                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9263                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9264                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9265                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9266                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9267                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9268                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9269                 }
9270
9271                 /* Buffer overflow protection */
9272                 if (pv[0] != NULLCHAR) {
9273                     if (strlen(pv) >= sizeof(tempStats.movelist)
9274                         && appData.debugMode) {
9275                         fprintf(debugFP,
9276                                 "PV is too long; using the first %u bytes.\n",
9277                                 (unsigned) sizeof(tempStats.movelist) - 1);
9278                     }
9279
9280                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9281                 } else {
9282                     sprintf(tempStats.movelist, " no PV\n");
9283                 }
9284
9285                 if (tempStats.seen_stat) {
9286                     tempStats.ok_to_send = 1;
9287                 }
9288
9289                 if (strchr(tempStats.movelist, '(') != NULL) {
9290                     tempStats.line_is_book = 1;
9291                     tempStats.nr_moves = 0;
9292                     tempStats.moves_left = 0;
9293                 } else {
9294                     tempStats.line_is_book = 0;
9295                 }
9296
9297                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9298                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9299
9300                 SendProgramStatsToFrontend( cps, &tempStats );
9301
9302                 /*
9303                     [AS] Protect the thinkOutput buffer from overflow... this
9304                     is only useful if buf1 hasn't overflowed first!
9305                 */
9306                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9307                          plylev,
9308                          (gameMode == TwoMachinesPlay ?
9309                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9310                          ((double) curscore) / 100.0,
9311                          prefixHint ? lastHint : "",
9312                          prefixHint ? " " : "" );
9313
9314                 if( buf1[0] != NULLCHAR ) {
9315                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9316
9317                     if( strlen(pv) > max_len ) {
9318                         if( appData.debugMode) {
9319                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9320                         }
9321                         pv[max_len+1] = '\0';
9322                     }
9323
9324                     strcat( thinkOutput, pv);
9325                 }
9326
9327                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9328                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9329                     DisplayMove(currentMove - 1);
9330                 }
9331                 return;
9332
9333             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9334                 /* crafty (9.25+) says "(only move) <move>"
9335                  * if there is only 1 legal move
9336                  */
9337                 sscanf(p, "(only move) %s", buf1);
9338                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9339                 sprintf(programStats.movelist, "%s (only move)", buf1);
9340                 programStats.depth = 1;
9341                 programStats.nr_moves = 1;
9342                 programStats.moves_left = 1;
9343                 programStats.nodes = 1;
9344                 programStats.time = 1;
9345                 programStats.got_only_move = 1;
9346
9347                 /* Not really, but we also use this member to
9348                    mean "line isn't going to change" (Crafty
9349                    isn't searching, so stats won't change) */
9350                 programStats.line_is_book = 1;
9351
9352                 SendProgramStatsToFrontend( cps, &programStats );
9353
9354                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9355                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9356                     DisplayMove(currentMove - 1);
9357                 }
9358                 return;
9359             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9360                               &time, &nodes, &plylev, &mvleft,
9361                               &mvtot, mvname) >= 5) {
9362                 /* The stat01: line is from Crafty (9.29+) in response
9363                    to the "." command */
9364                 programStats.seen_stat = 1;
9365                 cps->maybeThinking = TRUE;
9366
9367                 if (programStats.got_only_move || !appData.periodicUpdates)
9368                   return;
9369
9370                 programStats.depth = plylev;
9371                 programStats.time = time;
9372                 programStats.nodes = nodes;
9373                 programStats.moves_left = mvleft;
9374                 programStats.nr_moves = mvtot;
9375                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9376                 programStats.ok_to_send = 1;
9377                 programStats.movelist[0] = '\0';
9378
9379                 SendProgramStatsToFrontend( cps, &programStats );
9380
9381                 return;
9382
9383             } else if (strncmp(message,"++",2) == 0) {
9384                 /* Crafty 9.29+ outputs this */
9385                 programStats.got_fail = 2;
9386                 return;
9387
9388             } else if (strncmp(message,"--",2) == 0) {
9389                 /* Crafty 9.29+ outputs this */
9390                 programStats.got_fail = 1;
9391                 return;
9392
9393             } else if (thinkOutput[0] != NULLCHAR &&
9394                        strncmp(message, "    ", 4) == 0) {
9395                 unsigned message_len;
9396
9397                 p = message;
9398                 while (*p && *p == ' ') p++;
9399
9400                 message_len = strlen( p );
9401
9402                 /* [AS] Avoid buffer overflow */
9403                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9404                     strcat(thinkOutput, " ");
9405                     strcat(thinkOutput, p);
9406                 }
9407
9408                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9409                     strcat(programStats.movelist, " ");
9410                     strcat(programStats.movelist, p);
9411                 }
9412
9413                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9414                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9415                     DisplayMove(currentMove - 1);
9416                 }
9417                 return;
9418             }
9419         }
9420         else {
9421             buf1[0] = NULLCHAR;
9422
9423             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9424                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9425             {
9426                 ChessProgramStats cpstats;
9427
9428                 if (plyext != ' ' && plyext != '\t') {
9429                     time *= 100;
9430                 }
9431
9432                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9433                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9434                     curscore = -curscore;
9435                 }
9436
9437                 cpstats.depth = plylev;
9438                 cpstats.nodes = nodes;
9439                 cpstats.time = time;
9440                 cpstats.score = curscore;
9441                 cpstats.got_only_move = 0;
9442                 cpstats.movelist[0] = '\0';
9443
9444                 if (buf1[0] != NULLCHAR) {
9445                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9446                 }
9447
9448                 cpstats.ok_to_send = 0;
9449                 cpstats.line_is_book = 0;
9450                 cpstats.nr_moves = 0;
9451                 cpstats.moves_left = 0;
9452
9453                 SendProgramStatsToFrontend( cps, &cpstats );
9454             }
9455         }
9456     }
9457 }
9458
9459
9460 /* Parse a game score from the character string "game", and
9461    record it as the history of the current game.  The game
9462    score is NOT assumed to start from the standard position.
9463    The display is not updated in any way.
9464    */
9465 void
9466 ParseGameHistory (char *game)
9467 {
9468     ChessMove moveType;
9469     int fromX, fromY, toX, toY, boardIndex;
9470     char promoChar;
9471     char *p, *q;
9472     char buf[MSG_SIZ];
9473
9474     if (appData.debugMode)
9475       fprintf(debugFP, "Parsing game history: %s\n", game);
9476
9477     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9478     gameInfo.site = StrSave(appData.icsHost);
9479     gameInfo.date = PGNDate();
9480     gameInfo.round = StrSave("-");
9481
9482     /* Parse out names of players */
9483     while (*game == ' ') game++;
9484     p = buf;
9485     while (*game != ' ') *p++ = *game++;
9486     *p = NULLCHAR;
9487     gameInfo.white = StrSave(buf);
9488     while (*game == ' ') game++;
9489     p = buf;
9490     while (*game != ' ' && *game != '\n') *p++ = *game++;
9491     *p = NULLCHAR;
9492     gameInfo.black = StrSave(buf);
9493
9494     /* Parse moves */
9495     boardIndex = blackPlaysFirst ? 1 : 0;
9496     yynewstr(game);
9497     for (;;) {
9498         yyboardindex = boardIndex;
9499         moveType = (ChessMove) Myylex();
9500         switch (moveType) {
9501           case IllegalMove:             /* maybe suicide chess, etc. */
9502   if (appData.debugMode) {
9503     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9504     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9505     setbuf(debugFP, NULL);
9506   }
9507           case WhitePromotion:
9508           case BlackPromotion:
9509           case WhiteNonPromotion:
9510           case BlackNonPromotion:
9511           case NormalMove:
9512           case FirstLeg:
9513           case WhiteCapturesEnPassant:
9514           case BlackCapturesEnPassant:
9515           case WhiteKingSideCastle:
9516           case WhiteQueenSideCastle:
9517           case BlackKingSideCastle:
9518           case BlackQueenSideCastle:
9519           case WhiteKingSideCastleWild:
9520           case WhiteQueenSideCastleWild:
9521           case BlackKingSideCastleWild:
9522           case BlackQueenSideCastleWild:
9523           /* PUSH Fabien */
9524           case WhiteHSideCastleFR:
9525           case WhiteASideCastleFR:
9526           case BlackHSideCastleFR:
9527           case BlackASideCastleFR:
9528           /* POP Fabien */
9529             fromX = currentMoveString[0] - AAA;
9530             fromY = currentMoveString[1] - ONE;
9531             toX = currentMoveString[2] - AAA;
9532             toY = currentMoveString[3] - ONE;
9533             promoChar = currentMoveString[4];
9534             break;
9535           case WhiteDrop:
9536           case BlackDrop:
9537             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9538             fromX = moveType == WhiteDrop ?
9539               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9540             (int) CharToPiece(ToLower(currentMoveString[0]));
9541             fromY = DROP_RANK;
9542             toX = currentMoveString[2] - AAA;
9543             toY = currentMoveString[3] - ONE;
9544             promoChar = NULLCHAR;
9545             break;
9546           case AmbiguousMove:
9547             /* bug? */
9548             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9549   if (appData.debugMode) {
9550     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9551     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9552     setbuf(debugFP, NULL);
9553   }
9554             DisplayError(buf, 0);
9555             return;
9556           case ImpossibleMove:
9557             /* bug? */
9558             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9559   if (appData.debugMode) {
9560     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9561     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9562     setbuf(debugFP, NULL);
9563   }
9564             DisplayError(buf, 0);
9565             return;
9566           case EndOfFile:
9567             if (boardIndex < backwardMostMove) {
9568                 /* Oops, gap.  How did that happen? */
9569                 DisplayError(_("Gap in move list"), 0);
9570                 return;
9571             }
9572             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9573             if (boardIndex > forwardMostMove) {
9574                 forwardMostMove = boardIndex;
9575             }
9576             return;
9577           case ElapsedTime:
9578             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9579                 strcat(parseList[boardIndex-1], " ");
9580                 strcat(parseList[boardIndex-1], yy_text);
9581             }
9582             continue;
9583           case Comment:
9584           case PGNTag:
9585           case NAG:
9586           default:
9587             /* ignore */
9588             continue;
9589           case WhiteWins:
9590           case BlackWins:
9591           case GameIsDrawn:
9592           case GameUnfinished:
9593             if (gameMode == IcsExamining) {
9594                 if (boardIndex < backwardMostMove) {
9595                     /* Oops, gap.  How did that happen? */
9596                     return;
9597                 }
9598                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9599                 return;
9600             }
9601             gameInfo.result = moveType;
9602             p = strchr(yy_text, '{');
9603             if (p == NULL) p = strchr(yy_text, '(');
9604             if (p == NULL) {
9605                 p = yy_text;
9606                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9607             } else {
9608                 q = strchr(p, *p == '{' ? '}' : ')');
9609                 if (q != NULL) *q = NULLCHAR;
9610                 p++;
9611             }
9612             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9613             gameInfo.resultDetails = StrSave(p);
9614             continue;
9615         }
9616         if (boardIndex >= forwardMostMove &&
9617             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9618             backwardMostMove = blackPlaysFirst ? 1 : 0;
9619             return;
9620         }
9621         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9622                                  fromY, fromX, toY, toX, promoChar,
9623                                  parseList[boardIndex]);
9624         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9625         /* currentMoveString is set as a side-effect of yylex */
9626         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9627         strcat(moveList[boardIndex], "\n");
9628         boardIndex++;
9629         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9630         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9631           case MT_NONE:
9632           case MT_STALEMATE:
9633           default:
9634             break;
9635           case MT_CHECK:
9636             if(gameInfo.variant != VariantShogi)
9637                 strcat(parseList[boardIndex - 1], "+");
9638             break;
9639           case MT_CHECKMATE:
9640           case MT_STAINMATE:
9641             strcat(parseList[boardIndex - 1], "#");
9642             break;
9643         }
9644     }
9645 }
9646
9647
9648 /* Apply a move to the given board  */
9649 void
9650 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9651 {
9652   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9653   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9654
9655     /* [HGM] compute & store e.p. status and castling rights for new position */
9656     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9657
9658       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9659       oldEP = (signed char)board[EP_STATUS];
9660       board[EP_STATUS] = EP_NONE;
9661
9662   if (fromY == DROP_RANK) {
9663         /* must be first */
9664         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9665             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9666             return;
9667         }
9668         piece = board[toY][toX] = (ChessSquare) fromX;
9669   } else {
9670       ChessSquare victim;
9671       int i;
9672
9673       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9674            victim = board[killY][killX],
9675            board[killY][killX] = EmptySquare,
9676            board[EP_STATUS] = EP_CAPTURE;
9677
9678       if( board[toY][toX] != EmptySquare ) {
9679            board[EP_STATUS] = EP_CAPTURE;
9680            if( (fromX != toX || fromY != toY) && // not igui!
9681                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9682                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9683                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9684            }
9685       }
9686
9687       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9688            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9689                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9690       } else
9691       if( board[fromY][fromX] == WhitePawn ) {
9692            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9693                board[EP_STATUS] = EP_PAWN_MOVE;
9694            if( toY-fromY==2) {
9695                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9696                         gameInfo.variant != VariantBerolina || toX < fromX)
9697                       board[EP_STATUS] = toX | berolina;
9698                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9699                         gameInfo.variant != VariantBerolina || toX > fromX)
9700                       board[EP_STATUS] = toX;
9701            }
9702       } else
9703       if( board[fromY][fromX] == BlackPawn ) {
9704            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9705                board[EP_STATUS] = EP_PAWN_MOVE;
9706            if( toY-fromY== -2) {
9707                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9708                         gameInfo.variant != VariantBerolina || toX < fromX)
9709                       board[EP_STATUS] = toX | berolina;
9710                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9711                         gameInfo.variant != VariantBerolina || toX > fromX)
9712                       board[EP_STATUS] = toX;
9713            }
9714        }
9715
9716        for(i=0; i<nrCastlingRights; i++) {
9717            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9718               board[CASTLING][i] == toX   && castlingRank[i] == toY
9719              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9720        }
9721
9722        if(gameInfo.variant == VariantSChess) { // update virginity
9723            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9724            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9725            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9726            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9727        }
9728
9729      if (fromX == toX && fromY == toY) return;
9730
9731      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9732      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9733      if(gameInfo.variant == VariantKnightmate)
9734          king += (int) WhiteUnicorn - (int) WhiteKing;
9735
9736     /* Code added by Tord: */
9737     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9738     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9739         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9740       board[fromY][fromX] = EmptySquare;
9741       board[toY][toX] = EmptySquare;
9742       if((toX > fromX) != (piece == WhiteRook)) {
9743         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9744       } else {
9745         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9746       }
9747     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9748                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9749       board[fromY][fromX] = EmptySquare;
9750       board[toY][toX] = EmptySquare;
9751       if((toX > fromX) != (piece == BlackRook)) {
9752         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9753       } else {
9754         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9755       }
9756     /* End of code added by Tord */
9757
9758     } else if (board[fromY][fromX] == king
9759         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9760         && toY == fromY && toX > fromX+1) {
9761         board[fromY][fromX] = EmptySquare;
9762         board[toY][toX] = king;
9763         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9764         board[fromY][BOARD_RGHT-1] = EmptySquare;
9765     } else if (board[fromY][fromX] == king
9766         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9767                && toY == fromY && toX < fromX-1) {
9768         board[fromY][fromX] = EmptySquare;
9769         board[toY][toX] = king;
9770         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9771         board[fromY][BOARD_LEFT] = EmptySquare;
9772     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9773                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9774                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9775                ) {
9776         /* white pawn promotion */
9777         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9778         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9779             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9780         board[fromY][fromX] = EmptySquare;
9781     } else if ((fromY >= BOARD_HEIGHT>>1)
9782                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9783                && (toX != fromX)
9784                && gameInfo.variant != VariantXiangqi
9785                && gameInfo.variant != VariantBerolina
9786                && (board[fromY][fromX] == WhitePawn)
9787                && (board[toY][toX] == EmptySquare)) {
9788         board[fromY][fromX] = EmptySquare;
9789         board[toY][toX] = WhitePawn;
9790         captured = board[toY - 1][toX];
9791         board[toY - 1][toX] = EmptySquare;
9792     } else if ((fromY == BOARD_HEIGHT-4)
9793                && (toX == fromX)
9794                && gameInfo.variant == VariantBerolina
9795                && (board[fromY][fromX] == WhitePawn)
9796                && (board[toY][toX] == EmptySquare)) {
9797         board[fromY][fromX] = EmptySquare;
9798         board[toY][toX] = WhitePawn;
9799         if(oldEP & EP_BEROLIN_A) {
9800                 captured = board[fromY][fromX-1];
9801                 board[fromY][fromX-1] = EmptySquare;
9802         }else{  captured = board[fromY][fromX+1];
9803                 board[fromY][fromX+1] = EmptySquare;
9804         }
9805     } else if (board[fromY][fromX] == king
9806         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9807                && toY == fromY && toX > fromX+1) {
9808         board[fromY][fromX] = EmptySquare;
9809         board[toY][toX] = king;
9810         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9811         board[fromY][BOARD_RGHT-1] = EmptySquare;
9812     } else if (board[fromY][fromX] == king
9813         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9814                && toY == fromY && toX < fromX-1) {
9815         board[fromY][fromX] = EmptySquare;
9816         board[toY][toX] = king;
9817         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9818         board[fromY][BOARD_LEFT] = EmptySquare;
9819     } else if (fromY == 7 && fromX == 3
9820                && board[fromY][fromX] == BlackKing
9821                && toY == 7 && toX == 5) {
9822         board[fromY][fromX] = EmptySquare;
9823         board[toY][toX] = BlackKing;
9824         board[fromY][7] = EmptySquare;
9825         board[toY][4] = BlackRook;
9826     } else if (fromY == 7 && fromX == 3
9827                && board[fromY][fromX] == BlackKing
9828                && toY == 7 && toX == 1) {
9829         board[fromY][fromX] = EmptySquare;
9830         board[toY][toX] = BlackKing;
9831         board[fromY][0] = EmptySquare;
9832         board[toY][2] = BlackRook;
9833     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9834                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9835                && toY < promoRank && promoChar
9836                ) {
9837         /* black pawn promotion */
9838         board[toY][toX] = CharToPiece(ToLower(promoChar));
9839         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9840             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9841         board[fromY][fromX] = EmptySquare;
9842     } else if ((fromY < BOARD_HEIGHT>>1)
9843                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9844                && (toX != fromX)
9845                && gameInfo.variant != VariantXiangqi
9846                && gameInfo.variant != VariantBerolina
9847                && (board[fromY][fromX] == BlackPawn)
9848                && (board[toY][toX] == EmptySquare)) {
9849         board[fromY][fromX] = EmptySquare;
9850         board[toY][toX] = BlackPawn;
9851         captured = board[toY + 1][toX];
9852         board[toY + 1][toX] = EmptySquare;
9853     } else if ((fromY == 3)
9854                && (toX == fromX)
9855                && gameInfo.variant == VariantBerolina
9856                && (board[fromY][fromX] == BlackPawn)
9857                && (board[toY][toX] == EmptySquare)) {
9858         board[fromY][fromX] = EmptySquare;
9859         board[toY][toX] = BlackPawn;
9860         if(oldEP & EP_BEROLIN_A) {
9861                 captured = board[fromY][fromX-1];
9862                 board[fromY][fromX-1] = EmptySquare;
9863         }else{  captured = board[fromY][fromX+1];
9864                 board[fromY][fromX+1] = EmptySquare;
9865         }
9866     } else {
9867         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9868         board[fromY][fromX] = EmptySquare;
9869         board[toY][toX] = piece;
9870     }
9871   }
9872
9873     if (gameInfo.holdingsWidth != 0) {
9874
9875       /* !!A lot more code needs to be written to support holdings  */
9876       /* [HGM] OK, so I have written it. Holdings are stored in the */
9877       /* penultimate board files, so they are automaticlly stored   */
9878       /* in the game history.                                       */
9879       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9880                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9881         /* Delete from holdings, by decreasing count */
9882         /* and erasing image if necessary            */
9883         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9884         if(p < (int) BlackPawn) { /* white drop */
9885              p -= (int)WhitePawn;
9886                  p = PieceToNumber((ChessSquare)p);
9887              if(p >= gameInfo.holdingsSize) p = 0;
9888              if(--board[p][BOARD_WIDTH-2] <= 0)
9889                   board[p][BOARD_WIDTH-1] = EmptySquare;
9890              if((int)board[p][BOARD_WIDTH-2] < 0)
9891                         board[p][BOARD_WIDTH-2] = 0;
9892         } else {                  /* black drop */
9893              p -= (int)BlackPawn;
9894                  p = PieceToNumber((ChessSquare)p);
9895              if(p >= gameInfo.holdingsSize) p = 0;
9896              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9897                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9898              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9899                         board[BOARD_HEIGHT-1-p][1] = 0;
9900         }
9901       }
9902       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9903           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9904         /* [HGM] holdings: Add to holdings, if holdings exist */
9905         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9906                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9907                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9908         }
9909         p = (int) captured;
9910         if (p >= (int) BlackPawn) {
9911           p -= (int)BlackPawn;
9912           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9913                   /* in Shogi restore piece to its original  first */
9914                   captured = (ChessSquare) (DEMOTED captured);
9915                   p = DEMOTED p;
9916           }
9917           p = PieceToNumber((ChessSquare)p);
9918           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9919           board[p][BOARD_WIDTH-2]++;
9920           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9921         } else {
9922           p -= (int)WhitePawn;
9923           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9924                   captured = (ChessSquare) (DEMOTED captured);
9925                   p = DEMOTED p;
9926           }
9927           p = PieceToNumber((ChessSquare)p);
9928           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9929           board[BOARD_HEIGHT-1-p][1]++;
9930           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9931         }
9932       }
9933     } else if (gameInfo.variant == VariantAtomic) {
9934       if (captured != EmptySquare) {
9935         int y, x;
9936         for (y = toY-1; y <= toY+1; y++) {
9937           for (x = toX-1; x <= toX+1; x++) {
9938             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9939                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9940               board[y][x] = EmptySquare;
9941             }
9942           }
9943         }
9944         board[toY][toX] = EmptySquare;
9945       }
9946     }
9947     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9948         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9949     } else
9950     if(promoChar == '+') {
9951         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9952         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9953     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9954         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9955         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9956            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9957         board[toY][toX] = newPiece;
9958     }
9959     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9960                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9961         // [HGM] superchess: take promotion piece out of holdings
9962         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9963         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9964             if(!--board[k][BOARD_WIDTH-2])
9965                 board[k][BOARD_WIDTH-1] = EmptySquare;
9966         } else {
9967             if(!--board[BOARD_HEIGHT-1-k][1])
9968                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9969         }
9970     }
9971 }
9972
9973 /* Updates forwardMostMove */
9974 void
9975 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9976 {
9977     int x = toX, y = toY;
9978     char *s = parseList[forwardMostMove];
9979     ChessSquare p = boards[forwardMostMove][toY][toX];
9980 //    forwardMostMove++; // [HGM] bare: moved downstream
9981
9982     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9983     (void) CoordsToAlgebraic(boards[forwardMostMove],
9984                              PosFlags(forwardMostMove),
9985                              fromY, fromX, y, x, promoChar,
9986                              s);
9987     if(killX >= 0 && killY >= 0)
9988         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9989
9990     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9991         int timeLeft; static int lastLoadFlag=0; int king, piece;
9992         piece = boards[forwardMostMove][fromY][fromX];
9993         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9994         if(gameInfo.variant == VariantKnightmate)
9995             king += (int) WhiteUnicorn - (int) WhiteKing;
9996         if(forwardMostMove == 0) {
9997             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9998                 fprintf(serverMoves, "%s;", UserName());
9999             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10000                 fprintf(serverMoves, "%s;", second.tidy);
10001             fprintf(serverMoves, "%s;", first.tidy);
10002             if(gameMode == MachinePlaysWhite)
10003                 fprintf(serverMoves, "%s;", UserName());
10004             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10005                 fprintf(serverMoves, "%s;", second.tidy);
10006         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10007         lastLoadFlag = loadFlag;
10008         // print base move
10009         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10010         // print castling suffix
10011         if( toY == fromY && piece == king ) {
10012             if(toX-fromX > 1)
10013                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10014             if(fromX-toX >1)
10015                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10016         }
10017         // e.p. suffix
10018         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10019              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10020              boards[forwardMostMove][toY][toX] == EmptySquare
10021              && fromX != toX && fromY != toY)
10022                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10023         // promotion suffix
10024         if(promoChar != NULLCHAR) {
10025             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10026                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10027                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10028             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10029         }
10030         if(!loadFlag) {
10031                 char buf[MOVE_LEN*2], *p; int len;
10032             fprintf(serverMoves, "/%d/%d",
10033                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10034             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10035             else                      timeLeft = blackTimeRemaining/1000;
10036             fprintf(serverMoves, "/%d", timeLeft);
10037                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10038                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10039                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10040                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10041             fprintf(serverMoves, "/%s", buf);
10042         }
10043         fflush(serverMoves);
10044     }
10045
10046     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10047         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10048       return;
10049     }
10050     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10051     if (commentList[forwardMostMove+1] != NULL) {
10052         free(commentList[forwardMostMove+1]);
10053         commentList[forwardMostMove+1] = NULL;
10054     }
10055     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10056     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10057     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10058     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10059     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10060     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10061     adjustedClock = FALSE;
10062     gameInfo.result = GameUnfinished;
10063     if (gameInfo.resultDetails != NULL) {
10064         free(gameInfo.resultDetails);
10065         gameInfo.resultDetails = NULL;
10066     }
10067     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10068                               moveList[forwardMostMove - 1]);
10069     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10070       case MT_NONE:
10071       case MT_STALEMATE:
10072       default:
10073         break;
10074       case MT_CHECK:
10075         if(gameInfo.variant != VariantShogi)
10076             strcat(parseList[forwardMostMove - 1], "+");
10077         break;
10078       case MT_CHECKMATE:
10079       case MT_STAINMATE:
10080         strcat(parseList[forwardMostMove - 1], "#");
10081         break;
10082     }
10083 }
10084
10085 /* Updates currentMove if not pausing */
10086 void
10087 ShowMove (int fromX, int fromY, int toX, int toY)
10088 {
10089     int instant = (gameMode == PlayFromGameFile) ?
10090         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10091     if(appData.noGUI) return;
10092     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10093         if (!instant) {
10094             if (forwardMostMove == currentMove + 1) {
10095                 AnimateMove(boards[forwardMostMove - 1],
10096                             fromX, fromY, toX, toY);
10097             }
10098         }
10099         currentMove = forwardMostMove;
10100     }
10101
10102     killX = killY = -1; // [HGM] lion: used up
10103
10104     if (instant) return;
10105
10106     DisplayMove(currentMove - 1);
10107     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10108             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10109                 SetHighlights(fromX, fromY, toX, toY);
10110             }
10111     }
10112     DrawPosition(FALSE, boards[currentMove]);
10113     DisplayBothClocks();
10114     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10115 }
10116
10117 void
10118 SendEgtPath (ChessProgramState *cps)
10119 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10120         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10121
10122         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10123
10124         while(*p) {
10125             char c, *q = name+1, *r, *s;
10126
10127             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10128             while(*p && *p != ',') *q++ = *p++;
10129             *q++ = ':'; *q = 0;
10130             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10131                 strcmp(name, ",nalimov:") == 0 ) {
10132                 // take nalimov path from the menu-changeable option first, if it is defined
10133               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10134                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10135             } else
10136             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10137                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10138                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10139                 s = r = StrStr(s, ":") + 1; // beginning of path info
10140                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10141                 c = *r; *r = 0;             // temporarily null-terminate path info
10142                     *--q = 0;               // strip of trailig ':' from name
10143                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10144                 *r = c;
10145                 SendToProgram(buf,cps);     // send egtbpath command for this format
10146             }
10147             if(*p == ',') p++; // read away comma to position for next format name
10148         }
10149 }
10150
10151 static int
10152 NonStandardBoardSize ()
10153 {
10154       /* [HGM] Awkward testing. Should really be a table */
10155       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10156       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10157       if( gameInfo.variant == VariantXiangqi )
10158            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10159       if( gameInfo.variant == VariantShogi )
10160            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10161       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10162            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10163       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10164           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10165            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10166       if( gameInfo.variant == VariantCourier )
10167            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10168       if( gameInfo.variant == VariantSuper )
10169            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10170       if( gameInfo.variant == VariantGreat )
10171            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10172       if( gameInfo.variant == VariantSChess )
10173            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10174       if( gameInfo.variant == VariantGrand )
10175            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10176       if( gameInfo.variant == VariantChu )
10177            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10178       return overruled;
10179 }
10180
10181 void
10182 InitChessProgram (ChessProgramState *cps, int setup)
10183 /* setup needed to setup FRC opening position */
10184 {
10185     char buf[MSG_SIZ], b[MSG_SIZ];
10186     if (appData.noChessProgram) return;
10187     hintRequested = FALSE;
10188     bookRequested = FALSE;
10189
10190     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10191     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10192     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10193     if(cps->memSize) { /* [HGM] memory */
10194       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10195         SendToProgram(buf, cps);
10196     }
10197     SendEgtPath(cps); /* [HGM] EGT */
10198     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10199       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10200         SendToProgram(buf, cps);
10201     }
10202
10203     SendToProgram(cps->initString, cps);
10204     if (gameInfo.variant != VariantNormal &&
10205         gameInfo.variant != VariantLoadable
10206         /* [HGM] also send variant if board size non-standard */
10207         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10208                                             ) {
10209       char *v = VariantName(gameInfo.variant);
10210       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10211         /* [HGM] in protocol 1 we have to assume all variants valid */
10212         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10213         DisplayFatalError(buf, 0, 1);
10214         return;
10215       }
10216
10217       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10218         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10219                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10220            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10221            if(StrStr(cps->variants, b) == NULL) {
10222                // specific sized variant not known, check if general sizing allowed
10223                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10224                    if(StrStr(cps->variants, "boardsize") == NULL) {
10225                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10226                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10227                        DisplayFatalError(buf, 0, 1);
10228                        return;
10229                    }
10230                    /* [HGM] here we really should compare with the maximum supported board size */
10231                }
10232            }
10233       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10234       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10235       SendToProgram(buf, cps);
10236     }
10237     currentlyInitializedVariant = gameInfo.variant;
10238
10239     /* [HGM] send opening position in FRC to first engine */
10240     if(setup) {
10241           SendToProgram("force\n", cps);
10242           SendBoard(cps, 0);
10243           /* engine is now in force mode! Set flag to wake it up after first move. */
10244           setboardSpoiledMachineBlack = 1;
10245     }
10246
10247     if (cps->sendICS) {
10248       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10249       SendToProgram(buf, cps);
10250     }
10251     cps->maybeThinking = FALSE;
10252     cps->offeredDraw = 0;
10253     if (!appData.icsActive) {
10254         SendTimeControl(cps, movesPerSession, timeControl,
10255                         timeIncrement, appData.searchDepth,
10256                         searchTime);
10257     }
10258     if (appData.showThinking
10259         // [HGM] thinking: four options require thinking output to be sent
10260         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10261                                 ) {
10262         SendToProgram("post\n", cps);
10263     }
10264     SendToProgram("hard\n", cps);
10265     if (!appData.ponderNextMove) {
10266         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10267            it without being sure what state we are in first.  "hard"
10268            is not a toggle, so that one is OK.
10269          */
10270         SendToProgram("easy\n", cps);
10271     }
10272     if (cps->usePing) {
10273       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10274       SendToProgram(buf, cps);
10275     }
10276     cps->initDone = TRUE;
10277     ClearEngineOutputPane(cps == &second);
10278 }
10279
10280
10281 void
10282 ResendOptions (ChessProgramState *cps)
10283 { // send the stored value of the options
10284   int i;
10285   char buf[MSG_SIZ];
10286   Option *opt = cps->option;
10287   for(i=0; i<cps->nrOptions; i++, opt++) {
10288       switch(opt->type) {
10289         case Spin:
10290         case Slider:
10291         case CheckBox:
10292             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10293           break;
10294         case ComboBox:
10295           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10296           break;
10297         default:
10298             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10299           break;
10300         case Button:
10301         case SaveButton:
10302           continue;
10303       }
10304       SendToProgram(buf, cps);
10305   }
10306 }
10307
10308 void
10309 StartChessProgram (ChessProgramState *cps)
10310 {
10311     char buf[MSG_SIZ];
10312     int err;
10313
10314     if (appData.noChessProgram) return;
10315     cps->initDone = FALSE;
10316
10317     if (strcmp(cps->host, "localhost") == 0) {
10318         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10319     } else if (*appData.remoteShell == NULLCHAR) {
10320         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10321     } else {
10322         if (*appData.remoteUser == NULLCHAR) {
10323           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10324                     cps->program);
10325         } else {
10326           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10327                     cps->host, appData.remoteUser, cps->program);
10328         }
10329         err = StartChildProcess(buf, "", &cps->pr);
10330     }
10331
10332     if (err != 0) {
10333       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10334         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10335         if(cps != &first) return;
10336         appData.noChessProgram = TRUE;
10337         ThawUI();
10338         SetNCPMode();
10339 //      DisplayFatalError(buf, err, 1);
10340 //      cps->pr = NoProc;
10341 //      cps->isr = NULL;
10342         return;
10343     }
10344
10345     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10346     if (cps->protocolVersion > 1) {
10347       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10348       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10349         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10350         cps->comboCnt = 0;  //                and values of combo boxes
10351       }
10352       SendToProgram(buf, cps);
10353       if(cps->reload) ResendOptions(cps);
10354     } else {
10355       SendToProgram("xboard\n", cps);
10356     }
10357 }
10358
10359 void
10360 TwoMachinesEventIfReady P((void))
10361 {
10362   static int curMess = 0;
10363   if (first.lastPing != first.lastPong) {
10364     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10365     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10366     return;
10367   }
10368   if (second.lastPing != second.lastPong) {
10369     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10370     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10371     return;
10372   }
10373   DisplayMessage("", ""); curMess = 0;
10374   TwoMachinesEvent();
10375 }
10376
10377 char *
10378 MakeName (char *template)
10379 {
10380     time_t clock;
10381     struct tm *tm;
10382     static char buf[MSG_SIZ];
10383     char *p = buf;
10384     int i;
10385
10386     clock = time((time_t *)NULL);
10387     tm = localtime(&clock);
10388
10389     while(*p++ = *template++) if(p[-1] == '%') {
10390         switch(*template++) {
10391           case 0:   *p = 0; return buf;
10392           case 'Y': i = tm->tm_year+1900; break;
10393           case 'y': i = tm->tm_year-100; break;
10394           case 'M': i = tm->tm_mon+1; break;
10395           case 'd': i = tm->tm_mday; break;
10396           case 'h': i = tm->tm_hour; break;
10397           case 'm': i = tm->tm_min; break;
10398           case 's': i = tm->tm_sec; break;
10399           default:  i = 0;
10400         }
10401         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10402     }
10403     return buf;
10404 }
10405
10406 int
10407 CountPlayers (char *p)
10408 {
10409     int n = 0;
10410     while(p = strchr(p, '\n')) p++, n++; // count participants
10411     return n;
10412 }
10413
10414 FILE *
10415 WriteTourneyFile (char *results, FILE *f)
10416 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10417     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10418     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10419         // create a file with tournament description
10420         fprintf(f, "-participants {%s}\n", appData.participants);
10421         fprintf(f, "-seedBase %d\n", appData.seedBase);
10422         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10423         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10424         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10425         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10426         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10427         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10428         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10429         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10430         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10431         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10432         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10433         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10434         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10435         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10436         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10437         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10438         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10439         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10440         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10441         fprintf(f, "-smpCores %d\n", appData.smpCores);
10442         if(searchTime > 0)
10443                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10444         else {
10445                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10446                 fprintf(f, "-tc %s\n", appData.timeControl);
10447                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10448         }
10449         fprintf(f, "-results \"%s\"\n", results);
10450     }
10451     return f;
10452 }
10453
10454 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10455
10456 void
10457 Substitute (char *participants, int expunge)
10458 {
10459     int i, changed, changes=0, nPlayers=0;
10460     char *p, *q, *r, buf[MSG_SIZ];
10461     if(participants == NULL) return;
10462     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10463     r = p = participants; q = appData.participants;
10464     while(*p && *p == *q) {
10465         if(*p == '\n') r = p+1, nPlayers++;
10466         p++; q++;
10467     }
10468     if(*p) { // difference
10469         while(*p && *p++ != '\n');
10470         while(*q && *q++ != '\n');
10471       changed = nPlayers;
10472         changes = 1 + (strcmp(p, q) != 0);
10473     }
10474     if(changes == 1) { // a single engine mnemonic was changed
10475         q = r; while(*q) nPlayers += (*q++ == '\n');
10476         p = buf; while(*r && (*p = *r++) != '\n') p++;
10477         *p = NULLCHAR;
10478         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10479         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10480         if(mnemonic[i]) { // The substitute is valid
10481             FILE *f;
10482             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10483                 flock(fileno(f), LOCK_EX);
10484                 ParseArgsFromFile(f);
10485                 fseek(f, 0, SEEK_SET);
10486                 FREE(appData.participants); appData.participants = participants;
10487                 if(expunge) { // erase results of replaced engine
10488                     int len = strlen(appData.results), w, b, dummy;
10489                     for(i=0; i<len; i++) {
10490                         Pairing(i, nPlayers, &w, &b, &dummy);
10491                         if((w == changed || b == changed) && appData.results[i] == '*') {
10492                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10493                             fclose(f);
10494                             return;
10495                         }
10496                     }
10497                     for(i=0; i<len; i++) {
10498                         Pairing(i, nPlayers, &w, &b, &dummy);
10499                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10500                     }
10501                 }
10502                 WriteTourneyFile(appData.results, f);
10503                 fclose(f); // release lock
10504                 return;
10505             }
10506         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10507     }
10508     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10509     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10510     free(participants);
10511     return;
10512 }
10513
10514 int
10515 CheckPlayers (char *participants)
10516 {
10517         int i;
10518         char buf[MSG_SIZ], *p;
10519         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10520         while(p = strchr(participants, '\n')) {
10521             *p = NULLCHAR;
10522             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10523             if(!mnemonic[i]) {
10524                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10525                 *p = '\n';
10526                 DisplayError(buf, 0);
10527                 return 1;
10528             }
10529             *p = '\n';
10530             participants = p + 1;
10531         }
10532         return 0;
10533 }
10534
10535 int
10536 CreateTourney (char *name)
10537 {
10538         FILE *f;
10539         if(matchMode && strcmp(name, appData.tourneyFile)) {
10540              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10541         }
10542         if(name[0] == NULLCHAR) {
10543             if(appData.participants[0])
10544                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10545             return 0;
10546         }
10547         f = fopen(name, "r");
10548         if(f) { // file exists
10549             ASSIGN(appData.tourneyFile, name);
10550             ParseArgsFromFile(f); // parse it
10551         } else {
10552             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10553             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10554                 DisplayError(_("Not enough participants"), 0);
10555                 return 0;
10556             }
10557             if(CheckPlayers(appData.participants)) return 0;
10558             ASSIGN(appData.tourneyFile, name);
10559             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10560             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10561         }
10562         fclose(f);
10563         appData.noChessProgram = FALSE;
10564         appData.clockMode = TRUE;
10565         SetGNUMode();
10566         return 1;
10567 }
10568
10569 int
10570 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10571 {
10572     char buf[MSG_SIZ], *p, *q;
10573     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10574     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10575     skip = !all && group[0]; // if group requested, we start in skip mode
10576     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10577         p = names; q = buf; header = 0;
10578         while(*p && *p != '\n') *q++ = *p++;
10579         *q = 0;
10580         if(*p == '\n') p++;
10581         if(buf[0] == '#') {
10582             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10583             depth++; // we must be entering a new group
10584             if(all) continue; // suppress printing group headers when complete list requested
10585             header = 1;
10586             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10587         }
10588         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10589         if(engineList[i]) free(engineList[i]);
10590         engineList[i] = strdup(buf);
10591         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10592         if(engineMnemonic[i]) free(engineMnemonic[i]);
10593         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10594             strcat(buf, " (");
10595             sscanf(q + 8, "%s", buf + strlen(buf));
10596             strcat(buf, ")");
10597         }
10598         engineMnemonic[i] = strdup(buf);
10599         i++;
10600     }
10601     engineList[i] = engineMnemonic[i] = NULL;
10602     return i;
10603 }
10604
10605 // following implemented as macro to avoid type limitations
10606 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10607
10608 void
10609 SwapEngines (int n)
10610 {   // swap settings for first engine and other engine (so far only some selected options)
10611     int h;
10612     char *p;
10613     if(n == 0) return;
10614     SWAP(directory, p)
10615     SWAP(chessProgram, p)
10616     SWAP(isUCI, h)
10617     SWAP(hasOwnBookUCI, h)
10618     SWAP(protocolVersion, h)
10619     SWAP(reuse, h)
10620     SWAP(scoreIsAbsolute, h)
10621     SWAP(timeOdds, h)
10622     SWAP(logo, p)
10623     SWAP(pgnName, p)
10624     SWAP(pvSAN, h)
10625     SWAP(engOptions, p)
10626     SWAP(engInitString, p)
10627     SWAP(computerString, p)
10628     SWAP(features, p)
10629     SWAP(fenOverride, p)
10630     SWAP(NPS, h)
10631     SWAP(accumulateTC, h)
10632     SWAP(host, p)
10633 }
10634
10635 int
10636 GetEngineLine (char *s, int n)
10637 {
10638     int i;
10639     char buf[MSG_SIZ];
10640     extern char *icsNames;
10641     if(!s || !*s) return 0;
10642     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10643     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10644     if(!mnemonic[i]) return 0;
10645     if(n == 11) return 1; // just testing if there was a match
10646     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10647     if(n == 1) SwapEngines(n);
10648     ParseArgsFromString(buf);
10649     if(n == 1) SwapEngines(n);
10650     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10651         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10652         ParseArgsFromString(buf);
10653     }
10654     return 1;
10655 }
10656
10657 int
10658 SetPlayer (int player, char *p)
10659 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10660     int i;
10661     char buf[MSG_SIZ], *engineName;
10662     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10663     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10664     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10665     if(mnemonic[i]) {
10666         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10667         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10668         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10669         ParseArgsFromString(buf);
10670     } else { // no engine with this nickname is installed!
10671         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10672         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10673         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10674         ModeHighlight();
10675         DisplayError(buf, 0);
10676         return 0;
10677     }
10678     free(engineName);
10679     return i;
10680 }
10681
10682 char *recentEngines;
10683
10684 void
10685 RecentEngineEvent (int nr)
10686 {
10687     int n;
10688 //    SwapEngines(1); // bump first to second
10689 //    ReplaceEngine(&second, 1); // and load it there
10690     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10691     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10692     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10693         ReplaceEngine(&first, 0);
10694         FloatToFront(&appData.recentEngineList, command[n]);
10695     }
10696 }
10697
10698 int
10699 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10700 {   // determine players from game number
10701     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10702
10703     if(appData.tourneyType == 0) {
10704         roundsPerCycle = (nPlayers - 1) | 1;
10705         pairingsPerRound = nPlayers / 2;
10706     } else if(appData.tourneyType > 0) {
10707         roundsPerCycle = nPlayers - appData.tourneyType;
10708         pairingsPerRound = appData.tourneyType;
10709     }
10710     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10711     gamesPerCycle = gamesPerRound * roundsPerCycle;
10712     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10713     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10714     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10715     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10716     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10717     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10718
10719     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10720     if(appData.roundSync) *syncInterval = gamesPerRound;
10721
10722     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10723
10724     if(appData.tourneyType == 0) {
10725         if(curPairing == (nPlayers-1)/2 ) {
10726             *whitePlayer = curRound;
10727             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10728         } else {
10729             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10730             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10731             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10732             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10733         }
10734     } else if(appData.tourneyType > 1) {
10735         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10736         *whitePlayer = curRound + appData.tourneyType;
10737     } else if(appData.tourneyType > 0) {
10738         *whitePlayer = curPairing;
10739         *blackPlayer = curRound + appData.tourneyType;
10740     }
10741
10742     // take care of white/black alternation per round.
10743     // For cycles and games this is already taken care of by default, derived from matchGame!
10744     return curRound & 1;
10745 }
10746
10747 int
10748 NextTourneyGame (int nr, int *swapColors)
10749 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10750     char *p, *q;
10751     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10752     FILE *tf;
10753     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10754     tf = fopen(appData.tourneyFile, "r");
10755     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10756     ParseArgsFromFile(tf); fclose(tf);
10757     InitTimeControls(); // TC might be altered from tourney file
10758
10759     nPlayers = CountPlayers(appData.participants); // count participants
10760     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10761     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10762
10763     if(syncInterval) {
10764         p = q = appData.results;
10765         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10766         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10767             DisplayMessage(_("Waiting for other game(s)"),"");
10768             waitingForGame = TRUE;
10769             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10770             return 0;
10771         }
10772         waitingForGame = FALSE;
10773     }
10774
10775     if(appData.tourneyType < 0) {
10776         if(nr>=0 && !pairingReceived) {
10777             char buf[1<<16];
10778             if(pairing.pr == NoProc) {
10779                 if(!appData.pairingEngine[0]) {
10780                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10781                     return 0;
10782                 }
10783                 StartChessProgram(&pairing); // starts the pairing engine
10784             }
10785             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10786             SendToProgram(buf, &pairing);
10787             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10788             SendToProgram(buf, &pairing);
10789             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10790         }
10791         pairingReceived = 0;                              // ... so we continue here
10792         *swapColors = 0;
10793         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10794         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10795         matchGame = 1; roundNr = nr / syncInterval + 1;
10796     }
10797
10798     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10799
10800     // redefine engines, engine dir, etc.
10801     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10802     if(first.pr == NoProc) {
10803       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10804       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10805     }
10806     if(second.pr == NoProc) {
10807       SwapEngines(1);
10808       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10809       SwapEngines(1);         // and make that valid for second engine by swapping
10810       InitEngine(&second, 1);
10811     }
10812     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10813     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10814     return OK;
10815 }
10816
10817 void
10818 NextMatchGame ()
10819 {   // performs game initialization that does not invoke engines, and then tries to start the game
10820     int res, firstWhite, swapColors = 0;
10821     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10822     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
10823         char buf[MSG_SIZ];
10824         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10825         if(strcmp(buf, currentDebugFile)) { // name has changed
10826             FILE *f = fopen(buf, "w");
10827             if(f) { // if opening the new file failed, just keep using the old one
10828                 ASSIGN(currentDebugFile, buf);
10829                 fclose(debugFP);
10830                 debugFP = f;
10831             }
10832             if(appData.serverFileName) {
10833                 if(serverFP) fclose(serverFP);
10834                 serverFP = fopen(appData.serverFileName, "w");
10835                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10836                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10837             }
10838         }
10839     }
10840     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10841     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10842     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10843     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10844     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10845     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10846     Reset(FALSE, first.pr != NoProc);
10847     res = LoadGameOrPosition(matchGame); // setup game
10848     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10849     if(!res) return; // abort when bad game/pos file
10850     TwoMachinesEvent();
10851 }
10852
10853 void
10854 UserAdjudicationEvent (int result)
10855 {
10856     ChessMove gameResult = GameIsDrawn;
10857
10858     if( result > 0 ) {
10859         gameResult = WhiteWins;
10860     }
10861     else if( result < 0 ) {
10862         gameResult = BlackWins;
10863     }
10864
10865     if( gameMode == TwoMachinesPlay ) {
10866         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10867     }
10868 }
10869
10870
10871 // [HGM] save: calculate checksum of game to make games easily identifiable
10872 int
10873 StringCheckSum (char *s)
10874 {
10875         int i = 0;
10876         if(s==NULL) return 0;
10877         while(*s) i = i*259 + *s++;
10878         return i;
10879 }
10880
10881 int
10882 GameCheckSum ()
10883 {
10884         int i, sum=0;
10885         for(i=backwardMostMove; i<forwardMostMove; i++) {
10886                 sum += pvInfoList[i].depth;
10887                 sum += StringCheckSum(parseList[i]);
10888                 sum += StringCheckSum(commentList[i]);
10889                 sum *= 261;
10890         }
10891         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10892         return sum + StringCheckSum(commentList[i]);
10893 } // end of save patch
10894
10895 void
10896 GameEnds (ChessMove result, char *resultDetails, int whosays)
10897 {
10898     GameMode nextGameMode;
10899     int isIcsGame;
10900     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10901
10902     if(endingGame) return; /* [HGM] crash: forbid recursion */
10903     endingGame = 1;
10904     if(twoBoards) { // [HGM] dual: switch back to one board
10905         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10906         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10907     }
10908     if (appData.debugMode) {
10909       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10910               result, resultDetails ? resultDetails : "(null)", whosays);
10911     }
10912
10913     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10914
10915     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10916
10917     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10918         /* If we are playing on ICS, the server decides when the
10919            game is over, but the engine can offer to draw, claim
10920            a draw, or resign.
10921          */
10922 #if ZIPPY
10923         if (appData.zippyPlay && first.initDone) {
10924             if (result == GameIsDrawn) {
10925                 /* In case draw still needs to be claimed */
10926                 SendToICS(ics_prefix);
10927                 SendToICS("draw\n");
10928             } else if (StrCaseStr(resultDetails, "resign")) {
10929                 SendToICS(ics_prefix);
10930                 SendToICS("resign\n");
10931             }
10932         }
10933 #endif
10934         endingGame = 0; /* [HGM] crash */
10935         return;
10936     }
10937
10938     /* If we're loading the game from a file, stop */
10939     if (whosays == GE_FILE) {
10940       (void) StopLoadGameTimer();
10941       gameFileFP = NULL;
10942     }
10943
10944     /* Cancel draw offers */
10945     first.offeredDraw = second.offeredDraw = 0;
10946
10947     /* If this is an ICS game, only ICS can really say it's done;
10948        if not, anyone can. */
10949     isIcsGame = (gameMode == IcsPlayingWhite ||
10950                  gameMode == IcsPlayingBlack ||
10951                  gameMode == IcsObserving    ||
10952                  gameMode == IcsExamining);
10953
10954     if (!isIcsGame || whosays == GE_ICS) {
10955         /* OK -- not an ICS game, or ICS said it was done */
10956         StopClocks();
10957         if (!isIcsGame && !appData.noChessProgram)
10958           SetUserThinkingEnables();
10959
10960         /* [HGM] if a machine claims the game end we verify this claim */
10961         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10962             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10963                 char claimer;
10964                 ChessMove trueResult = (ChessMove) -1;
10965
10966                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10967                                             first.twoMachinesColor[0] :
10968                                             second.twoMachinesColor[0] ;
10969
10970                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10971                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10972                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10973                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10974                 } else
10975                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10976                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10977                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10978                 } else
10979                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10980                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10981                 }
10982
10983                 // now verify win claims, but not in drop games, as we don't understand those yet
10984                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10985                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10986                     (result == WhiteWins && claimer == 'w' ||
10987                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10988                       if (appData.debugMode) {
10989                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10990                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10991                       }
10992                       if(result != trueResult) {
10993                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10994                               result = claimer == 'w' ? BlackWins : WhiteWins;
10995                               resultDetails = buf;
10996                       }
10997                 } else
10998                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10999                     && (forwardMostMove <= backwardMostMove ||
11000                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11001                         (claimer=='b')==(forwardMostMove&1))
11002                                                                                   ) {
11003                       /* [HGM] verify: draws that were not flagged are false claims */
11004                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11005                       result = claimer == 'w' ? BlackWins : WhiteWins;
11006                       resultDetails = buf;
11007                 }
11008                 /* (Claiming a loss is accepted no questions asked!) */
11009             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11010                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11011                 result = GameUnfinished;
11012                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11013             }
11014             /* [HGM] bare: don't allow bare King to win */
11015             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11016                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11017                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11018                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11019                && result != GameIsDrawn)
11020             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11021                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11022                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11023                         if(p >= 0 && p <= (int)WhiteKing) k++;
11024                 }
11025                 if (appData.debugMode) {
11026                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11027                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11028                 }
11029                 if(k <= 1) {
11030                         result = GameIsDrawn;
11031                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11032                         resultDetails = buf;
11033                 }
11034             }
11035         }
11036
11037
11038         if(serverMoves != NULL && !loadFlag) { char c = '=';
11039             if(result==WhiteWins) c = '+';
11040             if(result==BlackWins) c = '-';
11041             if(resultDetails != NULL)
11042                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11043         }
11044         if (resultDetails != NULL) {
11045             gameInfo.result = result;
11046             gameInfo.resultDetails = StrSave(resultDetails);
11047
11048             /* display last move only if game was not loaded from file */
11049             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11050                 DisplayMove(currentMove - 1);
11051
11052             if (forwardMostMove != 0) {
11053                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11054                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11055                                                                 ) {
11056                     if (*appData.saveGameFile != NULLCHAR) {
11057                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11058                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11059                         else
11060                         SaveGameToFile(appData.saveGameFile, TRUE);
11061                     } else if (appData.autoSaveGames) {
11062                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11063                     }
11064                     if (*appData.savePositionFile != NULLCHAR) {
11065                         SavePositionToFile(appData.savePositionFile);
11066                     }
11067                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11068                 }
11069             }
11070
11071             /* Tell program how game ended in case it is learning */
11072             /* [HGM] Moved this to after saving the PGN, just in case */
11073             /* engine died and we got here through time loss. In that */
11074             /* case we will get a fatal error writing the pipe, which */
11075             /* would otherwise lose us the PGN.                       */
11076             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11077             /* output during GameEnds should never be fatal anymore   */
11078             if (gameMode == MachinePlaysWhite ||
11079                 gameMode == MachinePlaysBlack ||
11080                 gameMode == TwoMachinesPlay ||
11081                 gameMode == IcsPlayingWhite ||
11082                 gameMode == IcsPlayingBlack ||
11083                 gameMode == BeginningOfGame) {
11084                 char buf[MSG_SIZ];
11085                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11086                         resultDetails);
11087                 if (first.pr != NoProc) {
11088                     SendToProgram(buf, &first);
11089                 }
11090                 if (second.pr != NoProc &&
11091                     gameMode == TwoMachinesPlay) {
11092                     SendToProgram(buf, &second);
11093                 }
11094             }
11095         }
11096
11097         if (appData.icsActive) {
11098             if (appData.quietPlay &&
11099                 (gameMode == IcsPlayingWhite ||
11100                  gameMode == IcsPlayingBlack)) {
11101                 SendToICS(ics_prefix);
11102                 SendToICS("set shout 1\n");
11103             }
11104             nextGameMode = IcsIdle;
11105             ics_user_moved = FALSE;
11106             /* clean up premove.  It's ugly when the game has ended and the
11107              * premove highlights are still on the board.
11108              */
11109             if (gotPremove) {
11110               gotPremove = FALSE;
11111               ClearPremoveHighlights();
11112               DrawPosition(FALSE, boards[currentMove]);
11113             }
11114             if (whosays == GE_ICS) {
11115                 switch (result) {
11116                 case WhiteWins:
11117                     if (gameMode == IcsPlayingWhite)
11118                         PlayIcsWinSound();
11119                     else if(gameMode == IcsPlayingBlack)
11120                         PlayIcsLossSound();
11121                     break;
11122                 case BlackWins:
11123                     if (gameMode == IcsPlayingBlack)
11124                         PlayIcsWinSound();
11125                     else if(gameMode == IcsPlayingWhite)
11126                         PlayIcsLossSound();
11127                     break;
11128                 case GameIsDrawn:
11129                     PlayIcsDrawSound();
11130                     break;
11131                 default:
11132                     PlayIcsUnfinishedSound();
11133                 }
11134             }
11135             if(appData.quitNext) { ExitEvent(0); return; }
11136         } else if (gameMode == EditGame ||
11137                    gameMode == PlayFromGameFile ||
11138                    gameMode == AnalyzeMode ||
11139                    gameMode == AnalyzeFile) {
11140             nextGameMode = gameMode;
11141         } else {
11142             nextGameMode = EndOfGame;
11143         }
11144         pausing = FALSE;
11145         ModeHighlight();
11146     } else {
11147         nextGameMode = gameMode;
11148     }
11149
11150     if (appData.noChessProgram) {
11151         gameMode = nextGameMode;
11152         ModeHighlight();
11153         endingGame = 0; /* [HGM] crash */
11154         return;
11155     }
11156
11157     if (first.reuse) {
11158         /* Put first chess program into idle state */
11159         if (first.pr != NoProc &&
11160             (gameMode == MachinePlaysWhite ||
11161              gameMode == MachinePlaysBlack ||
11162              gameMode == TwoMachinesPlay ||
11163              gameMode == IcsPlayingWhite ||
11164              gameMode == IcsPlayingBlack ||
11165              gameMode == BeginningOfGame)) {
11166             SendToProgram("force\n", &first);
11167             if (first.usePing) {
11168               char buf[MSG_SIZ];
11169               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11170               SendToProgram(buf, &first);
11171             }
11172         }
11173     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11174         /* Kill off first chess program */
11175         if (first.isr != NULL)
11176           RemoveInputSource(first.isr);
11177         first.isr = NULL;
11178
11179         if (first.pr != NoProc) {
11180             ExitAnalyzeMode();
11181             DoSleep( appData.delayBeforeQuit );
11182             SendToProgram("quit\n", &first);
11183             DoSleep( appData.delayAfterQuit );
11184             DestroyChildProcess(first.pr, first.useSigterm);
11185             first.reload = TRUE;
11186         }
11187         first.pr = NoProc;
11188     }
11189     if (second.reuse) {
11190         /* Put second chess program into idle state */
11191         if (second.pr != NoProc &&
11192             gameMode == TwoMachinesPlay) {
11193             SendToProgram("force\n", &second);
11194             if (second.usePing) {
11195               char buf[MSG_SIZ];
11196               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11197               SendToProgram(buf, &second);
11198             }
11199         }
11200     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11201         /* Kill off second chess program */
11202         if (second.isr != NULL)
11203           RemoveInputSource(second.isr);
11204         second.isr = NULL;
11205
11206         if (second.pr != NoProc) {
11207             DoSleep( appData.delayBeforeQuit );
11208             SendToProgram("quit\n", &second);
11209             DoSleep( appData.delayAfterQuit );
11210             DestroyChildProcess(second.pr, second.useSigterm);
11211             second.reload = TRUE;
11212         }
11213         second.pr = NoProc;
11214     }
11215
11216     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11217         char resChar = '=';
11218         switch (result) {
11219         case WhiteWins:
11220           resChar = '+';
11221           if (first.twoMachinesColor[0] == 'w') {
11222             first.matchWins++;
11223           } else {
11224             second.matchWins++;
11225           }
11226           break;
11227         case BlackWins:
11228           resChar = '-';
11229           if (first.twoMachinesColor[0] == 'b') {
11230             first.matchWins++;
11231           } else {
11232             second.matchWins++;
11233           }
11234           break;
11235         case GameUnfinished:
11236           resChar = ' ';
11237         default:
11238           break;
11239         }
11240
11241         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11242         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11243             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11244             ReserveGame(nextGame, resChar); // sets nextGame
11245             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11246             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11247         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11248
11249         if (nextGame <= appData.matchGames && !abortMatch) {
11250             gameMode = nextGameMode;
11251             matchGame = nextGame; // this will be overruled in tourney mode!
11252             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11253             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11254             endingGame = 0; /* [HGM] crash */
11255             return;
11256         } else {
11257             gameMode = nextGameMode;
11258             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11259                      first.tidy, second.tidy,
11260                      first.matchWins, second.matchWins,
11261                      appData.matchGames - (first.matchWins + second.matchWins));
11262             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11263             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11264             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11265             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11266                 first.twoMachinesColor = "black\n";
11267                 second.twoMachinesColor = "white\n";
11268             } else {
11269                 first.twoMachinesColor = "white\n";
11270                 second.twoMachinesColor = "black\n";
11271             }
11272         }
11273     }
11274     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11275         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11276       ExitAnalyzeMode();
11277     gameMode = nextGameMode;
11278     ModeHighlight();
11279     endingGame = 0;  /* [HGM] crash */
11280     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11281         if(matchMode == TRUE) { // match through command line: exit with or without popup
11282             if(ranking) {
11283                 ToNrEvent(forwardMostMove);
11284                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11285                 else ExitEvent(0);
11286             } else DisplayFatalError(buf, 0, 0);
11287         } else { // match through menu; just stop, with or without popup
11288             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11289             ModeHighlight();
11290             if(ranking){
11291                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11292             } else DisplayNote(buf);
11293       }
11294       if(ranking) free(ranking);
11295     }
11296 }
11297
11298 /* Assumes program was just initialized (initString sent).
11299    Leaves program in force mode. */
11300 void
11301 FeedMovesToProgram (ChessProgramState *cps, int upto)
11302 {
11303     int i;
11304
11305     if (appData.debugMode)
11306       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11307               startedFromSetupPosition ? "position and " : "",
11308               backwardMostMove, upto, cps->which);
11309     if(currentlyInitializedVariant != gameInfo.variant) {
11310       char buf[MSG_SIZ];
11311         // [HGM] variantswitch: make engine aware of new variant
11312         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11313                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11314         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11315         SendToProgram(buf, cps);
11316         currentlyInitializedVariant = gameInfo.variant;
11317     }
11318     SendToProgram("force\n", cps);
11319     if (startedFromSetupPosition) {
11320         SendBoard(cps, backwardMostMove);
11321     if (appData.debugMode) {
11322         fprintf(debugFP, "feedMoves\n");
11323     }
11324     }
11325     for (i = backwardMostMove; i < upto; i++) {
11326         SendMoveToProgram(i, cps);
11327     }
11328 }
11329
11330
11331 int
11332 ResurrectChessProgram ()
11333 {
11334      /* The chess program may have exited.
11335         If so, restart it and feed it all the moves made so far. */
11336     static int doInit = 0;
11337
11338     if (appData.noChessProgram) return 1;
11339
11340     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11341         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11342         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11343         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11344     } else {
11345         if (first.pr != NoProc) return 1;
11346         StartChessProgram(&first);
11347     }
11348     InitChessProgram(&first, FALSE);
11349     FeedMovesToProgram(&first, currentMove);
11350
11351     if (!first.sendTime) {
11352         /* can't tell gnuchess what its clock should read,
11353            so we bow to its notion. */
11354         ResetClocks();
11355         timeRemaining[0][currentMove] = whiteTimeRemaining;
11356         timeRemaining[1][currentMove] = blackTimeRemaining;
11357     }
11358
11359     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11360                 appData.icsEngineAnalyze) && first.analysisSupport) {
11361       SendToProgram("analyze\n", &first);
11362       first.analyzing = TRUE;
11363     }
11364     return 1;
11365 }
11366
11367 /*
11368  * Button procedures
11369  */
11370 void
11371 Reset (int redraw, int init)
11372 {
11373     int i;
11374
11375     if (appData.debugMode) {
11376         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11377                 redraw, init, gameMode);
11378     }
11379     CleanupTail(); // [HGM] vari: delete any stored variations
11380     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11381     pausing = pauseExamInvalid = FALSE;
11382     startedFromSetupPosition = blackPlaysFirst = FALSE;
11383     firstMove = TRUE;
11384     whiteFlag = blackFlag = FALSE;
11385     userOfferedDraw = FALSE;
11386     hintRequested = bookRequested = FALSE;
11387     first.maybeThinking = FALSE;
11388     second.maybeThinking = FALSE;
11389     first.bookSuspend = FALSE; // [HGM] book
11390     second.bookSuspend = FALSE;
11391     thinkOutput[0] = NULLCHAR;
11392     lastHint[0] = NULLCHAR;
11393     ClearGameInfo(&gameInfo);
11394     gameInfo.variant = StringToVariant(appData.variant);
11395     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11396     ics_user_moved = ics_clock_paused = FALSE;
11397     ics_getting_history = H_FALSE;
11398     ics_gamenum = -1;
11399     white_holding[0] = black_holding[0] = NULLCHAR;
11400     ClearProgramStats();
11401     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11402
11403     ResetFrontEnd();
11404     ClearHighlights();
11405     flipView = appData.flipView;
11406     ClearPremoveHighlights();
11407     gotPremove = FALSE;
11408     alarmSounded = FALSE;
11409     killX = killY = -1; // [HGM] lion
11410
11411     GameEnds(EndOfFile, NULL, GE_PLAYER);
11412     if(appData.serverMovesName != NULL) {
11413         /* [HGM] prepare to make moves file for broadcasting */
11414         clock_t t = clock();
11415         if(serverMoves != NULL) fclose(serverMoves);
11416         serverMoves = fopen(appData.serverMovesName, "r");
11417         if(serverMoves != NULL) {
11418             fclose(serverMoves);
11419             /* delay 15 sec before overwriting, so all clients can see end */
11420             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11421         }
11422         serverMoves = fopen(appData.serverMovesName, "w");
11423     }
11424
11425     ExitAnalyzeMode();
11426     gameMode = BeginningOfGame;
11427     ModeHighlight();
11428     if(appData.icsActive) gameInfo.variant = VariantNormal;
11429     currentMove = forwardMostMove = backwardMostMove = 0;
11430     MarkTargetSquares(1);
11431     InitPosition(redraw);
11432     for (i = 0; i < MAX_MOVES; i++) {
11433         if (commentList[i] != NULL) {
11434             free(commentList[i]);
11435             commentList[i] = NULL;
11436         }
11437     }
11438     ResetClocks();
11439     timeRemaining[0][0] = whiteTimeRemaining;
11440     timeRemaining[1][0] = blackTimeRemaining;
11441
11442     if (first.pr == NoProc) {
11443         StartChessProgram(&first);
11444     }
11445     if (init) {
11446             InitChessProgram(&first, startedFromSetupPosition);
11447     }
11448     DisplayTitle("");
11449     DisplayMessage("", "");
11450     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11451     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11452     ClearMap();        // [HGM] exclude: invalidate map
11453 }
11454
11455 void
11456 AutoPlayGameLoop ()
11457 {
11458     for (;;) {
11459         if (!AutoPlayOneMove())
11460           return;
11461         if (matchMode || appData.timeDelay == 0)
11462           continue;
11463         if (appData.timeDelay < 0)
11464           return;
11465         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11466         break;
11467     }
11468 }
11469
11470 void
11471 AnalyzeNextGame()
11472 {
11473     ReloadGame(1); // next game
11474 }
11475
11476 int
11477 AutoPlayOneMove ()
11478 {
11479     int fromX, fromY, toX, toY;
11480
11481     if (appData.debugMode) {
11482       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11483     }
11484
11485     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11486       return FALSE;
11487
11488     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11489       pvInfoList[currentMove].depth = programStats.depth;
11490       pvInfoList[currentMove].score = programStats.score;
11491       pvInfoList[currentMove].time  = 0;
11492       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11493       else { // append analysis of final position as comment
11494         char buf[MSG_SIZ];
11495         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11496         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11497       }
11498       programStats.depth = 0;
11499     }
11500
11501     if (currentMove >= forwardMostMove) {
11502       if(gameMode == AnalyzeFile) {
11503           if(appData.loadGameIndex == -1) {
11504             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11505           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11506           } else {
11507           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11508         }
11509       }
11510 //      gameMode = EndOfGame;
11511 //      ModeHighlight();
11512
11513       /* [AS] Clear current move marker at the end of a game */
11514       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11515
11516       return FALSE;
11517     }
11518
11519     toX = moveList[currentMove][2] - AAA;
11520     toY = moveList[currentMove][3] - ONE;
11521
11522     if (moveList[currentMove][1] == '@') {
11523         if (appData.highlightLastMove) {
11524             SetHighlights(-1, -1, toX, toY);
11525         }
11526     } else {
11527         fromX = moveList[currentMove][0] - AAA;
11528         fromY = moveList[currentMove][1] - ONE;
11529
11530         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11531
11532         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11533
11534         if (appData.highlightLastMove) {
11535             SetHighlights(fromX, fromY, toX, toY);
11536         }
11537     }
11538     DisplayMove(currentMove);
11539     SendMoveToProgram(currentMove++, &first);
11540     DisplayBothClocks();
11541     DrawPosition(FALSE, boards[currentMove]);
11542     // [HGM] PV info: always display, routine tests if empty
11543     DisplayComment(currentMove - 1, commentList[currentMove]);
11544     return TRUE;
11545 }
11546
11547
11548 int
11549 LoadGameOneMove (ChessMove readAhead)
11550 {
11551     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11552     char promoChar = NULLCHAR;
11553     ChessMove moveType;
11554     char move[MSG_SIZ];
11555     char *p, *q;
11556
11557     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11558         gameMode != AnalyzeMode && gameMode != Training) {
11559         gameFileFP = NULL;
11560         return FALSE;
11561     }
11562
11563     yyboardindex = forwardMostMove;
11564     if (readAhead != EndOfFile) {
11565       moveType = readAhead;
11566     } else {
11567       if (gameFileFP == NULL)
11568           return FALSE;
11569       moveType = (ChessMove) Myylex();
11570     }
11571
11572     done = FALSE;
11573     switch (moveType) {
11574       case Comment:
11575         if (appData.debugMode)
11576           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11577         p = yy_text;
11578
11579         /* append the comment but don't display it */
11580         AppendComment(currentMove, p, FALSE);
11581         return TRUE;
11582
11583       case WhiteCapturesEnPassant:
11584       case BlackCapturesEnPassant:
11585       case WhitePromotion:
11586       case BlackPromotion:
11587       case WhiteNonPromotion:
11588       case BlackNonPromotion:
11589       case NormalMove:
11590       case FirstLeg:
11591       case WhiteKingSideCastle:
11592       case WhiteQueenSideCastle:
11593       case BlackKingSideCastle:
11594       case BlackQueenSideCastle:
11595       case WhiteKingSideCastleWild:
11596       case WhiteQueenSideCastleWild:
11597       case BlackKingSideCastleWild:
11598       case BlackQueenSideCastleWild:
11599       /* PUSH Fabien */
11600       case WhiteHSideCastleFR:
11601       case WhiteASideCastleFR:
11602       case BlackHSideCastleFR:
11603       case BlackASideCastleFR:
11604       /* POP Fabien */
11605         if (appData.debugMode)
11606           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11607         fromX = currentMoveString[0] - AAA;
11608         fromY = currentMoveString[1] - ONE;
11609         toX = currentMoveString[2] - AAA;
11610         toY = currentMoveString[3] - ONE;
11611         promoChar = currentMoveString[4];
11612         if(promoChar == ';') promoChar = NULLCHAR;
11613         break;
11614
11615       case WhiteDrop:
11616       case BlackDrop:
11617         if (appData.debugMode)
11618           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11619         fromX = moveType == WhiteDrop ?
11620           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11621         (int) CharToPiece(ToLower(currentMoveString[0]));
11622         fromY = DROP_RANK;
11623         toX = currentMoveString[2] - AAA;
11624         toY = currentMoveString[3] - ONE;
11625         break;
11626
11627       case WhiteWins:
11628       case BlackWins:
11629       case GameIsDrawn:
11630       case GameUnfinished:
11631         if (appData.debugMode)
11632           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11633         p = strchr(yy_text, '{');
11634         if (p == NULL) p = strchr(yy_text, '(');
11635         if (p == NULL) {
11636             p = yy_text;
11637             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11638         } else {
11639             q = strchr(p, *p == '{' ? '}' : ')');
11640             if (q != NULL) *q = NULLCHAR;
11641             p++;
11642         }
11643         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11644         GameEnds(moveType, p, GE_FILE);
11645         done = TRUE;
11646         if (cmailMsgLoaded) {
11647             ClearHighlights();
11648             flipView = WhiteOnMove(currentMove);
11649             if (moveType == GameUnfinished) flipView = !flipView;
11650             if (appData.debugMode)
11651               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11652         }
11653         break;
11654
11655       case EndOfFile:
11656         if (appData.debugMode)
11657           fprintf(debugFP, "Parser hit end of file\n");
11658         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11659           case MT_NONE:
11660           case MT_CHECK:
11661             break;
11662           case MT_CHECKMATE:
11663           case MT_STAINMATE:
11664             if (WhiteOnMove(currentMove)) {
11665                 GameEnds(BlackWins, "Black mates", GE_FILE);
11666             } else {
11667                 GameEnds(WhiteWins, "White mates", GE_FILE);
11668             }
11669             break;
11670           case MT_STALEMATE:
11671             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11672             break;
11673         }
11674         done = TRUE;
11675         break;
11676
11677       case MoveNumberOne:
11678         if (lastLoadGameStart == GNUChessGame) {
11679             /* GNUChessGames have numbers, but they aren't move numbers */
11680             if (appData.debugMode)
11681               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11682                       yy_text, (int) moveType);
11683             return LoadGameOneMove(EndOfFile); /* tail recursion */
11684         }
11685         /* else fall thru */
11686
11687       case XBoardGame:
11688       case GNUChessGame:
11689       case PGNTag:
11690         /* Reached start of next game in file */
11691         if (appData.debugMode)
11692           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11693         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11694           case MT_NONE:
11695           case MT_CHECK:
11696             break;
11697           case MT_CHECKMATE:
11698           case MT_STAINMATE:
11699             if (WhiteOnMove(currentMove)) {
11700                 GameEnds(BlackWins, "Black mates", GE_FILE);
11701             } else {
11702                 GameEnds(WhiteWins, "White mates", GE_FILE);
11703             }
11704             break;
11705           case MT_STALEMATE:
11706             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11707             break;
11708         }
11709         done = TRUE;
11710         break;
11711
11712       case PositionDiagram:     /* should not happen; ignore */
11713       case ElapsedTime:         /* ignore */
11714       case NAG:                 /* ignore */
11715         if (appData.debugMode)
11716           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11717                   yy_text, (int) moveType);
11718         return LoadGameOneMove(EndOfFile); /* tail recursion */
11719
11720       case IllegalMove:
11721         if (appData.testLegality) {
11722             if (appData.debugMode)
11723               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11724             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11725                     (forwardMostMove / 2) + 1,
11726                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11727             DisplayError(move, 0);
11728             done = TRUE;
11729         } else {
11730             if (appData.debugMode)
11731               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11732                       yy_text, currentMoveString);
11733             fromX = currentMoveString[0] - AAA;
11734             fromY = currentMoveString[1] - ONE;
11735             toX = currentMoveString[2] - AAA;
11736             toY = currentMoveString[3] - ONE;
11737             promoChar = currentMoveString[4];
11738         }
11739         break;
11740
11741       case AmbiguousMove:
11742         if (appData.debugMode)
11743           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11744         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11745                 (forwardMostMove / 2) + 1,
11746                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11747         DisplayError(move, 0);
11748         done = TRUE;
11749         break;
11750
11751       default:
11752       case ImpossibleMove:
11753         if (appData.debugMode)
11754           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11755         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11756                 (forwardMostMove / 2) + 1,
11757                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11758         DisplayError(move, 0);
11759         done = TRUE;
11760         break;
11761     }
11762
11763     if (done) {
11764         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11765             DrawPosition(FALSE, boards[currentMove]);
11766             DisplayBothClocks();
11767             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11768               DisplayComment(currentMove - 1, commentList[currentMove]);
11769         }
11770         (void) StopLoadGameTimer();
11771         gameFileFP = NULL;
11772         cmailOldMove = forwardMostMove;
11773         return FALSE;
11774     } else {
11775         /* currentMoveString is set as a side-effect of yylex */
11776
11777         thinkOutput[0] = NULLCHAR;
11778         MakeMove(fromX, fromY, toX, toY, promoChar);
11779         killX = killY = -1; // [HGM] lion: used up
11780         currentMove = forwardMostMove;
11781         return TRUE;
11782     }
11783 }
11784
11785 /* Load the nth game from the given file */
11786 int
11787 LoadGameFromFile (char *filename, int n, char *title, int useList)
11788 {
11789     FILE *f;
11790     char buf[MSG_SIZ];
11791
11792     if (strcmp(filename, "-") == 0) {
11793         f = stdin;
11794         title = "stdin";
11795     } else {
11796         f = fopen(filename, "rb");
11797         if (f == NULL) {
11798           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11799             DisplayError(buf, errno);
11800             return FALSE;
11801         }
11802     }
11803     if (fseek(f, 0, 0) == -1) {
11804         /* f is not seekable; probably a pipe */
11805         useList = FALSE;
11806     }
11807     if (useList && n == 0) {
11808         int error = GameListBuild(f);
11809         if (error) {
11810             DisplayError(_("Cannot build game list"), error);
11811         } else if (!ListEmpty(&gameList) &&
11812                    ((ListGame *) gameList.tailPred)->number > 1) {
11813             GameListPopUp(f, title);
11814             return TRUE;
11815         }
11816         GameListDestroy();
11817         n = 1;
11818     }
11819     if (n == 0) n = 1;
11820     return LoadGame(f, n, title, FALSE);
11821 }
11822
11823
11824 void
11825 MakeRegisteredMove ()
11826 {
11827     int fromX, fromY, toX, toY;
11828     char promoChar;
11829     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11830         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11831           case CMAIL_MOVE:
11832           case CMAIL_DRAW:
11833             if (appData.debugMode)
11834               fprintf(debugFP, "Restoring %s for game %d\n",
11835                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11836
11837             thinkOutput[0] = NULLCHAR;
11838             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11839             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11840             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11841             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11842             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11843             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11844             MakeMove(fromX, fromY, toX, toY, promoChar);
11845             ShowMove(fromX, fromY, toX, toY);
11846
11847             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11848               case MT_NONE:
11849               case MT_CHECK:
11850                 break;
11851
11852               case MT_CHECKMATE:
11853               case MT_STAINMATE:
11854                 if (WhiteOnMove(currentMove)) {
11855                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11856                 } else {
11857                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11858                 }
11859                 break;
11860
11861               case MT_STALEMATE:
11862                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11863                 break;
11864             }
11865
11866             break;
11867
11868           case CMAIL_RESIGN:
11869             if (WhiteOnMove(currentMove)) {
11870                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11871             } else {
11872                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11873             }
11874             break;
11875
11876           case CMAIL_ACCEPT:
11877             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11878             break;
11879
11880           default:
11881             break;
11882         }
11883     }
11884
11885     return;
11886 }
11887
11888 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11889 int
11890 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11891 {
11892     int retVal;
11893
11894     if (gameNumber > nCmailGames) {
11895         DisplayError(_("No more games in this message"), 0);
11896         return FALSE;
11897     }
11898     if (f == lastLoadGameFP) {
11899         int offset = gameNumber - lastLoadGameNumber;
11900         if (offset == 0) {
11901             cmailMsg[0] = NULLCHAR;
11902             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11903                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11904                 nCmailMovesRegistered--;
11905             }
11906             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11907             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11908                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11909             }
11910         } else {
11911             if (! RegisterMove()) return FALSE;
11912         }
11913     }
11914
11915     retVal = LoadGame(f, gameNumber, title, useList);
11916
11917     /* Make move registered during previous look at this game, if any */
11918     MakeRegisteredMove();
11919
11920     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11921         commentList[currentMove]
11922           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11923         DisplayComment(currentMove - 1, commentList[currentMove]);
11924     }
11925
11926     return retVal;
11927 }
11928
11929 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11930 int
11931 ReloadGame (int offset)
11932 {
11933     int gameNumber = lastLoadGameNumber + offset;
11934     if (lastLoadGameFP == NULL) {
11935         DisplayError(_("No game has been loaded yet"), 0);
11936         return FALSE;
11937     }
11938     if (gameNumber <= 0) {
11939         DisplayError(_("Can't back up any further"), 0);
11940         return FALSE;
11941     }
11942     if (cmailMsgLoaded) {
11943         return CmailLoadGame(lastLoadGameFP, gameNumber,
11944                              lastLoadGameTitle, lastLoadGameUseList);
11945     } else {
11946         return LoadGame(lastLoadGameFP, gameNumber,
11947                         lastLoadGameTitle, lastLoadGameUseList);
11948     }
11949 }
11950
11951 int keys[EmptySquare+1];
11952
11953 int
11954 PositionMatches (Board b1, Board b2)
11955 {
11956     int r, f, sum=0;
11957     switch(appData.searchMode) {
11958         case 1: return CompareWithRights(b1, b2);
11959         case 2:
11960             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11961                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11962             }
11963             return TRUE;
11964         case 3:
11965             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11966               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11967                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11968             }
11969             return sum==0;
11970         case 4:
11971             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11972                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11973             }
11974             return sum==0;
11975     }
11976     return TRUE;
11977 }
11978
11979 #define Q_PROMO  4
11980 #define Q_EP     3
11981 #define Q_BCASTL 2
11982 #define Q_WCASTL 1
11983
11984 int pieceList[256], quickBoard[256];
11985 ChessSquare pieceType[256] = { EmptySquare };
11986 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11987 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11988 int soughtTotal, turn;
11989 Boolean epOK, flipSearch;
11990
11991 typedef struct {
11992     unsigned char piece, to;
11993 } Move;
11994
11995 #define DSIZE (250000)
11996
11997 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11998 Move *moveDatabase = initialSpace;
11999 unsigned int movePtr, dataSize = DSIZE;
12000
12001 int
12002 MakePieceList (Board board, int *counts)
12003 {
12004     int r, f, n=Q_PROMO, total=0;
12005     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12006     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12007         int sq = f + (r<<4);
12008         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12009             quickBoard[sq] = ++n;
12010             pieceList[n] = sq;
12011             pieceType[n] = board[r][f];
12012             counts[board[r][f]]++;
12013             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12014             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12015             total++;
12016         }
12017     }
12018     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12019     return total;
12020 }
12021
12022 void
12023 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12024 {
12025     int sq = fromX + (fromY<<4);
12026     int piece = quickBoard[sq];
12027     quickBoard[sq] = 0;
12028     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12029     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12030         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12031         moveDatabase[movePtr++].piece = Q_WCASTL;
12032         quickBoard[sq] = piece;
12033         piece = quickBoard[from]; quickBoard[from] = 0;
12034         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12035     } else
12036     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12037         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12038         moveDatabase[movePtr++].piece = Q_BCASTL;
12039         quickBoard[sq] = piece;
12040         piece = quickBoard[from]; quickBoard[from] = 0;
12041         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12042     } else
12043     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12044         quickBoard[(fromY<<4)+toX] = 0;
12045         moveDatabase[movePtr].piece = Q_EP;
12046         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12047         moveDatabase[movePtr].to = sq;
12048     } else
12049     if(promoPiece != pieceType[piece]) {
12050         moveDatabase[movePtr++].piece = Q_PROMO;
12051         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12052     }
12053     moveDatabase[movePtr].piece = piece;
12054     quickBoard[sq] = piece;
12055     movePtr++;
12056 }
12057
12058 int
12059 PackGame (Board board)
12060 {
12061     Move *newSpace = NULL;
12062     moveDatabase[movePtr].piece = 0; // terminate previous game
12063     if(movePtr > dataSize) {
12064         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12065         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12066         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12067         if(newSpace) {
12068             int i;
12069             Move *p = moveDatabase, *q = newSpace;
12070             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12071             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12072             moveDatabase = newSpace;
12073         } else { // calloc failed, we must be out of memory. Too bad...
12074             dataSize = 0; // prevent calloc events for all subsequent games
12075             return 0;     // and signal this one isn't cached
12076         }
12077     }
12078     movePtr++;
12079     MakePieceList(board, counts);
12080     return movePtr;
12081 }
12082
12083 int
12084 QuickCompare (Board board, int *minCounts, int *maxCounts)
12085 {   // compare according to search mode
12086     int r, f;
12087     switch(appData.searchMode)
12088     {
12089       case 1: // exact position match
12090         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12091         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12092             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12093         }
12094         break;
12095       case 2: // can have extra material on empty squares
12096         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12097             if(board[r][f] == EmptySquare) continue;
12098             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12099         }
12100         break;
12101       case 3: // material with exact Pawn structure
12102         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12103             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12104             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12105         } // fall through to material comparison
12106       case 4: // exact material
12107         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12108         break;
12109       case 6: // material range with given imbalance
12110         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12111         // fall through to range comparison
12112       case 5: // material range
12113         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12114     }
12115     return TRUE;
12116 }
12117
12118 int
12119 QuickScan (Board board, Move *move)
12120 {   // reconstruct game,and compare all positions in it
12121     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12122     do {
12123         int piece = move->piece;
12124         int to = move->to, from = pieceList[piece];
12125         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12126           if(!piece) return -1;
12127           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12128             piece = (++move)->piece;
12129             from = pieceList[piece];
12130             counts[pieceType[piece]]--;
12131             pieceType[piece] = (ChessSquare) move->to;
12132             counts[move->to]++;
12133           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12134             counts[pieceType[quickBoard[to]]]--;
12135             quickBoard[to] = 0; total--;
12136             move++;
12137             continue;
12138           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12139             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12140             from  = pieceList[piece]; // so this must be King
12141             quickBoard[from] = 0;
12142             pieceList[piece] = to;
12143             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12144             quickBoard[from] = 0; // rook
12145             quickBoard[to] = piece;
12146             to = move->to; piece = move->piece;
12147             goto aftercastle;
12148           }
12149         }
12150         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12151         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12152         quickBoard[from] = 0;
12153       aftercastle:
12154         quickBoard[to] = piece;
12155         pieceList[piece] = to;
12156         cnt++; turn ^= 3;
12157         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12158            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12159            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12160                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12161           ) {
12162             static int lastCounts[EmptySquare+1];
12163             int i;
12164             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12165             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12166         } else stretch = 0;
12167         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12168         move++;
12169     } while(1);
12170 }
12171
12172 void
12173 InitSearch ()
12174 {
12175     int r, f;
12176     flipSearch = FALSE;
12177     CopyBoard(soughtBoard, boards[currentMove]);
12178     soughtTotal = MakePieceList(soughtBoard, maxSought);
12179     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12180     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12181     CopyBoard(reverseBoard, boards[currentMove]);
12182     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12183         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12184         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12185         reverseBoard[r][f] = piece;
12186     }
12187     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12188     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12189     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12190                  || (boards[currentMove][CASTLING][2] == NoRights ||
12191                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12192                  && (boards[currentMove][CASTLING][5] == NoRights ||
12193                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12194       ) {
12195         flipSearch = TRUE;
12196         CopyBoard(flipBoard, soughtBoard);
12197         CopyBoard(rotateBoard, reverseBoard);
12198         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12199             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12200             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12201         }
12202     }
12203     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12204     if(appData.searchMode >= 5) {
12205         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12206         MakePieceList(soughtBoard, minSought);
12207         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12208     }
12209     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12210         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12211 }
12212
12213 GameInfo dummyInfo;
12214 static int creatingBook;
12215
12216 int
12217 GameContainsPosition (FILE *f, ListGame *lg)
12218 {
12219     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12220     int fromX, fromY, toX, toY;
12221     char promoChar;
12222     static int initDone=FALSE;
12223
12224     // weed out games based on numerical tag comparison
12225     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12226     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12227     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12228     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12229     if(!initDone) {
12230         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12231         initDone = TRUE;
12232     }
12233     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12234     else CopyBoard(boards[scratch], initialPosition); // default start position
12235     if(lg->moves) {
12236         turn = btm + 1;
12237         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12238         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12239     }
12240     if(btm) plyNr++;
12241     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12242     fseek(f, lg->offset, 0);
12243     yynewfile(f);
12244     while(1) {
12245         yyboardindex = scratch;
12246         quickFlag = plyNr+1;
12247         next = Myylex();
12248         quickFlag = 0;
12249         switch(next) {
12250             case PGNTag:
12251                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12252             default:
12253                 continue;
12254
12255             case XBoardGame:
12256             case GNUChessGame:
12257                 if(plyNr) return -1; // after we have seen moves, this is for new game
12258               continue;
12259
12260             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12261             case ImpossibleMove:
12262             case WhiteWins: // game ends here with these four
12263             case BlackWins:
12264             case GameIsDrawn:
12265             case GameUnfinished:
12266                 return -1;
12267
12268             case IllegalMove:
12269                 if(appData.testLegality) return -1;
12270             case WhiteCapturesEnPassant:
12271             case BlackCapturesEnPassant:
12272             case WhitePromotion:
12273             case BlackPromotion:
12274             case WhiteNonPromotion:
12275             case BlackNonPromotion:
12276             case NormalMove:
12277             case FirstLeg:
12278             case WhiteKingSideCastle:
12279             case WhiteQueenSideCastle:
12280             case BlackKingSideCastle:
12281             case BlackQueenSideCastle:
12282             case WhiteKingSideCastleWild:
12283             case WhiteQueenSideCastleWild:
12284             case BlackKingSideCastleWild:
12285             case BlackQueenSideCastleWild:
12286             case WhiteHSideCastleFR:
12287             case WhiteASideCastleFR:
12288             case BlackHSideCastleFR:
12289             case BlackASideCastleFR:
12290                 fromX = currentMoveString[0] - AAA;
12291                 fromY = currentMoveString[1] - ONE;
12292                 toX = currentMoveString[2] - AAA;
12293                 toY = currentMoveString[3] - ONE;
12294                 promoChar = currentMoveString[4];
12295                 break;
12296             case WhiteDrop:
12297             case BlackDrop:
12298                 fromX = next == WhiteDrop ?
12299                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12300                   (int) CharToPiece(ToLower(currentMoveString[0]));
12301                 fromY = DROP_RANK;
12302                 toX = currentMoveString[2] - AAA;
12303                 toY = currentMoveString[3] - ONE;
12304                 promoChar = 0;
12305                 break;
12306         }
12307         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12308         plyNr++;
12309         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12310         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12311         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12312         if(appData.findMirror) {
12313             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12314             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12315         }
12316     }
12317 }
12318
12319 /* Load the nth game from open file f */
12320 int
12321 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12322 {
12323     ChessMove cm;
12324     char buf[MSG_SIZ];
12325     int gn = gameNumber;
12326     ListGame *lg = NULL;
12327     int numPGNTags = 0;
12328     int err, pos = -1;
12329     GameMode oldGameMode;
12330     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12331
12332     if (appData.debugMode)
12333         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12334
12335     if (gameMode == Training )
12336         SetTrainingModeOff();
12337
12338     oldGameMode = gameMode;
12339     if (gameMode != BeginningOfGame) {
12340       Reset(FALSE, TRUE);
12341     }
12342     killX = killY = -1; // [HGM] lion: in case we did not Reset
12343
12344     gameFileFP = f;
12345     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12346         fclose(lastLoadGameFP);
12347     }
12348
12349     if (useList) {
12350         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12351
12352         if (lg) {
12353             fseek(f, lg->offset, 0);
12354             GameListHighlight(gameNumber);
12355             pos = lg->position;
12356             gn = 1;
12357         }
12358         else {
12359             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12360               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12361             else
12362             DisplayError(_("Game number out of range"), 0);
12363             return FALSE;
12364         }
12365     } else {
12366         GameListDestroy();
12367         if (fseek(f, 0, 0) == -1) {
12368             if (f == lastLoadGameFP ?
12369                 gameNumber == lastLoadGameNumber + 1 :
12370                 gameNumber == 1) {
12371                 gn = 1;
12372             } else {
12373                 DisplayError(_("Can't seek on game file"), 0);
12374                 return FALSE;
12375             }
12376         }
12377     }
12378     lastLoadGameFP = f;
12379     lastLoadGameNumber = gameNumber;
12380     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12381     lastLoadGameUseList = useList;
12382
12383     yynewfile(f);
12384
12385     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12386       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12387                 lg->gameInfo.black);
12388             DisplayTitle(buf);
12389     } else if (*title != NULLCHAR) {
12390         if (gameNumber > 1) {
12391           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12392             DisplayTitle(buf);
12393         } else {
12394             DisplayTitle(title);
12395         }
12396     }
12397
12398     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12399         gameMode = PlayFromGameFile;
12400         ModeHighlight();
12401     }
12402
12403     currentMove = forwardMostMove = backwardMostMove = 0;
12404     CopyBoard(boards[0], initialPosition);
12405     StopClocks();
12406
12407     /*
12408      * Skip the first gn-1 games in the file.
12409      * Also skip over anything that precedes an identifiable
12410      * start of game marker, to avoid being confused by
12411      * garbage at the start of the file.  Currently
12412      * recognized start of game markers are the move number "1",
12413      * the pattern "gnuchess .* game", the pattern
12414      * "^[#;%] [^ ]* game file", and a PGN tag block.
12415      * A game that starts with one of the latter two patterns
12416      * will also have a move number 1, possibly
12417      * following a position diagram.
12418      * 5-4-02: Let's try being more lenient and allowing a game to
12419      * start with an unnumbered move.  Does that break anything?
12420      */
12421     cm = lastLoadGameStart = EndOfFile;
12422     while (gn > 0) {
12423         yyboardindex = forwardMostMove;
12424         cm = (ChessMove) Myylex();
12425         switch (cm) {
12426           case EndOfFile:
12427             if (cmailMsgLoaded) {
12428                 nCmailGames = CMAIL_MAX_GAMES - gn;
12429             } else {
12430                 Reset(TRUE, TRUE);
12431                 DisplayError(_("Game not found in file"), 0);
12432             }
12433             return FALSE;
12434
12435           case GNUChessGame:
12436           case XBoardGame:
12437             gn--;
12438             lastLoadGameStart = cm;
12439             break;
12440
12441           case MoveNumberOne:
12442             switch (lastLoadGameStart) {
12443               case GNUChessGame:
12444               case XBoardGame:
12445               case PGNTag:
12446                 break;
12447               case MoveNumberOne:
12448               case EndOfFile:
12449                 gn--;           /* count this game */
12450                 lastLoadGameStart = cm;
12451                 break;
12452               default:
12453                 /* impossible */
12454                 break;
12455             }
12456             break;
12457
12458           case PGNTag:
12459             switch (lastLoadGameStart) {
12460               case GNUChessGame:
12461               case PGNTag:
12462               case MoveNumberOne:
12463               case EndOfFile:
12464                 gn--;           /* count this game */
12465                 lastLoadGameStart = cm;
12466                 break;
12467               case XBoardGame:
12468                 lastLoadGameStart = cm; /* game counted already */
12469                 break;
12470               default:
12471                 /* impossible */
12472                 break;
12473             }
12474             if (gn > 0) {
12475                 do {
12476                     yyboardindex = forwardMostMove;
12477                     cm = (ChessMove) Myylex();
12478                 } while (cm == PGNTag || cm == Comment);
12479             }
12480             break;
12481
12482           case WhiteWins:
12483           case BlackWins:
12484           case GameIsDrawn:
12485             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12486                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12487                     != CMAIL_OLD_RESULT) {
12488                     nCmailResults ++ ;
12489                     cmailResult[  CMAIL_MAX_GAMES
12490                                 - gn - 1] = CMAIL_OLD_RESULT;
12491                 }
12492             }
12493             break;
12494
12495           case NormalMove:
12496           case FirstLeg:
12497             /* Only a NormalMove can be at the start of a game
12498              * without a position diagram. */
12499             if (lastLoadGameStart == EndOfFile ) {
12500               gn--;
12501               lastLoadGameStart = MoveNumberOne;
12502             }
12503             break;
12504
12505           default:
12506             break;
12507         }
12508     }
12509
12510     if (appData.debugMode)
12511       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12512
12513     if (cm == XBoardGame) {
12514         /* Skip any header junk before position diagram and/or move 1 */
12515         for (;;) {
12516             yyboardindex = forwardMostMove;
12517             cm = (ChessMove) Myylex();
12518
12519             if (cm == EndOfFile ||
12520                 cm == GNUChessGame || cm == XBoardGame) {
12521                 /* Empty game; pretend end-of-file and handle later */
12522                 cm = EndOfFile;
12523                 break;
12524             }
12525
12526             if (cm == MoveNumberOne || cm == PositionDiagram ||
12527                 cm == PGNTag || cm == Comment)
12528               break;
12529         }
12530     } else if (cm == GNUChessGame) {
12531         if (gameInfo.event != NULL) {
12532             free(gameInfo.event);
12533         }
12534         gameInfo.event = StrSave(yy_text);
12535     }
12536
12537     startedFromSetupPosition = FALSE;
12538     while (cm == PGNTag) {
12539         if (appData.debugMode)
12540           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12541         err = ParsePGNTag(yy_text, &gameInfo);
12542         if (!err) numPGNTags++;
12543
12544         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12545         if(gameInfo.variant != oldVariant) {
12546             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12547             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12548             InitPosition(TRUE);
12549             oldVariant = gameInfo.variant;
12550             if (appData.debugMode)
12551               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12552         }
12553
12554
12555         if (gameInfo.fen != NULL) {
12556           Board initial_position;
12557           startedFromSetupPosition = TRUE;
12558           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12559             Reset(TRUE, TRUE);
12560             DisplayError(_("Bad FEN position in file"), 0);
12561             return FALSE;
12562           }
12563           CopyBoard(boards[0], initial_position);
12564           if (blackPlaysFirst) {
12565             currentMove = forwardMostMove = backwardMostMove = 1;
12566             CopyBoard(boards[1], initial_position);
12567             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12568             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12569             timeRemaining[0][1] = whiteTimeRemaining;
12570             timeRemaining[1][1] = blackTimeRemaining;
12571             if (commentList[0] != NULL) {
12572               commentList[1] = commentList[0];
12573               commentList[0] = NULL;
12574             }
12575           } else {
12576             currentMove = forwardMostMove = backwardMostMove = 0;
12577           }
12578           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12579           {   int i;
12580               initialRulePlies = FENrulePlies;
12581               for( i=0; i< nrCastlingRights; i++ )
12582                   initialRights[i] = initial_position[CASTLING][i];
12583           }
12584           yyboardindex = forwardMostMove;
12585           free(gameInfo.fen);
12586           gameInfo.fen = NULL;
12587         }
12588
12589         yyboardindex = forwardMostMove;
12590         cm = (ChessMove) Myylex();
12591
12592         /* Handle comments interspersed among the tags */
12593         while (cm == Comment) {
12594             char *p;
12595             if (appData.debugMode)
12596               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12597             p = yy_text;
12598             AppendComment(currentMove, p, FALSE);
12599             yyboardindex = forwardMostMove;
12600             cm = (ChessMove) Myylex();
12601         }
12602     }
12603
12604     /* don't rely on existence of Event tag since if game was
12605      * pasted from clipboard the Event tag may not exist
12606      */
12607     if (numPGNTags > 0){
12608         char *tags;
12609         if (gameInfo.variant == VariantNormal) {
12610           VariantClass v = StringToVariant(gameInfo.event);
12611           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12612           if(v < VariantShogi) gameInfo.variant = v;
12613         }
12614         if (!matchMode) {
12615           if( appData.autoDisplayTags ) {
12616             tags = PGNTags(&gameInfo);
12617             TagsPopUp(tags, CmailMsg());
12618             free(tags);
12619           }
12620         }
12621     } else {
12622         /* Make something up, but don't display it now */
12623         SetGameInfo();
12624         TagsPopDown();
12625     }
12626
12627     if (cm == PositionDiagram) {
12628         int i, j;
12629         char *p;
12630         Board initial_position;
12631
12632         if (appData.debugMode)
12633           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12634
12635         if (!startedFromSetupPosition) {
12636             p = yy_text;
12637             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12638               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12639                 switch (*p) {
12640                   case '{':
12641                   case '[':
12642                   case '-':
12643                   case ' ':
12644                   case '\t':
12645                   case '\n':
12646                   case '\r':
12647                     break;
12648                   default:
12649                     initial_position[i][j++] = CharToPiece(*p);
12650                     break;
12651                 }
12652             while (*p == ' ' || *p == '\t' ||
12653                    *p == '\n' || *p == '\r') p++;
12654
12655             if (strncmp(p, "black", strlen("black"))==0)
12656               blackPlaysFirst = TRUE;
12657             else
12658               blackPlaysFirst = FALSE;
12659             startedFromSetupPosition = TRUE;
12660
12661             CopyBoard(boards[0], initial_position);
12662             if (blackPlaysFirst) {
12663                 currentMove = forwardMostMove = backwardMostMove = 1;
12664                 CopyBoard(boards[1], initial_position);
12665                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12666                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12667                 timeRemaining[0][1] = whiteTimeRemaining;
12668                 timeRemaining[1][1] = blackTimeRemaining;
12669                 if (commentList[0] != NULL) {
12670                     commentList[1] = commentList[0];
12671                     commentList[0] = NULL;
12672                 }
12673             } else {
12674                 currentMove = forwardMostMove = backwardMostMove = 0;
12675             }
12676         }
12677         yyboardindex = forwardMostMove;
12678         cm = (ChessMove) Myylex();
12679     }
12680
12681   if(!creatingBook) {
12682     if (first.pr == NoProc) {
12683         StartChessProgram(&first);
12684     }
12685     InitChessProgram(&first, FALSE);
12686     SendToProgram("force\n", &first);
12687     if (startedFromSetupPosition) {
12688         SendBoard(&first, forwardMostMove);
12689     if (appData.debugMode) {
12690         fprintf(debugFP, "Load Game\n");
12691     }
12692         DisplayBothClocks();
12693     }
12694   }
12695
12696     /* [HGM] server: flag to write setup moves in broadcast file as one */
12697     loadFlag = appData.suppressLoadMoves;
12698
12699     while (cm == Comment) {
12700         char *p;
12701         if (appData.debugMode)
12702           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12703         p = yy_text;
12704         AppendComment(currentMove, p, FALSE);
12705         yyboardindex = forwardMostMove;
12706         cm = (ChessMove) Myylex();
12707     }
12708
12709     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12710         cm == WhiteWins || cm == BlackWins ||
12711         cm == GameIsDrawn || cm == GameUnfinished) {
12712         DisplayMessage("", _("No moves in game"));
12713         if (cmailMsgLoaded) {
12714             if (appData.debugMode)
12715               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12716             ClearHighlights();
12717             flipView = FALSE;
12718         }
12719         DrawPosition(FALSE, boards[currentMove]);
12720         DisplayBothClocks();
12721         gameMode = EditGame;
12722         ModeHighlight();
12723         gameFileFP = NULL;
12724         cmailOldMove = 0;
12725         return TRUE;
12726     }
12727
12728     // [HGM] PV info: routine tests if comment empty
12729     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12730         DisplayComment(currentMove - 1, commentList[currentMove]);
12731     }
12732     if (!matchMode && appData.timeDelay != 0)
12733       DrawPosition(FALSE, boards[currentMove]);
12734
12735     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12736       programStats.ok_to_send = 1;
12737     }
12738
12739     /* if the first token after the PGN tags is a move
12740      * and not move number 1, retrieve it from the parser
12741      */
12742     if (cm != MoveNumberOne)
12743         LoadGameOneMove(cm);
12744
12745     /* load the remaining moves from the file */
12746     while (LoadGameOneMove(EndOfFile)) {
12747       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12748       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12749     }
12750
12751     /* rewind to the start of the game */
12752     currentMove = backwardMostMove;
12753
12754     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12755
12756     if (oldGameMode == AnalyzeFile) {
12757       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12758       AnalyzeFileEvent();
12759     } else
12760     if (oldGameMode == AnalyzeMode) {
12761       AnalyzeFileEvent();
12762     }
12763
12764     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12765         long int w, b; // [HGM] adjourn: restore saved clock times
12766         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12767         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12768             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12769             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12770         }
12771     }
12772
12773     if(creatingBook) return TRUE;
12774     if (!matchMode && pos > 0) {
12775         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12776     } else
12777     if (matchMode || appData.timeDelay == 0) {
12778       ToEndEvent();
12779     } else if (appData.timeDelay > 0) {
12780       AutoPlayGameLoop();
12781     }
12782
12783     if (appData.debugMode)
12784         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12785
12786     loadFlag = 0; /* [HGM] true game starts */
12787     return TRUE;
12788 }
12789
12790 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12791 int
12792 ReloadPosition (int offset)
12793 {
12794     int positionNumber = lastLoadPositionNumber + offset;
12795     if (lastLoadPositionFP == NULL) {
12796         DisplayError(_("No position has been loaded yet"), 0);
12797         return FALSE;
12798     }
12799     if (positionNumber <= 0) {
12800         DisplayError(_("Can't back up any further"), 0);
12801         return FALSE;
12802     }
12803     return LoadPosition(lastLoadPositionFP, positionNumber,
12804                         lastLoadPositionTitle);
12805 }
12806
12807 /* Load the nth position from the given file */
12808 int
12809 LoadPositionFromFile (char *filename, int n, char *title)
12810 {
12811     FILE *f;
12812     char buf[MSG_SIZ];
12813
12814     if (strcmp(filename, "-") == 0) {
12815         return LoadPosition(stdin, n, "stdin");
12816     } else {
12817         f = fopen(filename, "rb");
12818         if (f == NULL) {
12819             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12820             DisplayError(buf, errno);
12821             return FALSE;
12822         } else {
12823             return LoadPosition(f, n, title);
12824         }
12825     }
12826 }
12827
12828 /* Load the nth position from the given open file, and close it */
12829 int
12830 LoadPosition (FILE *f, int positionNumber, char *title)
12831 {
12832     char *p, line[MSG_SIZ];
12833     Board initial_position;
12834     int i, j, fenMode, pn;
12835
12836     if (gameMode == Training )
12837         SetTrainingModeOff();
12838
12839     if (gameMode != BeginningOfGame) {
12840         Reset(FALSE, TRUE);
12841     }
12842     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12843         fclose(lastLoadPositionFP);
12844     }
12845     if (positionNumber == 0) positionNumber = 1;
12846     lastLoadPositionFP = f;
12847     lastLoadPositionNumber = positionNumber;
12848     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12849     if (first.pr == NoProc && !appData.noChessProgram) {
12850       StartChessProgram(&first);
12851       InitChessProgram(&first, FALSE);
12852     }
12853     pn = positionNumber;
12854     if (positionNumber < 0) {
12855         /* Negative position number means to seek to that byte offset */
12856         if (fseek(f, -positionNumber, 0) == -1) {
12857             DisplayError(_("Can't seek on position file"), 0);
12858             return FALSE;
12859         };
12860         pn = 1;
12861     } else {
12862         if (fseek(f, 0, 0) == -1) {
12863             if (f == lastLoadPositionFP ?
12864                 positionNumber == lastLoadPositionNumber + 1 :
12865                 positionNumber == 1) {
12866                 pn = 1;
12867             } else {
12868                 DisplayError(_("Can't seek on position file"), 0);
12869                 return FALSE;
12870             }
12871         }
12872     }
12873     /* See if this file is FEN or old-style xboard */
12874     if (fgets(line, MSG_SIZ, f) == NULL) {
12875         DisplayError(_("Position not found in file"), 0);
12876         return FALSE;
12877     }
12878     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12879     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12880
12881     if (pn >= 2) {
12882         if (fenMode || line[0] == '#') pn--;
12883         while (pn > 0) {
12884             /* skip positions before number pn */
12885             if (fgets(line, MSG_SIZ, f) == NULL) {
12886                 Reset(TRUE, TRUE);
12887                 DisplayError(_("Position not found in file"), 0);
12888                 return FALSE;
12889             }
12890             if (fenMode || line[0] == '#') pn--;
12891         }
12892     }
12893
12894     if (fenMode) {
12895         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12896             DisplayError(_("Bad FEN position in file"), 0);
12897             return FALSE;
12898         }
12899     } else {
12900         (void) fgets(line, MSG_SIZ, f);
12901         (void) fgets(line, MSG_SIZ, f);
12902
12903         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12904             (void) fgets(line, MSG_SIZ, f);
12905             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12906                 if (*p == ' ')
12907                   continue;
12908                 initial_position[i][j++] = CharToPiece(*p);
12909             }
12910         }
12911
12912         blackPlaysFirst = FALSE;
12913         if (!feof(f)) {
12914             (void) fgets(line, MSG_SIZ, f);
12915             if (strncmp(line, "black", strlen("black"))==0)
12916               blackPlaysFirst = TRUE;
12917         }
12918     }
12919     startedFromSetupPosition = TRUE;
12920
12921     CopyBoard(boards[0], initial_position);
12922     if (blackPlaysFirst) {
12923         currentMove = forwardMostMove = backwardMostMove = 1;
12924         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12925         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12926         CopyBoard(boards[1], initial_position);
12927         DisplayMessage("", _("Black to play"));
12928     } else {
12929         currentMove = forwardMostMove = backwardMostMove = 0;
12930         DisplayMessage("", _("White to play"));
12931     }
12932     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12933     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12934         SendToProgram("force\n", &first);
12935         SendBoard(&first, forwardMostMove);
12936     }
12937     if (appData.debugMode) {
12938 int i, j;
12939   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12940   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12941         fprintf(debugFP, "Load Position\n");
12942     }
12943
12944     if (positionNumber > 1) {
12945       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12946         DisplayTitle(line);
12947     } else {
12948         DisplayTitle(title);
12949     }
12950     gameMode = EditGame;
12951     ModeHighlight();
12952     ResetClocks();
12953     timeRemaining[0][1] = whiteTimeRemaining;
12954     timeRemaining[1][1] = blackTimeRemaining;
12955     DrawPosition(FALSE, boards[currentMove]);
12956
12957     return TRUE;
12958 }
12959
12960
12961 void
12962 CopyPlayerNameIntoFileName (char **dest, char *src)
12963 {
12964     while (*src != NULLCHAR && *src != ',') {
12965         if (*src == ' ') {
12966             *(*dest)++ = '_';
12967             src++;
12968         } else {
12969             *(*dest)++ = *src++;
12970         }
12971     }
12972 }
12973
12974 char *
12975 DefaultFileName (char *ext)
12976 {
12977     static char def[MSG_SIZ];
12978     char *p;
12979
12980     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12981         p = def;
12982         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12983         *p++ = '-';
12984         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12985         *p++ = '.';
12986         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12987     } else {
12988         def[0] = NULLCHAR;
12989     }
12990     return def;
12991 }
12992
12993 /* Save the current game to the given file */
12994 int
12995 SaveGameToFile (char *filename, int append)
12996 {
12997     FILE *f;
12998     char buf[MSG_SIZ];
12999     int result, i, t,tot=0;
13000
13001     if (strcmp(filename, "-") == 0) {
13002         return SaveGame(stdout, 0, NULL);
13003     } else {
13004         for(i=0; i<10; i++) { // upto 10 tries
13005              f = fopen(filename, append ? "a" : "w");
13006              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13007              if(f || errno != 13) break;
13008              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13009              tot += t;
13010         }
13011         if (f == NULL) {
13012             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13013             DisplayError(buf, errno);
13014             return FALSE;
13015         } else {
13016             safeStrCpy(buf, lastMsg, MSG_SIZ);
13017             DisplayMessage(_("Waiting for access to save file"), "");
13018             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13019             DisplayMessage(_("Saving game"), "");
13020             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13021             result = SaveGame(f, 0, NULL);
13022             DisplayMessage(buf, "");
13023             return result;
13024         }
13025     }
13026 }
13027
13028 char *
13029 SavePart (char *str)
13030 {
13031     static char buf[MSG_SIZ];
13032     char *p;
13033
13034     p = strchr(str, ' ');
13035     if (p == NULL) return str;
13036     strncpy(buf, str, p - str);
13037     buf[p - str] = NULLCHAR;
13038     return buf;
13039 }
13040
13041 #define PGN_MAX_LINE 75
13042
13043 #define PGN_SIDE_WHITE  0
13044 #define PGN_SIDE_BLACK  1
13045
13046 static int
13047 FindFirstMoveOutOfBook (int side)
13048 {
13049     int result = -1;
13050
13051     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13052         int index = backwardMostMove;
13053         int has_book_hit = 0;
13054
13055         if( (index % 2) != side ) {
13056             index++;
13057         }
13058
13059         while( index < forwardMostMove ) {
13060             /* Check to see if engine is in book */
13061             int depth = pvInfoList[index].depth;
13062             int score = pvInfoList[index].score;
13063             int in_book = 0;
13064
13065             if( depth <= 2 ) {
13066                 in_book = 1;
13067             }
13068             else if( score == 0 && depth == 63 ) {
13069                 in_book = 1; /* Zappa */
13070             }
13071             else if( score == 2 && depth == 99 ) {
13072                 in_book = 1; /* Abrok */
13073             }
13074
13075             has_book_hit += in_book;
13076
13077             if( ! in_book ) {
13078                 result = index;
13079
13080                 break;
13081             }
13082
13083             index += 2;
13084         }
13085     }
13086
13087     return result;
13088 }
13089
13090 void
13091 GetOutOfBookInfo (char * buf)
13092 {
13093     int oob[2];
13094     int i;
13095     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13096
13097     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13098     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13099
13100     *buf = '\0';
13101
13102     if( oob[0] >= 0 || oob[1] >= 0 ) {
13103         for( i=0; i<2; i++ ) {
13104             int idx = oob[i];
13105
13106             if( idx >= 0 ) {
13107                 if( i > 0 && oob[0] >= 0 ) {
13108                     strcat( buf, "   " );
13109                 }
13110
13111                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13112                 sprintf( buf+strlen(buf), "%s%.2f",
13113                     pvInfoList[idx].score >= 0 ? "+" : "",
13114                     pvInfoList[idx].score / 100.0 );
13115             }
13116         }
13117     }
13118 }
13119
13120 /* Save game in PGN style and close the file */
13121 int
13122 SaveGamePGN (FILE *f)
13123 {
13124     int i, offset, linelen, newblock;
13125 //    char *movetext;
13126     char numtext[32];
13127     int movelen, numlen, blank;
13128     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13129
13130     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13131
13132     PrintPGNTags(f, &gameInfo);
13133
13134     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13135
13136     if (backwardMostMove > 0 || startedFromSetupPosition) {
13137         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13138         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13139         fprintf(f, "\n{--------------\n");
13140         PrintPosition(f, backwardMostMove);
13141         fprintf(f, "--------------}\n");
13142         free(fen);
13143     }
13144     else {
13145         /* [AS] Out of book annotation */
13146         if( appData.saveOutOfBookInfo ) {
13147             char buf[64];
13148
13149             GetOutOfBookInfo( buf );
13150
13151             if( buf[0] != '\0' ) {
13152                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13153             }
13154         }
13155
13156         fprintf(f, "\n");
13157     }
13158
13159     i = backwardMostMove;
13160     linelen = 0;
13161     newblock = TRUE;
13162
13163     while (i < forwardMostMove) {
13164         /* Print comments preceding this move */
13165         if (commentList[i] != NULL) {
13166             if (linelen > 0) fprintf(f, "\n");
13167             fprintf(f, "%s", commentList[i]);
13168             linelen = 0;
13169             newblock = TRUE;
13170         }
13171
13172         /* Format move number */
13173         if ((i % 2) == 0)
13174           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13175         else
13176           if (newblock)
13177             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13178           else
13179             numtext[0] = NULLCHAR;
13180
13181         numlen = strlen(numtext);
13182         newblock = FALSE;
13183
13184         /* Print move number */
13185         blank = linelen > 0 && numlen > 0;
13186         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13187             fprintf(f, "\n");
13188             linelen = 0;
13189             blank = 0;
13190         }
13191         if (blank) {
13192             fprintf(f, " ");
13193             linelen++;
13194         }
13195         fprintf(f, "%s", numtext);
13196         linelen += numlen;
13197
13198         /* Get move */
13199         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13200         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13201
13202         /* Print move */
13203         blank = linelen > 0 && movelen > 0;
13204         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13205             fprintf(f, "\n");
13206             linelen = 0;
13207             blank = 0;
13208         }
13209         if (blank) {
13210             fprintf(f, " ");
13211             linelen++;
13212         }
13213         fprintf(f, "%s", move_buffer);
13214         linelen += movelen;
13215
13216         /* [AS] Add PV info if present */
13217         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13218             /* [HGM] add time */
13219             char buf[MSG_SIZ]; int seconds;
13220
13221             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13222
13223             if( seconds <= 0)
13224               buf[0] = 0;
13225             else
13226               if( seconds < 30 )
13227                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13228               else
13229                 {
13230                   seconds = (seconds + 4)/10; // round to full seconds
13231                   if( seconds < 60 )
13232                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13233                   else
13234                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13235                 }
13236
13237             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13238                       pvInfoList[i].score >= 0 ? "+" : "",
13239                       pvInfoList[i].score / 100.0,
13240                       pvInfoList[i].depth,
13241                       buf );
13242
13243             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13244
13245             /* Print score/depth */
13246             blank = linelen > 0 && movelen > 0;
13247             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13248                 fprintf(f, "\n");
13249                 linelen = 0;
13250                 blank = 0;
13251             }
13252             if (blank) {
13253                 fprintf(f, " ");
13254                 linelen++;
13255             }
13256             fprintf(f, "%s", move_buffer);
13257             linelen += movelen;
13258         }
13259
13260         i++;
13261     }
13262
13263     /* Start a new line */
13264     if (linelen > 0) fprintf(f, "\n");
13265
13266     /* Print comments after last move */
13267     if (commentList[i] != NULL) {
13268         fprintf(f, "%s\n", commentList[i]);
13269     }
13270
13271     /* Print result */
13272     if (gameInfo.resultDetails != NULL &&
13273         gameInfo.resultDetails[0] != NULLCHAR) {
13274         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13275         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13276            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13277             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13278         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13279     } else {
13280         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13281     }
13282
13283     fclose(f);
13284     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13285     return TRUE;
13286 }
13287
13288 /* Save game in old style and close the file */
13289 int
13290 SaveGameOldStyle (FILE *f)
13291 {
13292     int i, offset;
13293     time_t tm;
13294
13295     tm = time((time_t *) NULL);
13296
13297     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13298     PrintOpponents(f);
13299
13300     if (backwardMostMove > 0 || startedFromSetupPosition) {
13301         fprintf(f, "\n[--------------\n");
13302         PrintPosition(f, backwardMostMove);
13303         fprintf(f, "--------------]\n");
13304     } else {
13305         fprintf(f, "\n");
13306     }
13307
13308     i = backwardMostMove;
13309     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13310
13311     while (i < forwardMostMove) {
13312         if (commentList[i] != NULL) {
13313             fprintf(f, "[%s]\n", commentList[i]);
13314         }
13315
13316         if ((i % 2) == 1) {
13317             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13318             i++;
13319         } else {
13320             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13321             i++;
13322             if (commentList[i] != NULL) {
13323                 fprintf(f, "\n");
13324                 continue;
13325             }
13326             if (i >= forwardMostMove) {
13327                 fprintf(f, "\n");
13328                 break;
13329             }
13330             fprintf(f, "%s\n", parseList[i]);
13331             i++;
13332         }
13333     }
13334
13335     if (commentList[i] != NULL) {
13336         fprintf(f, "[%s]\n", commentList[i]);
13337     }
13338
13339     /* This isn't really the old style, but it's close enough */
13340     if (gameInfo.resultDetails != NULL &&
13341         gameInfo.resultDetails[0] != NULLCHAR) {
13342         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13343                 gameInfo.resultDetails);
13344     } else {
13345         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13346     }
13347
13348     fclose(f);
13349     return TRUE;
13350 }
13351
13352 /* Save the current game to open file f and close the file */
13353 int
13354 SaveGame (FILE *f, int dummy, char *dummy2)
13355 {
13356     if (gameMode == EditPosition) EditPositionDone(TRUE);
13357     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13358     if (appData.oldSaveStyle)
13359       return SaveGameOldStyle(f);
13360     else
13361       return SaveGamePGN(f);
13362 }
13363
13364 /* Save the current position to the given file */
13365 int
13366 SavePositionToFile (char *filename)
13367 {
13368     FILE *f;
13369     char buf[MSG_SIZ];
13370
13371     if (strcmp(filename, "-") == 0) {
13372         return SavePosition(stdout, 0, NULL);
13373     } else {
13374         f = fopen(filename, "a");
13375         if (f == NULL) {
13376             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13377             DisplayError(buf, errno);
13378             return FALSE;
13379         } else {
13380             safeStrCpy(buf, lastMsg, MSG_SIZ);
13381             DisplayMessage(_("Waiting for access to save file"), "");
13382             flock(fileno(f), LOCK_EX); // [HGM] lock
13383             DisplayMessage(_("Saving position"), "");
13384             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13385             SavePosition(f, 0, NULL);
13386             DisplayMessage(buf, "");
13387             return TRUE;
13388         }
13389     }
13390 }
13391
13392 /* Save the current position to the given open file and close the file */
13393 int
13394 SavePosition (FILE *f, int dummy, char *dummy2)
13395 {
13396     time_t tm;
13397     char *fen;
13398
13399     if (gameMode == EditPosition) EditPositionDone(TRUE);
13400     if (appData.oldSaveStyle) {
13401         tm = time((time_t *) NULL);
13402
13403         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13404         PrintOpponents(f);
13405         fprintf(f, "[--------------\n");
13406         PrintPosition(f, currentMove);
13407         fprintf(f, "--------------]\n");
13408     } else {
13409         fen = PositionToFEN(currentMove, NULL, 1);
13410         fprintf(f, "%s\n", fen);
13411         free(fen);
13412     }
13413     fclose(f);
13414     return TRUE;
13415 }
13416
13417 void
13418 ReloadCmailMsgEvent (int unregister)
13419 {
13420 #if !WIN32
13421     static char *inFilename = NULL;
13422     static char *outFilename;
13423     int i;
13424     struct stat inbuf, outbuf;
13425     int status;
13426
13427     /* Any registered moves are unregistered if unregister is set, */
13428     /* i.e. invoked by the signal handler */
13429     if (unregister) {
13430         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13431             cmailMoveRegistered[i] = FALSE;
13432             if (cmailCommentList[i] != NULL) {
13433                 free(cmailCommentList[i]);
13434                 cmailCommentList[i] = NULL;
13435             }
13436         }
13437         nCmailMovesRegistered = 0;
13438     }
13439
13440     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13441         cmailResult[i] = CMAIL_NOT_RESULT;
13442     }
13443     nCmailResults = 0;
13444
13445     if (inFilename == NULL) {
13446         /* Because the filenames are static they only get malloced once  */
13447         /* and they never get freed                                      */
13448         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13449         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13450
13451         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13452         sprintf(outFilename, "%s.out", appData.cmailGameName);
13453     }
13454
13455     status = stat(outFilename, &outbuf);
13456     if (status < 0) {
13457         cmailMailedMove = FALSE;
13458     } else {
13459         status = stat(inFilename, &inbuf);
13460         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13461     }
13462
13463     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13464        counts the games, notes how each one terminated, etc.
13465
13466        It would be nice to remove this kludge and instead gather all
13467        the information while building the game list.  (And to keep it
13468        in the game list nodes instead of having a bunch of fixed-size
13469        parallel arrays.)  Note this will require getting each game's
13470        termination from the PGN tags, as the game list builder does
13471        not process the game moves.  --mann
13472        */
13473     cmailMsgLoaded = TRUE;
13474     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13475
13476     /* Load first game in the file or popup game menu */
13477     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13478
13479 #endif /* !WIN32 */
13480     return;
13481 }
13482
13483 int
13484 RegisterMove ()
13485 {
13486     FILE *f;
13487     char string[MSG_SIZ];
13488
13489     if (   cmailMailedMove
13490         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13491         return TRUE;            /* Allow free viewing  */
13492     }
13493
13494     /* Unregister move to ensure that we don't leave RegisterMove        */
13495     /* with the move registered when the conditions for registering no   */
13496     /* longer hold                                                       */
13497     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13498         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13499         nCmailMovesRegistered --;
13500
13501         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13502           {
13503               free(cmailCommentList[lastLoadGameNumber - 1]);
13504               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13505           }
13506     }
13507
13508     if (cmailOldMove == -1) {
13509         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13510         return FALSE;
13511     }
13512
13513     if (currentMove > cmailOldMove + 1) {
13514         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13515         return FALSE;
13516     }
13517
13518     if (currentMove < cmailOldMove) {
13519         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13520         return FALSE;
13521     }
13522
13523     if (forwardMostMove > currentMove) {
13524         /* Silently truncate extra moves */
13525         TruncateGame();
13526     }
13527
13528     if (   (currentMove == cmailOldMove + 1)
13529         || (   (currentMove == cmailOldMove)
13530             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13531                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13532         if (gameInfo.result != GameUnfinished) {
13533             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13534         }
13535
13536         if (commentList[currentMove] != NULL) {
13537             cmailCommentList[lastLoadGameNumber - 1]
13538               = StrSave(commentList[currentMove]);
13539         }
13540         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13541
13542         if (appData.debugMode)
13543           fprintf(debugFP, "Saving %s for game %d\n",
13544                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13545
13546         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13547
13548         f = fopen(string, "w");
13549         if (appData.oldSaveStyle) {
13550             SaveGameOldStyle(f); /* also closes the file */
13551
13552             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13553             f = fopen(string, "w");
13554             SavePosition(f, 0, NULL); /* also closes the file */
13555         } else {
13556             fprintf(f, "{--------------\n");
13557             PrintPosition(f, currentMove);
13558             fprintf(f, "--------------}\n\n");
13559
13560             SaveGame(f, 0, NULL); /* also closes the file*/
13561         }
13562
13563         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13564         nCmailMovesRegistered ++;
13565     } else if (nCmailGames == 1) {
13566         DisplayError(_("You have not made a move yet"), 0);
13567         return FALSE;
13568     }
13569
13570     return TRUE;
13571 }
13572
13573 void
13574 MailMoveEvent ()
13575 {
13576 #if !WIN32
13577     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13578     FILE *commandOutput;
13579     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13580     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13581     int nBuffers;
13582     int i;
13583     int archived;
13584     char *arcDir;
13585
13586     if (! cmailMsgLoaded) {
13587         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13588         return;
13589     }
13590
13591     if (nCmailGames == nCmailResults) {
13592         DisplayError(_("No unfinished games"), 0);
13593         return;
13594     }
13595
13596 #if CMAIL_PROHIBIT_REMAIL
13597     if (cmailMailedMove) {
13598       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);
13599         DisplayError(msg, 0);
13600         return;
13601     }
13602 #endif
13603
13604     if (! (cmailMailedMove || RegisterMove())) return;
13605
13606     if (   cmailMailedMove
13607         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13608       snprintf(string, MSG_SIZ, partCommandString,
13609                appData.debugMode ? " -v" : "", appData.cmailGameName);
13610         commandOutput = popen(string, "r");
13611
13612         if (commandOutput == NULL) {
13613             DisplayError(_("Failed to invoke cmail"), 0);
13614         } else {
13615             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13616                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13617             }
13618             if (nBuffers > 1) {
13619                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13620                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13621                 nBytes = MSG_SIZ - 1;
13622             } else {
13623                 (void) memcpy(msg, buffer, nBytes);
13624             }
13625             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13626
13627             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13628                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13629
13630                 archived = TRUE;
13631                 for (i = 0; i < nCmailGames; i ++) {
13632                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13633                         archived = FALSE;
13634                     }
13635                 }
13636                 if (   archived
13637                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13638                         != NULL)) {
13639                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13640                            arcDir,
13641                            appData.cmailGameName,
13642                            gameInfo.date);
13643                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13644                     cmailMsgLoaded = FALSE;
13645                 }
13646             }
13647
13648             DisplayInformation(msg);
13649             pclose(commandOutput);
13650         }
13651     } else {
13652         if ((*cmailMsg) != '\0') {
13653             DisplayInformation(cmailMsg);
13654         }
13655     }
13656
13657     return;
13658 #endif /* !WIN32 */
13659 }
13660
13661 char *
13662 CmailMsg ()
13663 {
13664 #if WIN32
13665     return NULL;
13666 #else
13667     int  prependComma = 0;
13668     char number[5];
13669     char string[MSG_SIZ];       /* Space for game-list */
13670     int  i;
13671
13672     if (!cmailMsgLoaded) return "";
13673
13674     if (cmailMailedMove) {
13675       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13676     } else {
13677         /* Create a list of games left */
13678       snprintf(string, MSG_SIZ, "[");
13679         for (i = 0; i < nCmailGames; i ++) {
13680             if (! (   cmailMoveRegistered[i]
13681                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13682                 if (prependComma) {
13683                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13684                 } else {
13685                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13686                     prependComma = 1;
13687                 }
13688
13689                 strcat(string, number);
13690             }
13691         }
13692         strcat(string, "]");
13693
13694         if (nCmailMovesRegistered + nCmailResults == 0) {
13695             switch (nCmailGames) {
13696               case 1:
13697                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13698                 break;
13699
13700               case 2:
13701                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13702                 break;
13703
13704               default:
13705                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13706                          nCmailGames);
13707                 break;
13708             }
13709         } else {
13710             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13711               case 1:
13712                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13713                          string);
13714                 break;
13715
13716               case 0:
13717                 if (nCmailResults == nCmailGames) {
13718                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13719                 } else {
13720                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13721                 }
13722                 break;
13723
13724               default:
13725                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13726                          string);
13727             }
13728         }
13729     }
13730     return cmailMsg;
13731 #endif /* WIN32 */
13732 }
13733
13734 void
13735 ResetGameEvent ()
13736 {
13737     if (gameMode == Training)
13738       SetTrainingModeOff();
13739
13740     Reset(TRUE, TRUE);
13741     cmailMsgLoaded = FALSE;
13742     if (appData.icsActive) {
13743       SendToICS(ics_prefix);
13744       SendToICS("refresh\n");
13745     }
13746 }
13747
13748 void
13749 ExitEvent (int status)
13750 {
13751     exiting++;
13752     if (exiting > 2) {
13753       /* Give up on clean exit */
13754       exit(status);
13755     }
13756     if (exiting > 1) {
13757       /* Keep trying for clean exit */
13758       return;
13759     }
13760
13761     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13762
13763     if (telnetISR != NULL) {
13764       RemoveInputSource(telnetISR);
13765     }
13766     if (icsPR != NoProc) {
13767       DestroyChildProcess(icsPR, TRUE);
13768     }
13769
13770     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13771     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13772
13773     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13774     /* make sure this other one finishes before killing it!                  */
13775     if(endingGame) { int count = 0;
13776         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13777         while(endingGame && count++ < 10) DoSleep(1);
13778         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13779     }
13780
13781     /* Kill off chess programs */
13782     if (first.pr != NoProc) {
13783         ExitAnalyzeMode();
13784
13785         DoSleep( appData.delayBeforeQuit );
13786         SendToProgram("quit\n", &first);
13787         DoSleep( appData.delayAfterQuit );
13788         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13789     }
13790     if (second.pr != NoProc) {
13791         DoSleep( appData.delayBeforeQuit );
13792         SendToProgram("quit\n", &second);
13793         DoSleep( appData.delayAfterQuit );
13794         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13795     }
13796     if (first.isr != NULL) {
13797         RemoveInputSource(first.isr);
13798     }
13799     if (second.isr != NULL) {
13800         RemoveInputSource(second.isr);
13801     }
13802
13803     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13804     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13805
13806     ShutDownFrontEnd();
13807     exit(status);
13808 }
13809
13810 void
13811 PauseEngine (ChessProgramState *cps)
13812 {
13813     SendToProgram("pause\n", cps);
13814     cps->pause = 2;
13815 }
13816
13817 void
13818 UnPauseEngine (ChessProgramState *cps)
13819 {
13820     SendToProgram("resume\n", cps);
13821     cps->pause = 1;
13822 }
13823
13824 void
13825 PauseEvent ()
13826 {
13827     if (appData.debugMode)
13828         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13829     if (pausing) {
13830         pausing = FALSE;
13831         ModeHighlight();
13832         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13833             StartClocks();
13834             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13835                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13836                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13837             }
13838             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13839             HandleMachineMove(stashedInputMove, stalledEngine);
13840             stalledEngine = NULL;
13841             return;
13842         }
13843         if (gameMode == MachinePlaysWhite ||
13844             gameMode == TwoMachinesPlay   ||
13845             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13846             if(first.pause)  UnPauseEngine(&first);
13847             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13848             if(second.pause) UnPauseEngine(&second);
13849             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13850             StartClocks();
13851         } else {
13852             DisplayBothClocks();
13853         }
13854         if (gameMode == PlayFromGameFile) {
13855             if (appData.timeDelay >= 0)
13856                 AutoPlayGameLoop();
13857         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13858             Reset(FALSE, TRUE);
13859             SendToICS(ics_prefix);
13860             SendToICS("refresh\n");
13861         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13862             ForwardInner(forwardMostMove);
13863         }
13864         pauseExamInvalid = FALSE;
13865     } else {
13866         switch (gameMode) {
13867           default:
13868             return;
13869           case IcsExamining:
13870             pauseExamForwardMostMove = forwardMostMove;
13871             pauseExamInvalid = FALSE;
13872             /* fall through */
13873           case IcsObserving:
13874           case IcsPlayingWhite:
13875           case IcsPlayingBlack:
13876             pausing = TRUE;
13877             ModeHighlight();
13878             return;
13879           case PlayFromGameFile:
13880             (void) StopLoadGameTimer();
13881             pausing = TRUE;
13882             ModeHighlight();
13883             break;
13884           case BeginningOfGame:
13885             if (appData.icsActive) return;
13886             /* else fall through */
13887           case MachinePlaysWhite:
13888           case MachinePlaysBlack:
13889           case TwoMachinesPlay:
13890             if (forwardMostMove == 0)
13891               return;           /* don't pause if no one has moved */
13892             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13893                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13894                 if(onMove->pause) {           // thinking engine can be paused
13895                     PauseEngine(onMove);      // do it
13896                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13897                         PauseEngine(onMove->other);
13898                     else
13899                         SendToProgram("easy\n", onMove->other);
13900                     StopClocks();
13901                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13902             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13903                 if(first.pause) {
13904                     PauseEngine(&first);
13905                     StopClocks();
13906                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13907             } else { // human on move, pause pondering by either method
13908                 if(first.pause)
13909                     PauseEngine(&first);
13910                 else if(appData.ponderNextMove)
13911                     SendToProgram("easy\n", &first);
13912                 StopClocks();
13913             }
13914             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13915           case AnalyzeMode:
13916             pausing = TRUE;
13917             ModeHighlight();
13918             break;
13919         }
13920     }
13921 }
13922
13923 void
13924 EditCommentEvent ()
13925 {
13926     char title[MSG_SIZ];
13927
13928     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13929       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13930     } else {
13931       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13932                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13933                parseList[currentMove - 1]);
13934     }
13935
13936     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13937 }
13938
13939
13940 void
13941 EditTagsEvent ()
13942 {
13943     char *tags = PGNTags(&gameInfo);
13944     bookUp = FALSE;
13945     EditTagsPopUp(tags, NULL);
13946     free(tags);
13947 }
13948
13949 void
13950 ToggleSecond ()
13951 {
13952   if(second.analyzing) {
13953     SendToProgram("exit\n", &second);
13954     second.analyzing = FALSE;
13955   } else {
13956     if (second.pr == NoProc) StartChessProgram(&second);
13957     InitChessProgram(&second, FALSE);
13958     FeedMovesToProgram(&second, currentMove);
13959
13960     SendToProgram("analyze\n", &second);
13961     second.analyzing = TRUE;
13962   }
13963 }
13964
13965 /* Toggle ShowThinking */
13966 void
13967 ToggleShowThinking()
13968 {
13969   appData.showThinking = !appData.showThinking;
13970   ShowThinkingEvent();
13971 }
13972
13973 int
13974 AnalyzeModeEvent ()
13975 {
13976     char buf[MSG_SIZ];
13977
13978     if (!first.analysisSupport) {
13979       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13980       DisplayError(buf, 0);
13981       return 0;
13982     }
13983     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13984     if (appData.icsActive) {
13985         if (gameMode != IcsObserving) {
13986           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13987             DisplayError(buf, 0);
13988             /* secure check */
13989             if (appData.icsEngineAnalyze) {
13990                 if (appData.debugMode)
13991                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13992                 ExitAnalyzeMode();
13993                 ModeHighlight();
13994             }
13995             return 0;
13996         }
13997         /* if enable, user wants to disable icsEngineAnalyze */
13998         if (appData.icsEngineAnalyze) {
13999                 ExitAnalyzeMode();
14000                 ModeHighlight();
14001                 return 0;
14002         }
14003         appData.icsEngineAnalyze = TRUE;
14004         if (appData.debugMode)
14005             fprintf(debugFP, "ICS engine analyze starting... \n");
14006     }
14007
14008     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14009     if (appData.noChessProgram || gameMode == AnalyzeMode)
14010       return 0;
14011
14012     if (gameMode != AnalyzeFile) {
14013         if (!appData.icsEngineAnalyze) {
14014                EditGameEvent();
14015                if (gameMode != EditGame) return 0;
14016         }
14017         if (!appData.showThinking) ToggleShowThinking();
14018         ResurrectChessProgram();
14019         SendToProgram("analyze\n", &first);
14020         first.analyzing = TRUE;
14021         /*first.maybeThinking = TRUE;*/
14022         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14023         EngineOutputPopUp();
14024     }
14025     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14026     pausing = FALSE;
14027     ModeHighlight();
14028     SetGameInfo();
14029
14030     StartAnalysisClock();
14031     GetTimeMark(&lastNodeCountTime);
14032     lastNodeCount = 0;
14033     return 1;
14034 }
14035
14036 void
14037 AnalyzeFileEvent ()
14038 {
14039     if (appData.noChessProgram || gameMode == AnalyzeFile)
14040       return;
14041
14042     if (!first.analysisSupport) {
14043       char buf[MSG_SIZ];
14044       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14045       DisplayError(buf, 0);
14046       return;
14047     }
14048
14049     if (gameMode != AnalyzeMode) {
14050         keepInfo = 1; // mere annotating should not alter PGN tags
14051         EditGameEvent();
14052         keepInfo = 0;
14053         if (gameMode != EditGame) return;
14054         if (!appData.showThinking) ToggleShowThinking();
14055         ResurrectChessProgram();
14056         SendToProgram("analyze\n", &first);
14057         first.analyzing = TRUE;
14058         /*first.maybeThinking = TRUE;*/
14059         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14060         EngineOutputPopUp();
14061     }
14062     gameMode = AnalyzeFile;
14063     pausing = FALSE;
14064     ModeHighlight();
14065
14066     StartAnalysisClock();
14067     GetTimeMark(&lastNodeCountTime);
14068     lastNodeCount = 0;
14069     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14070     AnalysisPeriodicEvent(1);
14071 }
14072
14073 void
14074 MachineWhiteEvent ()
14075 {
14076     char buf[MSG_SIZ];
14077     char *bookHit = NULL;
14078
14079     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14080       return;
14081
14082
14083     if (gameMode == PlayFromGameFile ||
14084         gameMode == TwoMachinesPlay  ||
14085         gameMode == Training         ||
14086         gameMode == AnalyzeMode      ||
14087         gameMode == EndOfGame)
14088         EditGameEvent();
14089
14090     if (gameMode == EditPosition)
14091         EditPositionDone(TRUE);
14092
14093     if (!WhiteOnMove(currentMove)) {
14094         DisplayError(_("It is not White's turn"), 0);
14095         return;
14096     }
14097
14098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14099       ExitAnalyzeMode();
14100
14101     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14102         gameMode == AnalyzeFile)
14103         TruncateGame();
14104
14105     ResurrectChessProgram();    /* in case it isn't running */
14106     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14107         gameMode = MachinePlaysWhite;
14108         ResetClocks();
14109     } else
14110     gameMode = MachinePlaysWhite;
14111     pausing = FALSE;
14112     ModeHighlight();
14113     SetGameInfo();
14114     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14115     DisplayTitle(buf);
14116     if (first.sendName) {
14117       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14118       SendToProgram(buf, &first);
14119     }
14120     if (first.sendTime) {
14121       if (first.useColors) {
14122         SendToProgram("black\n", &first); /*gnu kludge*/
14123       }
14124       SendTimeRemaining(&first, TRUE);
14125     }
14126     if (first.useColors) {
14127       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14128     }
14129     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14130     SetMachineThinkingEnables();
14131     first.maybeThinking = TRUE;
14132     StartClocks();
14133     firstMove = FALSE;
14134
14135     if (appData.autoFlipView && !flipView) {
14136       flipView = !flipView;
14137       DrawPosition(FALSE, NULL);
14138       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14139     }
14140
14141     if(bookHit) { // [HGM] book: simulate book reply
14142         static char bookMove[MSG_SIZ]; // a bit generous?
14143
14144         programStats.nodes = programStats.depth = programStats.time =
14145         programStats.score = programStats.got_only_move = 0;
14146         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14147
14148         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14149         strcat(bookMove, bookHit);
14150         HandleMachineMove(bookMove, &first);
14151     }
14152 }
14153
14154 void
14155 MachineBlackEvent ()
14156 {
14157   char buf[MSG_SIZ];
14158   char *bookHit = NULL;
14159
14160     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14161         return;
14162
14163
14164     if (gameMode == PlayFromGameFile ||
14165         gameMode == TwoMachinesPlay  ||
14166         gameMode == Training         ||
14167         gameMode == AnalyzeMode      ||
14168         gameMode == EndOfGame)
14169         EditGameEvent();
14170
14171     if (gameMode == EditPosition)
14172         EditPositionDone(TRUE);
14173
14174     if (WhiteOnMove(currentMove)) {
14175         DisplayError(_("It is not Black's turn"), 0);
14176         return;
14177     }
14178
14179     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14180       ExitAnalyzeMode();
14181
14182     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14183         gameMode == AnalyzeFile)
14184         TruncateGame();
14185
14186     ResurrectChessProgram();    /* in case it isn't running */
14187     gameMode = MachinePlaysBlack;
14188     pausing = FALSE;
14189     ModeHighlight();
14190     SetGameInfo();
14191     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14192     DisplayTitle(buf);
14193     if (first.sendName) {
14194       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14195       SendToProgram(buf, &first);
14196     }
14197     if (first.sendTime) {
14198       if (first.useColors) {
14199         SendToProgram("white\n", &first); /*gnu kludge*/
14200       }
14201       SendTimeRemaining(&first, FALSE);
14202     }
14203     if (first.useColors) {
14204       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14205     }
14206     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14207     SetMachineThinkingEnables();
14208     first.maybeThinking = TRUE;
14209     StartClocks();
14210
14211     if (appData.autoFlipView && flipView) {
14212       flipView = !flipView;
14213       DrawPosition(FALSE, NULL);
14214       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14215     }
14216     if(bookHit) { // [HGM] book: simulate book reply
14217         static char bookMove[MSG_SIZ]; // a bit generous?
14218
14219         programStats.nodes = programStats.depth = programStats.time =
14220         programStats.score = programStats.got_only_move = 0;
14221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14222
14223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14224         strcat(bookMove, bookHit);
14225         HandleMachineMove(bookMove, &first);
14226     }
14227 }
14228
14229
14230 void
14231 DisplayTwoMachinesTitle ()
14232 {
14233     char buf[MSG_SIZ];
14234     if (appData.matchGames > 0) {
14235         if(appData.tourneyFile[0]) {
14236           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14237                    gameInfo.white, _("vs."), gameInfo.black,
14238                    nextGame+1, appData.matchGames+1,
14239                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14240         } else
14241         if (first.twoMachinesColor[0] == 'w') {
14242           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14243                    gameInfo.white, _("vs."),  gameInfo.black,
14244                    first.matchWins, second.matchWins,
14245                    matchGame - 1 - (first.matchWins + second.matchWins));
14246         } else {
14247           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14248                    gameInfo.white, _("vs."), gameInfo.black,
14249                    second.matchWins, first.matchWins,
14250                    matchGame - 1 - (first.matchWins + second.matchWins));
14251         }
14252     } else {
14253       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14254     }
14255     DisplayTitle(buf);
14256 }
14257
14258 void
14259 SettingsMenuIfReady ()
14260 {
14261   if (second.lastPing != second.lastPong) {
14262     DisplayMessage("", _("Waiting for second chess program"));
14263     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14264     return;
14265   }
14266   ThawUI();
14267   DisplayMessage("", "");
14268   SettingsPopUp(&second);
14269 }
14270
14271 int
14272 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14273 {
14274     char buf[MSG_SIZ];
14275     if (cps->pr == NoProc) {
14276         StartChessProgram(cps);
14277         if (cps->protocolVersion == 1) {
14278           retry();
14279           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14280         } else {
14281           /* kludge: allow timeout for initial "feature" command */
14282           if(retry != TwoMachinesEventIfReady) FreezeUI();
14283           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14284           DisplayMessage("", buf);
14285           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14286         }
14287         return 1;
14288     }
14289     return 0;
14290 }
14291
14292 void
14293 TwoMachinesEvent P((void))
14294 {
14295     int i;
14296     char buf[MSG_SIZ];
14297     ChessProgramState *onmove;
14298     char *bookHit = NULL;
14299     static int stalling = 0;
14300     TimeMark now;
14301     long wait;
14302
14303     if (appData.noChessProgram) return;
14304
14305     switch (gameMode) {
14306       case TwoMachinesPlay:
14307         return;
14308       case MachinePlaysWhite:
14309       case MachinePlaysBlack:
14310         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14311             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14312             return;
14313         }
14314         /* fall through */
14315       case BeginningOfGame:
14316       case PlayFromGameFile:
14317       case EndOfGame:
14318         EditGameEvent();
14319         if (gameMode != EditGame) return;
14320         break;
14321       case EditPosition:
14322         EditPositionDone(TRUE);
14323         break;
14324       case AnalyzeMode:
14325       case AnalyzeFile:
14326         ExitAnalyzeMode();
14327         break;
14328       case EditGame:
14329       default:
14330         break;
14331     }
14332
14333 //    forwardMostMove = currentMove;
14334     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14335     startingEngine = TRUE;
14336
14337     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14338
14339     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14340     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14341       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14342       return;
14343     }
14344     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14345
14346     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14347         startingEngine = FALSE;
14348         DisplayError("second engine does not play this", 0);
14349         return;
14350     }
14351
14352     if(!stalling) {
14353       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14354       SendToProgram("force\n", &second);
14355       stalling = 1;
14356       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14357       return;
14358     }
14359     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14360     if(appData.matchPause>10000 || appData.matchPause<10)
14361                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14362     wait = SubtractTimeMarks(&now, &pauseStart);
14363     if(wait < appData.matchPause) {
14364         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14365         return;
14366     }
14367     // we are now committed to starting the game
14368     stalling = 0;
14369     DisplayMessage("", "");
14370     if (startedFromSetupPosition) {
14371         SendBoard(&second, backwardMostMove);
14372     if (appData.debugMode) {
14373         fprintf(debugFP, "Two Machines\n");
14374     }
14375     }
14376     for (i = backwardMostMove; i < forwardMostMove; i++) {
14377         SendMoveToProgram(i, &second);
14378     }
14379
14380     gameMode = TwoMachinesPlay;
14381     pausing = startingEngine = FALSE;
14382     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14383     SetGameInfo();
14384     DisplayTwoMachinesTitle();
14385     firstMove = TRUE;
14386     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14387         onmove = &first;
14388     } else {
14389         onmove = &second;
14390     }
14391     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14392     SendToProgram(first.computerString, &first);
14393     if (first.sendName) {
14394       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14395       SendToProgram(buf, &first);
14396     }
14397     SendToProgram(second.computerString, &second);
14398     if (second.sendName) {
14399       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14400       SendToProgram(buf, &second);
14401     }
14402
14403     ResetClocks();
14404     if (!first.sendTime || !second.sendTime) {
14405         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14406         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14407     }
14408     if (onmove->sendTime) {
14409       if (onmove->useColors) {
14410         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14411       }
14412       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14413     }
14414     if (onmove->useColors) {
14415       SendToProgram(onmove->twoMachinesColor, onmove);
14416     }
14417     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14418 //    SendToProgram("go\n", onmove);
14419     onmove->maybeThinking = TRUE;
14420     SetMachineThinkingEnables();
14421
14422     StartClocks();
14423
14424     if(bookHit) { // [HGM] book: simulate book reply
14425         static char bookMove[MSG_SIZ]; // a bit generous?
14426
14427         programStats.nodes = programStats.depth = programStats.time =
14428         programStats.score = programStats.got_only_move = 0;
14429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14430
14431         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14432         strcat(bookMove, bookHit);
14433         savedMessage = bookMove; // args for deferred call
14434         savedState = onmove;
14435         ScheduleDelayedEvent(DeferredBookMove, 1);
14436     }
14437 }
14438
14439 void
14440 TrainingEvent ()
14441 {
14442     if (gameMode == Training) {
14443       SetTrainingModeOff();
14444       gameMode = PlayFromGameFile;
14445       DisplayMessage("", _("Training mode off"));
14446     } else {
14447       gameMode = Training;
14448       animateTraining = appData.animate;
14449
14450       /* make sure we are not already at the end of the game */
14451       if (currentMove < forwardMostMove) {
14452         SetTrainingModeOn();
14453         DisplayMessage("", _("Training mode on"));
14454       } else {
14455         gameMode = PlayFromGameFile;
14456         DisplayError(_("Already at end of game"), 0);
14457       }
14458     }
14459     ModeHighlight();
14460 }
14461
14462 void
14463 IcsClientEvent ()
14464 {
14465     if (!appData.icsActive) return;
14466     switch (gameMode) {
14467       case IcsPlayingWhite:
14468       case IcsPlayingBlack:
14469       case IcsObserving:
14470       case IcsIdle:
14471       case BeginningOfGame:
14472       case IcsExamining:
14473         return;
14474
14475       case EditGame:
14476         break;
14477
14478       case EditPosition:
14479         EditPositionDone(TRUE);
14480         break;
14481
14482       case AnalyzeMode:
14483       case AnalyzeFile:
14484         ExitAnalyzeMode();
14485         break;
14486
14487       default:
14488         EditGameEvent();
14489         break;
14490     }
14491
14492     gameMode = IcsIdle;
14493     ModeHighlight();
14494     return;
14495 }
14496
14497 void
14498 EditGameEvent ()
14499 {
14500     int i;
14501
14502     switch (gameMode) {
14503       case Training:
14504         SetTrainingModeOff();
14505         break;
14506       case MachinePlaysWhite:
14507       case MachinePlaysBlack:
14508       case BeginningOfGame:
14509         SendToProgram("force\n", &first);
14510         SetUserThinkingEnables();
14511         break;
14512       case PlayFromGameFile:
14513         (void) StopLoadGameTimer();
14514         if (gameFileFP != NULL) {
14515             gameFileFP = NULL;
14516         }
14517         break;
14518       case EditPosition:
14519         EditPositionDone(TRUE);
14520         break;
14521       case AnalyzeMode:
14522       case AnalyzeFile:
14523         ExitAnalyzeMode();
14524         SendToProgram("force\n", &first);
14525         break;
14526       case TwoMachinesPlay:
14527         GameEnds(EndOfFile, NULL, GE_PLAYER);
14528         ResurrectChessProgram();
14529         SetUserThinkingEnables();
14530         break;
14531       case EndOfGame:
14532         ResurrectChessProgram();
14533         break;
14534       case IcsPlayingBlack:
14535       case IcsPlayingWhite:
14536         DisplayError(_("Warning: You are still playing a game"), 0);
14537         break;
14538       case IcsObserving:
14539         DisplayError(_("Warning: You are still observing a game"), 0);
14540         break;
14541       case IcsExamining:
14542         DisplayError(_("Warning: You are still examining a game"), 0);
14543         break;
14544       case IcsIdle:
14545         break;
14546       case EditGame:
14547       default:
14548         return;
14549     }
14550
14551     pausing = FALSE;
14552     StopClocks();
14553     first.offeredDraw = second.offeredDraw = 0;
14554
14555     if (gameMode == PlayFromGameFile) {
14556         whiteTimeRemaining = timeRemaining[0][currentMove];
14557         blackTimeRemaining = timeRemaining[1][currentMove];
14558         DisplayTitle("");
14559     }
14560
14561     if (gameMode == MachinePlaysWhite ||
14562         gameMode == MachinePlaysBlack ||
14563         gameMode == TwoMachinesPlay ||
14564         gameMode == EndOfGame) {
14565         i = forwardMostMove;
14566         while (i > currentMove) {
14567             SendToProgram("undo\n", &first);
14568             i--;
14569         }
14570         if(!adjustedClock) {
14571         whiteTimeRemaining = timeRemaining[0][currentMove];
14572         blackTimeRemaining = timeRemaining[1][currentMove];
14573         DisplayBothClocks();
14574         }
14575         if (whiteFlag || blackFlag) {
14576             whiteFlag = blackFlag = 0;
14577         }
14578         DisplayTitle("");
14579     }
14580
14581     gameMode = EditGame;
14582     ModeHighlight();
14583     SetGameInfo();
14584 }
14585
14586
14587 void
14588 EditPositionEvent ()
14589 {
14590     if (gameMode == EditPosition) {
14591         EditGameEvent();
14592         return;
14593     }
14594
14595     EditGameEvent();
14596     if (gameMode != EditGame) return;
14597
14598     gameMode = EditPosition;
14599     ModeHighlight();
14600     SetGameInfo();
14601     if (currentMove > 0)
14602       CopyBoard(boards[0], boards[currentMove]);
14603
14604     blackPlaysFirst = !WhiteOnMove(currentMove);
14605     ResetClocks();
14606     currentMove = forwardMostMove = backwardMostMove = 0;
14607     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14608     DisplayMove(-1);
14609     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14610 }
14611
14612 void
14613 ExitAnalyzeMode ()
14614 {
14615     /* [DM] icsEngineAnalyze - possible call from other functions */
14616     if (appData.icsEngineAnalyze) {
14617         appData.icsEngineAnalyze = FALSE;
14618
14619         DisplayMessage("",_("Close ICS engine analyze..."));
14620     }
14621     if (first.analysisSupport && first.analyzing) {
14622       SendToBoth("exit\n");
14623       first.analyzing = second.analyzing = FALSE;
14624     }
14625     thinkOutput[0] = NULLCHAR;
14626 }
14627
14628 void
14629 EditPositionDone (Boolean fakeRights)
14630 {
14631     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14632
14633     startedFromSetupPosition = TRUE;
14634     InitChessProgram(&first, FALSE);
14635     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14636       boards[0][EP_STATUS] = EP_NONE;
14637       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14638       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14639         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14640         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14641       } else boards[0][CASTLING][2] = NoRights;
14642       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14643         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14644         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14645       } else boards[0][CASTLING][5] = NoRights;
14646       if(gameInfo.variant == VariantSChess) {
14647         int i;
14648         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14649           boards[0][VIRGIN][i] = 0;
14650           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14651           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14652         }
14653       }
14654     }
14655     SendToProgram("force\n", &first);
14656     if (blackPlaysFirst) {
14657         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14658         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14659         currentMove = forwardMostMove = backwardMostMove = 1;
14660         CopyBoard(boards[1], boards[0]);
14661     } else {
14662         currentMove = forwardMostMove = backwardMostMove = 0;
14663     }
14664     SendBoard(&first, forwardMostMove);
14665     if (appData.debugMode) {
14666         fprintf(debugFP, "EditPosDone\n");
14667     }
14668     DisplayTitle("");
14669     DisplayMessage("", "");
14670     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14671     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14672     gameMode = EditGame;
14673     ModeHighlight();
14674     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14675     ClearHighlights(); /* [AS] */
14676 }
14677
14678 /* Pause for `ms' milliseconds */
14679 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14680 void
14681 TimeDelay (long ms)
14682 {
14683     TimeMark m1, m2;
14684
14685     GetTimeMark(&m1);
14686     do {
14687         GetTimeMark(&m2);
14688     } while (SubtractTimeMarks(&m2, &m1) < ms);
14689 }
14690
14691 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14692 void
14693 SendMultiLineToICS (char *buf)
14694 {
14695     char temp[MSG_SIZ+1], *p;
14696     int len;
14697
14698     len = strlen(buf);
14699     if (len > MSG_SIZ)
14700       len = MSG_SIZ;
14701
14702     strncpy(temp, buf, len);
14703     temp[len] = 0;
14704
14705     p = temp;
14706     while (*p) {
14707         if (*p == '\n' || *p == '\r')
14708           *p = ' ';
14709         ++p;
14710     }
14711
14712     strcat(temp, "\n");
14713     SendToICS(temp);
14714     SendToPlayer(temp, strlen(temp));
14715 }
14716
14717 void
14718 SetWhiteToPlayEvent ()
14719 {
14720     if (gameMode == EditPosition) {
14721         blackPlaysFirst = FALSE;
14722         DisplayBothClocks();    /* works because currentMove is 0 */
14723     } else if (gameMode == IcsExamining) {
14724         SendToICS(ics_prefix);
14725         SendToICS("tomove white\n");
14726     }
14727 }
14728
14729 void
14730 SetBlackToPlayEvent ()
14731 {
14732     if (gameMode == EditPosition) {
14733         blackPlaysFirst = TRUE;
14734         currentMove = 1;        /* kludge */
14735         DisplayBothClocks();
14736         currentMove = 0;
14737     } else if (gameMode == IcsExamining) {
14738         SendToICS(ics_prefix);
14739         SendToICS("tomove black\n");
14740     }
14741 }
14742
14743 void
14744 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14745 {
14746     char buf[MSG_SIZ];
14747     ChessSquare piece = boards[0][y][x];
14748     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14749     static int lastVariant;
14750
14751     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14752
14753     switch (selection) {
14754       case ClearBoard:
14755         CopyBoard(currentBoard, boards[0]);
14756         CopyBoard(menuBoard, initialPosition);
14757         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14758             SendToICS(ics_prefix);
14759             SendToICS("bsetup clear\n");
14760         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14761             SendToICS(ics_prefix);
14762             SendToICS("clearboard\n");
14763         } else {
14764             int nonEmpty = 0;
14765             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14766                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14767                 for (y = 0; y < BOARD_HEIGHT; y++) {
14768                     if (gameMode == IcsExamining) {
14769                         if (boards[currentMove][y][x] != EmptySquare) {
14770                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14771                                     AAA + x, ONE + y);
14772                             SendToICS(buf);
14773                         }
14774                     } else {
14775                         if(boards[0][y][x] != p) nonEmpty++;
14776                         boards[0][y][x] = p;
14777                     }
14778                 }
14779                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14780             }
14781             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14782                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14783                     ChessSquare p = menuBoard[0][x];
14784                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14785                     p = menuBoard[BOARD_HEIGHT-1][x];
14786                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14787                 }
14788                 DisplayMessage("Clicking clock again restores position", "");
14789                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14790                 if(!nonEmpty) { // asked to clear an empty board
14791                     CopyBoard(boards[0], menuBoard);
14792                 } else
14793                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14794                     CopyBoard(boards[0], initialPosition);
14795                 } else
14796                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14797                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14798                     CopyBoard(boards[0], erasedBoard);
14799                 } else
14800                     CopyBoard(erasedBoard, currentBoard);
14801
14802             }
14803         }
14804         if (gameMode == EditPosition) {
14805             DrawPosition(FALSE, boards[0]);
14806         }
14807         break;
14808
14809       case WhitePlay:
14810         SetWhiteToPlayEvent();
14811         break;
14812
14813       case BlackPlay:
14814         SetBlackToPlayEvent();
14815         break;
14816
14817       case EmptySquare:
14818         if (gameMode == IcsExamining) {
14819             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14820             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14821             SendToICS(buf);
14822         } else {
14823             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14824                 if(x == BOARD_LEFT-2) {
14825                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14826                     boards[0][y][1] = 0;
14827                 } else
14828                 if(x == BOARD_RGHT+1) {
14829                     if(y >= gameInfo.holdingsSize) break;
14830                     boards[0][y][BOARD_WIDTH-2] = 0;
14831                 } else break;
14832             }
14833             boards[0][y][x] = EmptySquare;
14834             DrawPosition(FALSE, boards[0]);
14835         }
14836         break;
14837
14838       case PromotePiece:
14839         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14840            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14841             selection = (ChessSquare) (PROMOTED piece);
14842         } else if(piece == EmptySquare) selection = WhiteSilver;
14843         else selection = (ChessSquare)((int)piece - 1);
14844         goto defaultlabel;
14845
14846       case DemotePiece:
14847         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14848            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14849             selection = (ChessSquare) (DEMOTED piece);
14850         } else if(piece == EmptySquare) selection = BlackSilver;
14851         else selection = (ChessSquare)((int)piece + 1);
14852         goto defaultlabel;
14853
14854       case WhiteQueen:
14855       case BlackQueen:
14856         if(gameInfo.variant == VariantShatranj ||
14857            gameInfo.variant == VariantXiangqi  ||
14858            gameInfo.variant == VariantCourier  ||
14859            gameInfo.variant == VariantASEAN    ||
14860            gameInfo.variant == VariantMakruk     )
14861             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14862         goto defaultlabel;
14863
14864       case WhiteKing:
14865       case BlackKing:
14866         if(gameInfo.variant == VariantXiangqi)
14867             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14868         if(gameInfo.variant == VariantKnightmate)
14869             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14870       default:
14871         defaultlabel:
14872         if (gameMode == IcsExamining) {
14873             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14874             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14875                      PieceToChar(selection), AAA + x, ONE + y);
14876             SendToICS(buf);
14877         } else {
14878             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14879                 int n;
14880                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14881                     n = PieceToNumber(selection - BlackPawn);
14882                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14883                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14884                     boards[0][BOARD_HEIGHT-1-n][1]++;
14885                 } else
14886                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14887                     n = PieceToNumber(selection);
14888                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14889                     boards[0][n][BOARD_WIDTH-1] = selection;
14890                     boards[0][n][BOARD_WIDTH-2]++;
14891                 }
14892             } else
14893             boards[0][y][x] = selection;
14894             DrawPosition(TRUE, boards[0]);
14895             ClearHighlights();
14896             fromX = fromY = -1;
14897         }
14898         break;
14899     }
14900 }
14901
14902
14903 void
14904 DropMenuEvent (ChessSquare selection, int x, int y)
14905 {
14906     ChessMove moveType;
14907
14908     switch (gameMode) {
14909       case IcsPlayingWhite:
14910       case MachinePlaysBlack:
14911         if (!WhiteOnMove(currentMove)) {
14912             DisplayMoveError(_("It is Black's turn"));
14913             return;
14914         }
14915         moveType = WhiteDrop;
14916         break;
14917       case IcsPlayingBlack:
14918       case MachinePlaysWhite:
14919         if (WhiteOnMove(currentMove)) {
14920             DisplayMoveError(_("It is White's turn"));
14921             return;
14922         }
14923         moveType = BlackDrop;
14924         break;
14925       case EditGame:
14926         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14927         break;
14928       default:
14929         return;
14930     }
14931
14932     if (moveType == BlackDrop && selection < BlackPawn) {
14933       selection = (ChessSquare) ((int) selection
14934                                  + (int) BlackPawn - (int) WhitePawn);
14935     }
14936     if (boards[currentMove][y][x] != EmptySquare) {
14937         DisplayMoveError(_("That square is occupied"));
14938         return;
14939     }
14940
14941     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14942 }
14943
14944 void
14945 AcceptEvent ()
14946 {
14947     /* Accept a pending offer of any kind from opponent */
14948
14949     if (appData.icsActive) {
14950         SendToICS(ics_prefix);
14951         SendToICS("accept\n");
14952     } else if (cmailMsgLoaded) {
14953         if (currentMove == cmailOldMove &&
14954             commentList[cmailOldMove] != NULL &&
14955             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14956                    "Black offers a draw" : "White offers a draw")) {
14957             TruncateGame();
14958             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14959             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14960         } else {
14961             DisplayError(_("There is no pending offer on this move"), 0);
14962             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14963         }
14964     } else {
14965         /* Not used for offers from chess program */
14966     }
14967 }
14968
14969 void
14970 DeclineEvent ()
14971 {
14972     /* Decline a pending offer of any kind from opponent */
14973
14974     if (appData.icsActive) {
14975         SendToICS(ics_prefix);
14976         SendToICS("decline\n");
14977     } else if (cmailMsgLoaded) {
14978         if (currentMove == cmailOldMove &&
14979             commentList[cmailOldMove] != NULL &&
14980             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14981                    "Black offers a draw" : "White offers a draw")) {
14982 #ifdef NOTDEF
14983             AppendComment(cmailOldMove, "Draw declined", TRUE);
14984             DisplayComment(cmailOldMove - 1, "Draw declined");
14985 #endif /*NOTDEF*/
14986         } else {
14987             DisplayError(_("There is no pending offer on this move"), 0);
14988         }
14989     } else {
14990         /* Not used for offers from chess program */
14991     }
14992 }
14993
14994 void
14995 RematchEvent ()
14996 {
14997     /* Issue ICS rematch command */
14998     if (appData.icsActive) {
14999         SendToICS(ics_prefix);
15000         SendToICS("rematch\n");
15001     }
15002 }
15003
15004 void
15005 CallFlagEvent ()
15006 {
15007     /* Call your opponent's flag (claim a win on time) */
15008     if (appData.icsActive) {
15009         SendToICS(ics_prefix);
15010         SendToICS("flag\n");
15011     } else {
15012         switch (gameMode) {
15013           default:
15014             return;
15015           case MachinePlaysWhite:
15016             if (whiteFlag) {
15017                 if (blackFlag)
15018                   GameEnds(GameIsDrawn, "Both players ran out of time",
15019                            GE_PLAYER);
15020                 else
15021                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15022             } else {
15023                 DisplayError(_("Your opponent is not out of time"), 0);
15024             }
15025             break;
15026           case MachinePlaysBlack:
15027             if (blackFlag) {
15028                 if (whiteFlag)
15029                   GameEnds(GameIsDrawn, "Both players ran out of time",
15030                            GE_PLAYER);
15031                 else
15032                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15033             } else {
15034                 DisplayError(_("Your opponent is not out of time"), 0);
15035             }
15036             break;
15037         }
15038     }
15039 }
15040
15041 void
15042 ClockClick (int which)
15043 {       // [HGM] code moved to back-end from winboard.c
15044         if(which) { // black clock
15045           if (gameMode == EditPosition || gameMode == IcsExamining) {
15046             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15047             SetBlackToPlayEvent();
15048           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15049           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15050           } else if (shiftKey) {
15051             AdjustClock(which, -1);
15052           } else if (gameMode == IcsPlayingWhite ||
15053                      gameMode == MachinePlaysBlack) {
15054             CallFlagEvent();
15055           }
15056         } else { // white clock
15057           if (gameMode == EditPosition || gameMode == IcsExamining) {
15058             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15059             SetWhiteToPlayEvent();
15060           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15061           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15062           } else if (shiftKey) {
15063             AdjustClock(which, -1);
15064           } else if (gameMode == IcsPlayingBlack ||
15065                    gameMode == MachinePlaysWhite) {
15066             CallFlagEvent();
15067           }
15068         }
15069 }
15070
15071 void
15072 DrawEvent ()
15073 {
15074     /* Offer draw or accept pending draw offer from opponent */
15075
15076     if (appData.icsActive) {
15077         /* Note: tournament rules require draw offers to be
15078            made after you make your move but before you punch
15079            your clock.  Currently ICS doesn't let you do that;
15080            instead, you immediately punch your clock after making
15081            a move, but you can offer a draw at any time. */
15082
15083         SendToICS(ics_prefix);
15084         SendToICS("draw\n");
15085         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15086     } else if (cmailMsgLoaded) {
15087         if (currentMove == cmailOldMove &&
15088             commentList[cmailOldMove] != NULL &&
15089             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15090                    "Black offers a draw" : "White offers a draw")) {
15091             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15092             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15093         } else if (currentMove == cmailOldMove + 1) {
15094             char *offer = WhiteOnMove(cmailOldMove) ?
15095               "White offers a draw" : "Black offers a draw";
15096             AppendComment(currentMove, offer, TRUE);
15097             DisplayComment(currentMove - 1, offer);
15098             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15099         } else {
15100             DisplayError(_("You must make your move before offering a draw"), 0);
15101             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15102         }
15103     } else if (first.offeredDraw) {
15104         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15105     } else {
15106         if (first.sendDrawOffers) {
15107             SendToProgram("draw\n", &first);
15108             userOfferedDraw = TRUE;
15109         }
15110     }
15111 }
15112
15113 void
15114 AdjournEvent ()
15115 {
15116     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15117
15118     if (appData.icsActive) {
15119         SendToICS(ics_prefix);
15120         SendToICS("adjourn\n");
15121     } else {
15122         /* Currently GNU Chess doesn't offer or accept Adjourns */
15123     }
15124 }
15125
15126
15127 void
15128 AbortEvent ()
15129 {
15130     /* Offer Abort or accept pending Abort offer from opponent */
15131
15132     if (appData.icsActive) {
15133         SendToICS(ics_prefix);
15134         SendToICS("abort\n");
15135     } else {
15136         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15137     }
15138 }
15139
15140 void
15141 ResignEvent ()
15142 {
15143     /* Resign.  You can do this even if it's not your turn. */
15144
15145     if (appData.icsActive) {
15146         SendToICS(ics_prefix);
15147         SendToICS("resign\n");
15148     } else {
15149         switch (gameMode) {
15150           case MachinePlaysWhite:
15151             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15152             break;
15153           case MachinePlaysBlack:
15154             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15155             break;
15156           case EditGame:
15157             if (cmailMsgLoaded) {
15158                 TruncateGame();
15159                 if (WhiteOnMove(cmailOldMove)) {
15160                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15161                 } else {
15162                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15163                 }
15164                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15165             }
15166             break;
15167           default:
15168             break;
15169         }
15170     }
15171 }
15172
15173
15174 void
15175 StopObservingEvent ()
15176 {
15177     /* Stop observing current games */
15178     SendToICS(ics_prefix);
15179     SendToICS("unobserve\n");
15180 }
15181
15182 void
15183 StopExaminingEvent ()
15184 {
15185     /* Stop observing current game */
15186     SendToICS(ics_prefix);
15187     SendToICS("unexamine\n");
15188 }
15189
15190 void
15191 ForwardInner (int target)
15192 {
15193     int limit; int oldSeekGraphUp = seekGraphUp;
15194
15195     if (appData.debugMode)
15196         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15197                 target, currentMove, forwardMostMove);
15198
15199     if (gameMode == EditPosition)
15200       return;
15201
15202     seekGraphUp = FALSE;
15203     MarkTargetSquares(1);
15204
15205     if (gameMode == PlayFromGameFile && !pausing)
15206       PauseEvent();
15207
15208     if (gameMode == IcsExamining && pausing)
15209       limit = pauseExamForwardMostMove;
15210     else
15211       limit = forwardMostMove;
15212
15213     if (target > limit) target = limit;
15214
15215     if (target > 0 && moveList[target - 1][0]) {
15216         int fromX, fromY, toX, toY;
15217         toX = moveList[target - 1][2] - AAA;
15218         toY = moveList[target - 1][3] - ONE;
15219         if (moveList[target - 1][1] == '@') {
15220             if (appData.highlightLastMove) {
15221                 SetHighlights(-1, -1, toX, toY);
15222             }
15223         } else {
15224             fromX = moveList[target - 1][0] - AAA;
15225             fromY = moveList[target - 1][1] - ONE;
15226             if (target == currentMove + 1) {
15227                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15228             }
15229             if (appData.highlightLastMove) {
15230                 SetHighlights(fromX, fromY, toX, toY);
15231             }
15232         }
15233     }
15234     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15235         gameMode == Training || gameMode == PlayFromGameFile ||
15236         gameMode == AnalyzeFile) {
15237         while (currentMove < target) {
15238             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15239             SendMoveToProgram(currentMove++, &first);
15240         }
15241     } else {
15242         currentMove = target;
15243     }
15244
15245     if (gameMode == EditGame || gameMode == EndOfGame) {
15246         whiteTimeRemaining = timeRemaining[0][currentMove];
15247         blackTimeRemaining = timeRemaining[1][currentMove];
15248     }
15249     DisplayBothClocks();
15250     DisplayMove(currentMove - 1);
15251     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15252     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15253     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15254         DisplayComment(currentMove - 1, commentList[currentMove]);
15255     }
15256     ClearMap(); // [HGM] exclude: invalidate map
15257 }
15258
15259
15260 void
15261 ForwardEvent ()
15262 {
15263     if (gameMode == IcsExamining && !pausing) {
15264         SendToICS(ics_prefix);
15265         SendToICS("forward\n");
15266     } else {
15267         ForwardInner(currentMove + 1);
15268     }
15269 }
15270
15271 void
15272 ToEndEvent ()
15273 {
15274     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15275         /* to optimze, we temporarily turn off analysis mode while we feed
15276          * the remaining moves to the engine. Otherwise we get analysis output
15277          * after each move.
15278          */
15279         if (first.analysisSupport) {
15280           SendToProgram("exit\nforce\n", &first);
15281           first.analyzing = FALSE;
15282         }
15283     }
15284
15285     if (gameMode == IcsExamining && !pausing) {
15286         SendToICS(ics_prefix);
15287         SendToICS("forward 999999\n");
15288     } else {
15289         ForwardInner(forwardMostMove);
15290     }
15291
15292     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15293         /* we have fed all the moves, so reactivate analysis mode */
15294         SendToProgram("analyze\n", &first);
15295         first.analyzing = TRUE;
15296         /*first.maybeThinking = TRUE;*/
15297         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15298     }
15299 }
15300
15301 void
15302 BackwardInner (int target)
15303 {
15304     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15305
15306     if (appData.debugMode)
15307         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15308                 target, currentMove, forwardMostMove);
15309
15310     if (gameMode == EditPosition) return;
15311     seekGraphUp = FALSE;
15312     MarkTargetSquares(1);
15313     if (currentMove <= backwardMostMove) {
15314         ClearHighlights();
15315         DrawPosition(full_redraw, boards[currentMove]);
15316         return;
15317     }
15318     if (gameMode == PlayFromGameFile && !pausing)
15319       PauseEvent();
15320
15321     if (moveList[target][0]) {
15322         int fromX, fromY, toX, toY;
15323         toX = moveList[target][2] - AAA;
15324         toY = moveList[target][3] - ONE;
15325         if (moveList[target][1] == '@') {
15326             if (appData.highlightLastMove) {
15327                 SetHighlights(-1, -1, toX, toY);
15328             }
15329         } else {
15330             fromX = moveList[target][0] - AAA;
15331             fromY = moveList[target][1] - ONE;
15332             if (target == currentMove - 1) {
15333                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15334             }
15335             if (appData.highlightLastMove) {
15336                 SetHighlights(fromX, fromY, toX, toY);
15337             }
15338         }
15339     }
15340     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15341         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15342         while (currentMove > target) {
15343             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15344                 // null move cannot be undone. Reload program with move history before it.
15345                 int i;
15346                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15347                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15348                 }
15349                 SendBoard(&first, i);
15350               if(second.analyzing) SendBoard(&second, i);
15351                 for(currentMove=i; currentMove<target; currentMove++) {
15352                     SendMoveToProgram(currentMove, &first);
15353                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15354                 }
15355                 break;
15356             }
15357             SendToBoth("undo\n");
15358             currentMove--;
15359         }
15360     } else {
15361         currentMove = target;
15362     }
15363
15364     if (gameMode == EditGame || gameMode == EndOfGame) {
15365         whiteTimeRemaining = timeRemaining[0][currentMove];
15366         blackTimeRemaining = timeRemaining[1][currentMove];
15367     }
15368     DisplayBothClocks();
15369     DisplayMove(currentMove - 1);
15370     DrawPosition(full_redraw, boards[currentMove]);
15371     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15372     // [HGM] PV info: routine tests if comment empty
15373     DisplayComment(currentMove - 1, commentList[currentMove]);
15374     ClearMap(); // [HGM] exclude: invalidate map
15375 }
15376
15377 void
15378 BackwardEvent ()
15379 {
15380     if (gameMode == IcsExamining && !pausing) {
15381         SendToICS(ics_prefix);
15382         SendToICS("backward\n");
15383     } else {
15384         BackwardInner(currentMove - 1);
15385     }
15386 }
15387
15388 void
15389 ToStartEvent ()
15390 {
15391     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15392         /* to optimize, we temporarily turn off analysis mode while we undo
15393          * all the moves. Otherwise we get analysis output after each undo.
15394          */
15395         if (first.analysisSupport) {
15396           SendToProgram("exit\nforce\n", &first);
15397           first.analyzing = FALSE;
15398         }
15399     }
15400
15401     if (gameMode == IcsExamining && !pausing) {
15402         SendToICS(ics_prefix);
15403         SendToICS("backward 999999\n");
15404     } else {
15405         BackwardInner(backwardMostMove);
15406     }
15407
15408     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15409         /* we have fed all the moves, so reactivate analysis mode */
15410         SendToProgram("analyze\n", &first);
15411         first.analyzing = TRUE;
15412         /*first.maybeThinking = TRUE;*/
15413         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15414     }
15415 }
15416
15417 void
15418 ToNrEvent (int to)
15419 {
15420   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15421   if (to >= forwardMostMove) to = forwardMostMove;
15422   if (to <= backwardMostMove) to = backwardMostMove;
15423   if (to < currentMove) {
15424     BackwardInner(to);
15425   } else {
15426     ForwardInner(to);
15427   }
15428 }
15429
15430 void
15431 RevertEvent (Boolean annotate)
15432 {
15433     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15434         return;
15435     }
15436     if (gameMode != IcsExamining) {
15437         DisplayError(_("You are not examining a game"), 0);
15438         return;
15439     }
15440     if (pausing) {
15441         DisplayError(_("You can't revert while pausing"), 0);
15442         return;
15443     }
15444     SendToICS(ics_prefix);
15445     SendToICS("revert\n");
15446 }
15447
15448 void
15449 RetractMoveEvent ()
15450 {
15451     switch (gameMode) {
15452       case MachinePlaysWhite:
15453       case MachinePlaysBlack:
15454         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15455             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15456             return;
15457         }
15458         if (forwardMostMove < 2) return;
15459         currentMove = forwardMostMove = forwardMostMove - 2;
15460         whiteTimeRemaining = timeRemaining[0][currentMove];
15461         blackTimeRemaining = timeRemaining[1][currentMove];
15462         DisplayBothClocks();
15463         DisplayMove(currentMove - 1);
15464         ClearHighlights();/*!! could figure this out*/
15465         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15466         SendToProgram("remove\n", &first);
15467         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15468         break;
15469
15470       case BeginningOfGame:
15471       default:
15472         break;
15473
15474       case IcsPlayingWhite:
15475       case IcsPlayingBlack:
15476         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15477             SendToICS(ics_prefix);
15478             SendToICS("takeback 2\n");
15479         } else {
15480             SendToICS(ics_prefix);
15481             SendToICS("takeback 1\n");
15482         }
15483         break;
15484     }
15485 }
15486
15487 void
15488 MoveNowEvent ()
15489 {
15490     ChessProgramState *cps;
15491
15492     switch (gameMode) {
15493       case MachinePlaysWhite:
15494         if (!WhiteOnMove(forwardMostMove)) {
15495             DisplayError(_("It is your turn"), 0);
15496             return;
15497         }
15498         cps = &first;
15499         break;
15500       case MachinePlaysBlack:
15501         if (WhiteOnMove(forwardMostMove)) {
15502             DisplayError(_("It is your turn"), 0);
15503             return;
15504         }
15505         cps = &first;
15506         break;
15507       case TwoMachinesPlay:
15508         if (WhiteOnMove(forwardMostMove) ==
15509             (first.twoMachinesColor[0] == 'w')) {
15510             cps = &first;
15511         } else {
15512             cps = &second;
15513         }
15514         break;
15515       case BeginningOfGame:
15516       default:
15517         return;
15518     }
15519     SendToProgram("?\n", cps);
15520 }
15521
15522 void
15523 TruncateGameEvent ()
15524 {
15525     EditGameEvent();
15526     if (gameMode != EditGame) return;
15527     TruncateGame();
15528 }
15529
15530 void
15531 TruncateGame ()
15532 {
15533     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15534     if (forwardMostMove > currentMove) {
15535         if (gameInfo.resultDetails != NULL) {
15536             free(gameInfo.resultDetails);
15537             gameInfo.resultDetails = NULL;
15538             gameInfo.result = GameUnfinished;
15539         }
15540         forwardMostMove = currentMove;
15541         HistorySet(parseList, backwardMostMove, forwardMostMove,
15542                    currentMove-1);
15543     }
15544 }
15545
15546 void
15547 HintEvent ()
15548 {
15549     if (appData.noChessProgram) return;
15550     switch (gameMode) {
15551       case MachinePlaysWhite:
15552         if (WhiteOnMove(forwardMostMove)) {
15553             DisplayError(_("Wait until your turn."), 0);
15554             return;
15555         }
15556         break;
15557       case BeginningOfGame:
15558       case MachinePlaysBlack:
15559         if (!WhiteOnMove(forwardMostMove)) {
15560             DisplayError(_("Wait until your turn."), 0);
15561             return;
15562         }
15563         break;
15564       default:
15565         DisplayError(_("No hint available"), 0);
15566         return;
15567     }
15568     SendToProgram("hint\n", &first);
15569     hintRequested = TRUE;
15570 }
15571
15572 void
15573 CreateBookEvent ()
15574 {
15575     ListGame * lg = (ListGame *) gameList.head;
15576     FILE *f, *g;
15577     int nItem;
15578     static int secondTime = FALSE;
15579
15580     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15581         DisplayError(_("Game list not loaded or empty"), 0);
15582         return;
15583     }
15584
15585     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15586         fclose(g);
15587         secondTime++;
15588         DisplayNote(_("Book file exists! Try again for overwrite."));
15589         return;
15590     }
15591
15592     creatingBook = TRUE;
15593     secondTime = FALSE;
15594
15595     /* Get list size */
15596     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15597         LoadGame(f, nItem, "", TRUE);
15598         AddGameToBook(TRUE);
15599         lg = (ListGame *) lg->node.succ;
15600     }
15601
15602     creatingBook = FALSE;
15603     FlushBook();
15604 }
15605
15606 void
15607 BookEvent ()
15608 {
15609     if (appData.noChessProgram) return;
15610     switch (gameMode) {
15611       case MachinePlaysWhite:
15612         if (WhiteOnMove(forwardMostMove)) {
15613             DisplayError(_("Wait until your turn."), 0);
15614             return;
15615         }
15616         break;
15617       case BeginningOfGame:
15618       case MachinePlaysBlack:
15619         if (!WhiteOnMove(forwardMostMove)) {
15620             DisplayError(_("Wait until your turn."), 0);
15621             return;
15622         }
15623         break;
15624       case EditPosition:
15625         EditPositionDone(TRUE);
15626         break;
15627       case TwoMachinesPlay:
15628         return;
15629       default:
15630         break;
15631     }
15632     SendToProgram("bk\n", &first);
15633     bookOutput[0] = NULLCHAR;
15634     bookRequested = TRUE;
15635 }
15636
15637 void
15638 AboutGameEvent ()
15639 {
15640     char *tags = PGNTags(&gameInfo);
15641     TagsPopUp(tags, CmailMsg());
15642     free(tags);
15643 }
15644
15645 /* end button procedures */
15646
15647 void
15648 PrintPosition (FILE *fp, int move)
15649 {
15650     int i, j;
15651
15652     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15653         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15654             char c = PieceToChar(boards[move][i][j]);
15655             fputc(c == 'x' ? '.' : c, fp);
15656             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15657         }
15658     }
15659     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15660       fprintf(fp, "white to play\n");
15661     else
15662       fprintf(fp, "black to play\n");
15663 }
15664
15665 void
15666 PrintOpponents (FILE *fp)
15667 {
15668     if (gameInfo.white != NULL) {
15669         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15670     } else {
15671         fprintf(fp, "\n");
15672     }
15673 }
15674
15675 /* Find last component of program's own name, using some heuristics */
15676 void
15677 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15678 {
15679     char *p, *q, c;
15680     int local = (strcmp(host, "localhost") == 0);
15681     while (!local && (p = strchr(prog, ';')) != NULL) {
15682         p++;
15683         while (*p == ' ') p++;
15684         prog = p;
15685     }
15686     if (*prog == '"' || *prog == '\'') {
15687         q = strchr(prog + 1, *prog);
15688     } else {
15689         q = strchr(prog, ' ');
15690     }
15691     if (q == NULL) q = prog + strlen(prog);
15692     p = q;
15693     while (p >= prog && *p != '/' && *p != '\\') p--;
15694     p++;
15695     if(p == prog && *p == '"') p++;
15696     c = *q; *q = 0;
15697     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15698     memcpy(buf, p, q - p);
15699     buf[q - p] = NULLCHAR;
15700     if (!local) {
15701         strcat(buf, "@");
15702         strcat(buf, host);
15703     }
15704 }
15705
15706 char *
15707 TimeControlTagValue ()
15708 {
15709     char buf[MSG_SIZ];
15710     if (!appData.clockMode) {
15711       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15712     } else if (movesPerSession > 0) {
15713       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15714     } else if (timeIncrement == 0) {
15715       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15716     } else {
15717       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15718     }
15719     return StrSave(buf);
15720 }
15721
15722 void
15723 SetGameInfo ()
15724 {
15725     /* This routine is used only for certain modes */
15726     VariantClass v = gameInfo.variant;
15727     ChessMove r = GameUnfinished;
15728     char *p = NULL;
15729
15730     if(keepInfo) return;
15731
15732     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15733         r = gameInfo.result;
15734         p = gameInfo.resultDetails;
15735         gameInfo.resultDetails = NULL;
15736     }
15737     ClearGameInfo(&gameInfo);
15738     gameInfo.variant = v;
15739
15740     switch (gameMode) {
15741       case MachinePlaysWhite:
15742         gameInfo.event = StrSave( appData.pgnEventHeader );
15743         gameInfo.site = StrSave(HostName());
15744         gameInfo.date = PGNDate();
15745         gameInfo.round = StrSave("-");
15746         gameInfo.white = StrSave(first.tidy);
15747         gameInfo.black = StrSave(UserName());
15748         gameInfo.timeControl = TimeControlTagValue();
15749         break;
15750
15751       case MachinePlaysBlack:
15752         gameInfo.event = StrSave( appData.pgnEventHeader );
15753         gameInfo.site = StrSave(HostName());
15754         gameInfo.date = PGNDate();
15755         gameInfo.round = StrSave("-");
15756         gameInfo.white = StrSave(UserName());
15757         gameInfo.black = StrSave(first.tidy);
15758         gameInfo.timeControl = TimeControlTagValue();
15759         break;
15760
15761       case TwoMachinesPlay:
15762         gameInfo.event = StrSave( appData.pgnEventHeader );
15763         gameInfo.site = StrSave(HostName());
15764         gameInfo.date = PGNDate();
15765         if (roundNr > 0) {
15766             char buf[MSG_SIZ];
15767             snprintf(buf, MSG_SIZ, "%d", roundNr);
15768             gameInfo.round = StrSave(buf);
15769         } else {
15770             gameInfo.round = StrSave("-");
15771         }
15772         if (first.twoMachinesColor[0] == 'w') {
15773             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15774             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15775         } else {
15776             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15777             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15778         }
15779         gameInfo.timeControl = TimeControlTagValue();
15780         break;
15781
15782       case EditGame:
15783         gameInfo.event = StrSave("Edited game");
15784         gameInfo.site = StrSave(HostName());
15785         gameInfo.date = PGNDate();
15786         gameInfo.round = StrSave("-");
15787         gameInfo.white = StrSave("-");
15788         gameInfo.black = StrSave("-");
15789         gameInfo.result = r;
15790         gameInfo.resultDetails = p;
15791         break;
15792
15793       case EditPosition:
15794         gameInfo.event = StrSave("Edited position");
15795         gameInfo.site = StrSave(HostName());
15796         gameInfo.date = PGNDate();
15797         gameInfo.round = StrSave("-");
15798         gameInfo.white = StrSave("-");
15799         gameInfo.black = StrSave("-");
15800         break;
15801
15802       case IcsPlayingWhite:
15803       case IcsPlayingBlack:
15804       case IcsObserving:
15805       case IcsExamining:
15806         break;
15807
15808       case PlayFromGameFile:
15809         gameInfo.event = StrSave("Game from non-PGN file");
15810         gameInfo.site = StrSave(HostName());
15811         gameInfo.date = PGNDate();
15812         gameInfo.round = StrSave("-");
15813         gameInfo.white = StrSave("?");
15814         gameInfo.black = StrSave("?");
15815         break;
15816
15817       default:
15818         break;
15819     }
15820 }
15821
15822 void
15823 ReplaceComment (int index, char *text)
15824 {
15825     int len;
15826     char *p;
15827     float score;
15828
15829     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15830        pvInfoList[index-1].depth == len &&
15831        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15832        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15833     while (*text == '\n') text++;
15834     len = strlen(text);
15835     while (len > 0 && text[len - 1] == '\n') len--;
15836
15837     if (commentList[index] != NULL)
15838       free(commentList[index]);
15839
15840     if (len == 0) {
15841         commentList[index] = NULL;
15842         return;
15843     }
15844   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15845       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15846       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15847     commentList[index] = (char *) malloc(len + 2);
15848     strncpy(commentList[index], text, len);
15849     commentList[index][len] = '\n';
15850     commentList[index][len + 1] = NULLCHAR;
15851   } else {
15852     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15853     char *p;
15854     commentList[index] = (char *) malloc(len + 7);
15855     safeStrCpy(commentList[index], "{\n", 3);
15856     safeStrCpy(commentList[index]+2, text, len+1);
15857     commentList[index][len+2] = NULLCHAR;
15858     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15859     strcat(commentList[index], "\n}\n");
15860   }
15861 }
15862
15863 void
15864 CrushCRs (char *text)
15865 {
15866   char *p = text;
15867   char *q = text;
15868   char ch;
15869
15870   do {
15871     ch = *p++;
15872     if (ch == '\r') continue;
15873     *q++ = ch;
15874   } while (ch != '\0');
15875 }
15876
15877 void
15878 AppendComment (int index, char *text, Boolean addBraces)
15879 /* addBraces  tells if we should add {} */
15880 {
15881     int oldlen, len;
15882     char *old;
15883
15884 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15885     if(addBraces == 3) addBraces = 0; else // force appending literally
15886     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15887
15888     CrushCRs(text);
15889     while (*text == '\n') text++;
15890     len = strlen(text);
15891     while (len > 0 && text[len - 1] == '\n') len--;
15892     text[len] = NULLCHAR;
15893
15894     if (len == 0) return;
15895
15896     if (commentList[index] != NULL) {
15897       Boolean addClosingBrace = addBraces;
15898         old = commentList[index];
15899         oldlen = strlen(old);
15900         while(commentList[index][oldlen-1] ==  '\n')
15901           commentList[index][--oldlen] = NULLCHAR;
15902         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15903         safeStrCpy(commentList[index], old, oldlen + len + 6);
15904         free(old);
15905         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15906         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15907           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15908           while (*text == '\n') { text++; len--; }
15909           commentList[index][--oldlen] = NULLCHAR;
15910       }
15911         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15912         else          strcat(commentList[index], "\n");
15913         strcat(commentList[index], text);
15914         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15915         else          strcat(commentList[index], "\n");
15916     } else {
15917         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15918         if(addBraces)
15919           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15920         else commentList[index][0] = NULLCHAR;
15921         strcat(commentList[index], text);
15922         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15923         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15924     }
15925 }
15926
15927 static char *
15928 FindStr (char * text, char * sub_text)
15929 {
15930     char * result = strstr( text, sub_text );
15931
15932     if( result != NULL ) {
15933         result += strlen( sub_text );
15934     }
15935
15936     return result;
15937 }
15938
15939 /* [AS] Try to extract PV info from PGN comment */
15940 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15941 char *
15942 GetInfoFromComment (int index, char * text)
15943 {
15944     char * sep = text, *p;
15945
15946     if( text != NULL && index > 0 ) {
15947         int score = 0;
15948         int depth = 0;
15949         int time = -1, sec = 0, deci;
15950         char * s_eval = FindStr( text, "[%eval " );
15951         char * s_emt = FindStr( text, "[%emt " );
15952 #if 0
15953         if( s_eval != NULL || s_emt != NULL ) {
15954 #else
15955         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15956 #endif
15957             /* New style */
15958             char delim;
15959
15960             if( s_eval != NULL ) {
15961                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15962                     return text;
15963                 }
15964
15965                 if( delim != ']' ) {
15966                     return text;
15967                 }
15968             }
15969
15970             if( s_emt != NULL ) {
15971             }
15972                 return text;
15973         }
15974         else {
15975             /* We expect something like: [+|-]nnn.nn/dd */
15976             int score_lo = 0;
15977
15978             if(*text != '{') return text; // [HGM] braces: must be normal comment
15979
15980             sep = strchr( text, '/' );
15981             if( sep == NULL || sep < (text+4) ) {
15982                 return text;
15983             }
15984
15985             p = text;
15986             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15987             if(p[1] == '(') { // comment starts with PV
15988                p = strchr(p, ')'); // locate end of PV
15989                if(p == NULL || sep < p+5) return text;
15990                // at this point we have something like "{(.*) +0.23/6 ..."
15991                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15992                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15993                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15994             }
15995             time = -1; sec = -1; deci = -1;
15996             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15997                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15998                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15999                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16000                 return text;
16001             }
16002
16003             if( score_lo < 0 || score_lo >= 100 ) {
16004                 return text;
16005             }
16006
16007             if(sec >= 0) time = 600*time + 10*sec; else
16008             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16009
16010             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16011
16012             /* [HGM] PV time: now locate end of PV info */
16013             while( *++sep >= '0' && *sep <= '9'); // strip depth
16014             if(time >= 0)
16015             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16016             if(sec >= 0)
16017             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16018             if(deci >= 0)
16019             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16020             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16021         }
16022
16023         if( depth <= 0 ) {
16024             return text;
16025         }
16026
16027         if( time < 0 ) {
16028             time = -1;
16029         }
16030
16031         pvInfoList[index-1].depth = depth;
16032         pvInfoList[index-1].score = score;
16033         pvInfoList[index-1].time  = 10*time; // centi-sec
16034         if(*sep == '}') *sep = 0; else *--sep = '{';
16035         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16036     }
16037     return sep;
16038 }
16039
16040 void
16041 SendToProgram (char *message, ChessProgramState *cps)
16042 {
16043     int count, outCount, error;
16044     char buf[MSG_SIZ];
16045
16046     if (cps->pr == NoProc) return;
16047     Attention(cps);
16048
16049     if (appData.debugMode) {
16050         TimeMark now;
16051         GetTimeMark(&now);
16052         fprintf(debugFP, "%ld >%-6s: %s",
16053                 SubtractTimeMarks(&now, &programStartTime),
16054                 cps->which, message);
16055         if(serverFP)
16056             fprintf(serverFP, "%ld >%-6s: %s",
16057                 SubtractTimeMarks(&now, &programStartTime),
16058                 cps->which, message), fflush(serverFP);
16059     }
16060
16061     count = strlen(message);
16062     outCount = OutputToProcess(cps->pr, message, count, &error);
16063     if (outCount < count && !exiting
16064                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16065       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16066       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16067         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16068             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16069                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16070                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16071                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16072             } else {
16073                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16074                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16075                 gameInfo.result = res;
16076             }
16077             gameInfo.resultDetails = StrSave(buf);
16078         }
16079         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16080         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16081     }
16082 }
16083
16084 void
16085 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16086 {
16087     char *end_str;
16088     char buf[MSG_SIZ];
16089     ChessProgramState *cps = (ChessProgramState *)closure;
16090
16091     if (isr != cps->isr) return; /* Killed intentionally */
16092     if (count <= 0) {
16093         if (count == 0) {
16094             RemoveInputSource(cps->isr);
16095             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16096                     _(cps->which), cps->program);
16097             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16098             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16099                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16100                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16101                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16102                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16103                 } else {
16104                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16105                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16106                     gameInfo.result = res;
16107                 }
16108                 gameInfo.resultDetails = StrSave(buf);
16109             }
16110             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16111             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16112         } else {
16113             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16114                     _(cps->which), cps->program);
16115             RemoveInputSource(cps->isr);
16116
16117             /* [AS] Program is misbehaving badly... kill it */
16118             if( count == -2 ) {
16119                 DestroyChildProcess( cps->pr, 9 );
16120                 cps->pr = NoProc;
16121             }
16122
16123             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16124         }
16125         return;
16126     }
16127
16128     if ((end_str = strchr(message, '\r')) != NULL)
16129       *end_str = NULLCHAR;
16130     if ((end_str = strchr(message, '\n')) != NULL)
16131       *end_str = NULLCHAR;
16132
16133     if (appData.debugMode) {
16134         TimeMark now; int print = 1;
16135         char *quote = ""; char c; int i;
16136
16137         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16138                 char start = message[0];
16139                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16140                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16141                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16142                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16143                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16144                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16145                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16146                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16147                    sscanf(message, "hint: %c", &c)!=1 &&
16148                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16149                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16150                     print = (appData.engineComments >= 2);
16151                 }
16152                 message[0] = start; // restore original message
16153         }
16154         if(print) {
16155                 GetTimeMark(&now);
16156                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16157                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16158                         quote,
16159                         message);
16160                 if(serverFP)
16161                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16162                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16163                         quote,
16164                         message), fflush(serverFP);
16165         }
16166     }
16167
16168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16169     if (appData.icsEngineAnalyze) {
16170         if (strstr(message, "whisper") != NULL ||
16171              strstr(message, "kibitz") != NULL ||
16172             strstr(message, "tellics") != NULL) return;
16173     }
16174
16175     HandleMachineMove(message, cps);
16176 }
16177
16178
16179 void
16180 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16181 {
16182     char buf[MSG_SIZ];
16183     int seconds;
16184
16185     if( timeControl_2 > 0 ) {
16186         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16187             tc = timeControl_2;
16188         }
16189     }
16190     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16191     inc /= cps->timeOdds;
16192     st  /= cps->timeOdds;
16193
16194     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16195
16196     if (st > 0) {
16197       /* Set exact time per move, normally using st command */
16198       if (cps->stKludge) {
16199         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16200         seconds = st % 60;
16201         if (seconds == 0) {
16202           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16203         } else {
16204           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16205         }
16206       } else {
16207         snprintf(buf, MSG_SIZ, "st %d\n", st);
16208       }
16209     } else {
16210       /* Set conventional or incremental time control, using level command */
16211       if (seconds == 0) {
16212         /* Note old gnuchess bug -- minutes:seconds used to not work.
16213            Fixed in later versions, but still avoid :seconds
16214            when seconds is 0. */
16215         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16216       } else {
16217         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16218                  seconds, inc/1000.);
16219       }
16220     }
16221     SendToProgram(buf, cps);
16222
16223     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16224     /* Orthogonally, limit search to given depth */
16225     if (sd > 0) {
16226       if (cps->sdKludge) {
16227         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16228       } else {
16229         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16230       }
16231       SendToProgram(buf, cps);
16232     }
16233
16234     if(cps->nps >= 0) { /* [HGM] nps */
16235         if(cps->supportsNPS == FALSE)
16236           cps->nps = -1; // don't use if engine explicitly says not supported!
16237         else {
16238           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16239           SendToProgram(buf, cps);
16240         }
16241     }
16242 }
16243
16244 ChessProgramState *
16245 WhitePlayer ()
16246 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16247 {
16248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16249        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16250         return &second;
16251     return &first;
16252 }
16253
16254 void
16255 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16256 {
16257     char message[MSG_SIZ];
16258     long time, otime;
16259
16260     /* Note: this routine must be called when the clocks are stopped
16261        or when they have *just* been set or switched; otherwise
16262        it will be off by the time since the current tick started.
16263     */
16264     if (machineWhite) {
16265         time = whiteTimeRemaining / 10;
16266         otime = blackTimeRemaining / 10;
16267     } else {
16268         time = blackTimeRemaining / 10;
16269         otime = whiteTimeRemaining / 10;
16270     }
16271     /* [HGM] translate opponent's time by time-odds factor */
16272     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16273
16274     if (time <= 0) time = 1;
16275     if (otime <= 0) otime = 1;
16276
16277     snprintf(message, MSG_SIZ, "time %ld\n", time);
16278     SendToProgram(message, cps);
16279
16280     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16281     SendToProgram(message, cps);
16282 }
16283
16284 char *
16285 EngineDefinedVariant (ChessProgramState *cps, int n)
16286 {   // return name of n-th unknown variant that engine supports
16287     static char buf[MSG_SIZ];
16288     char *p, *s = cps->variants;
16289     if(!s) return NULL;
16290     do { // parse string from variants feature
16291       VariantClass v;
16292         p = strchr(s, ',');
16293         if(p) *p = NULLCHAR;
16294       v = StringToVariant(s);
16295       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16296         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16297             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16298         }
16299         if(p) *p++ = ',';
16300         if(n < 0) return buf;
16301     } while(s = p);
16302     return NULL;
16303 }
16304
16305 int
16306 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16307 {
16308   char buf[MSG_SIZ];
16309   int len = strlen(name);
16310   int val;
16311
16312   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16313     (*p) += len + 1;
16314     sscanf(*p, "%d", &val);
16315     *loc = (val != 0);
16316     while (**p && **p != ' ')
16317       (*p)++;
16318     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16319     SendToProgram(buf, cps);
16320     return TRUE;
16321   }
16322   return FALSE;
16323 }
16324
16325 int
16326 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16327 {
16328   char buf[MSG_SIZ];
16329   int len = strlen(name);
16330   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16331     (*p) += len + 1;
16332     sscanf(*p, "%d", loc);
16333     while (**p && **p != ' ') (*p)++;
16334     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16335     SendToProgram(buf, cps);
16336     return TRUE;
16337   }
16338   return FALSE;
16339 }
16340
16341 int
16342 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16343 {
16344   char buf[MSG_SIZ];
16345   int len = strlen(name);
16346   if (strncmp((*p), name, len) == 0
16347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16348     (*p) += len + 2;
16349     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16350     sscanf(*p, "%[^\"]", *loc);
16351     while (**p && **p != '\"') (*p)++;
16352     if (**p == '\"') (*p)++;
16353     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16354     SendToProgram(buf, cps);
16355     return TRUE;
16356   }
16357   return FALSE;
16358 }
16359
16360 int
16361 ParseOption (Option *opt, ChessProgramState *cps)
16362 // [HGM] options: process the string that defines an engine option, and determine
16363 // name, type, default value, and allowed value range
16364 {
16365         char *p, *q, buf[MSG_SIZ];
16366         int n, min = (-1)<<31, max = 1<<31, def;
16367
16368         if(p = strstr(opt->name, " -spin ")) {
16369             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16370             if(max < min) max = min; // enforce consistency
16371             if(def < min) def = min;
16372             if(def > max) def = max;
16373             opt->value = def;
16374             opt->min = min;
16375             opt->max = max;
16376             opt->type = Spin;
16377         } else if((p = strstr(opt->name, " -slider "))) {
16378             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16379             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16380             if(max < min) max = min; // enforce consistency
16381             if(def < min) def = min;
16382             if(def > max) def = max;
16383             opt->value = def;
16384             opt->min = min;
16385             opt->max = max;
16386             opt->type = Spin; // Slider;
16387         } else if((p = strstr(opt->name, " -string "))) {
16388             opt->textValue = p+9;
16389             opt->type = TextBox;
16390         } else if((p = strstr(opt->name, " -file "))) {
16391             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16392             opt->textValue = p+7;
16393             opt->type = FileName; // FileName;
16394         } else if((p = strstr(opt->name, " -path "))) {
16395             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16396             opt->textValue = p+7;
16397             opt->type = PathName; // PathName;
16398         } else if(p = strstr(opt->name, " -check ")) {
16399             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16400             opt->value = (def != 0);
16401             opt->type = CheckBox;
16402         } else if(p = strstr(opt->name, " -combo ")) {
16403             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16404             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16405             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16406             opt->value = n = 0;
16407             while(q = StrStr(q, " /// ")) {
16408                 n++; *q = 0;    // count choices, and null-terminate each of them
16409                 q += 5;
16410                 if(*q == '*') { // remember default, which is marked with * prefix
16411                     q++;
16412                     opt->value = n;
16413                 }
16414                 cps->comboList[cps->comboCnt++] = q;
16415             }
16416             cps->comboList[cps->comboCnt++] = NULL;
16417             opt->max = n + 1;
16418             opt->type = ComboBox;
16419         } else if(p = strstr(opt->name, " -button")) {
16420             opt->type = Button;
16421         } else if(p = strstr(opt->name, " -save")) {
16422             opt->type = SaveButton;
16423         } else return FALSE;
16424         *p = 0; // terminate option name
16425         // now look if the command-line options define a setting for this engine option.
16426         if(cps->optionSettings && cps->optionSettings[0])
16427             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16428         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16429           snprintf(buf, MSG_SIZ, "option %s", p);
16430                 if(p = strstr(buf, ",")) *p = 0;
16431                 if(q = strchr(buf, '=')) switch(opt->type) {
16432                     case ComboBox:
16433                         for(n=0; n<opt->max; n++)
16434                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16435                         break;
16436                     case TextBox:
16437                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16438                         break;
16439                     case Spin:
16440                     case CheckBox:
16441                         opt->value = atoi(q+1);
16442                     default:
16443                         break;
16444                 }
16445                 strcat(buf, "\n");
16446                 SendToProgram(buf, cps);
16447         }
16448         return TRUE;
16449 }
16450
16451 void
16452 FeatureDone (ChessProgramState *cps, int val)
16453 {
16454   DelayedEventCallback cb = GetDelayedEvent();
16455   if ((cb == InitBackEnd3 && cps == &first) ||
16456       (cb == SettingsMenuIfReady && cps == &second) ||
16457       (cb == LoadEngine) ||
16458       (cb == TwoMachinesEventIfReady)) {
16459     CancelDelayedEvent();
16460     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16461   }
16462   cps->initDone = val;
16463   if(val) cps->reload = FALSE;
16464 }
16465
16466 /* Parse feature command from engine */
16467 void
16468 ParseFeatures (char *args, ChessProgramState *cps)
16469 {
16470   char *p = args;
16471   char *q = NULL;
16472   int val;
16473   char buf[MSG_SIZ];
16474
16475   for (;;) {
16476     while (*p == ' ') p++;
16477     if (*p == NULLCHAR) return;
16478
16479     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16480     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16481     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16482     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16483     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16484     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16485     if (BoolFeature(&p, "reuse", &val, cps)) {
16486       /* Engine can disable reuse, but can't enable it if user said no */
16487       if (!val) cps->reuse = FALSE;
16488       continue;
16489     }
16490     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16491     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16492       if (gameMode == TwoMachinesPlay) {
16493         DisplayTwoMachinesTitle();
16494       } else {
16495         DisplayTitle("");
16496       }
16497       continue;
16498     }
16499     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16500     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16501     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16502     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16503     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16504     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16505     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16506     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16507     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16508     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16509     if (IntFeature(&p, "done", &val, cps)) {
16510       FeatureDone(cps, val);
16511       continue;
16512     }
16513     /* Added by Tord: */
16514     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16515     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16516     /* End of additions by Tord */
16517
16518     /* [HGM] added features: */
16519     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16520     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16521     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16522     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16523     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16524     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16525     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16526     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16527         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16528         FREE(cps->option[cps->nrOptions].name);
16529         cps->option[cps->nrOptions].name = q; q = NULL;
16530         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16531           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16532             SendToProgram(buf, cps);
16533             continue;
16534         }
16535         if(cps->nrOptions >= MAX_OPTIONS) {
16536             cps->nrOptions--;
16537             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16538             DisplayError(buf, 0);
16539         }
16540         continue;
16541     }
16542     /* End of additions by HGM */
16543
16544     /* unknown feature: complain and skip */
16545     q = p;
16546     while (*q && *q != '=') q++;
16547     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16548     SendToProgram(buf, cps);
16549     p = q;
16550     if (*p == '=') {
16551       p++;
16552       if (*p == '\"') {
16553         p++;
16554         while (*p && *p != '\"') p++;
16555         if (*p == '\"') p++;
16556       } else {
16557         while (*p && *p != ' ') p++;
16558       }
16559     }
16560   }
16561
16562 }
16563
16564 void
16565 PeriodicUpdatesEvent (int newState)
16566 {
16567     if (newState == appData.periodicUpdates)
16568       return;
16569
16570     appData.periodicUpdates=newState;
16571
16572     /* Display type changes, so update it now */
16573 //    DisplayAnalysis();
16574
16575     /* Get the ball rolling again... */
16576     if (newState) {
16577         AnalysisPeriodicEvent(1);
16578         StartAnalysisClock();
16579     }
16580 }
16581
16582 void
16583 PonderNextMoveEvent (int newState)
16584 {
16585     if (newState == appData.ponderNextMove) return;
16586     if (gameMode == EditPosition) EditPositionDone(TRUE);
16587     if (newState) {
16588         SendToProgram("hard\n", &first);
16589         if (gameMode == TwoMachinesPlay) {
16590             SendToProgram("hard\n", &second);
16591         }
16592     } else {
16593         SendToProgram("easy\n", &first);
16594         thinkOutput[0] = NULLCHAR;
16595         if (gameMode == TwoMachinesPlay) {
16596             SendToProgram("easy\n", &second);
16597         }
16598     }
16599     appData.ponderNextMove = newState;
16600 }
16601
16602 void
16603 NewSettingEvent (int option, int *feature, char *command, int value)
16604 {
16605     char buf[MSG_SIZ];
16606
16607     if (gameMode == EditPosition) EditPositionDone(TRUE);
16608     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16609     if(feature == NULL || *feature) SendToProgram(buf, &first);
16610     if (gameMode == TwoMachinesPlay) {
16611         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16612     }
16613 }
16614
16615 void
16616 ShowThinkingEvent ()
16617 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16618 {
16619     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16620     int newState = appData.showThinking
16621         // [HGM] thinking: other features now need thinking output as well
16622         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16623
16624     if (oldState == newState) return;
16625     oldState = newState;
16626     if (gameMode == EditPosition) EditPositionDone(TRUE);
16627     if (oldState) {
16628         SendToProgram("post\n", &first);
16629         if (gameMode == TwoMachinesPlay) {
16630             SendToProgram("post\n", &second);
16631         }
16632     } else {
16633         SendToProgram("nopost\n", &first);
16634         thinkOutput[0] = NULLCHAR;
16635         if (gameMode == TwoMachinesPlay) {
16636             SendToProgram("nopost\n", &second);
16637         }
16638     }
16639 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16640 }
16641
16642 void
16643 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16644 {
16645   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16646   if (pr == NoProc) return;
16647   AskQuestion(title, question, replyPrefix, pr);
16648 }
16649
16650 void
16651 TypeInEvent (char firstChar)
16652 {
16653     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16654         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16655         gameMode == AnalyzeMode || gameMode == EditGame ||
16656         gameMode == EditPosition || gameMode == IcsExamining ||
16657         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16658         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16659                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16660                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16661         gameMode == Training) PopUpMoveDialog(firstChar);
16662 }
16663
16664 void
16665 TypeInDoneEvent (char *move)
16666 {
16667         Board board;
16668         int n, fromX, fromY, toX, toY;
16669         char promoChar;
16670         ChessMove moveType;
16671
16672         // [HGM] FENedit
16673         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16674                 EditPositionPasteFEN(move);
16675                 return;
16676         }
16677         // [HGM] movenum: allow move number to be typed in any mode
16678         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16679           ToNrEvent(2*n-1);
16680           return;
16681         }
16682         // undocumented kludge: allow command-line option to be typed in!
16683         // (potentially fatal, and does not implement the effect of the option.)
16684         // should only be used for options that are values on which future decisions will be made,
16685         // and definitely not on options that would be used during initialization.
16686         if(strstr(move, "!!! -") == move) {
16687             ParseArgsFromString(move+4);
16688             return;
16689         }
16690
16691       if (gameMode != EditGame && currentMove != forwardMostMove &&
16692         gameMode != Training) {
16693         DisplayMoveError(_("Displayed move is not current"));
16694       } else {
16695         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16696           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16697         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16698         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16699           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16700           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16701         } else {
16702           DisplayMoveError(_("Could not parse move"));
16703         }
16704       }
16705 }
16706
16707 void
16708 DisplayMove (int moveNumber)
16709 {
16710     char message[MSG_SIZ];
16711     char res[MSG_SIZ];
16712     char cpThinkOutput[MSG_SIZ];
16713
16714     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16715
16716     if (moveNumber == forwardMostMove - 1 ||
16717         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16718
16719         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16720
16721         if (strchr(cpThinkOutput, '\n')) {
16722             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16723         }
16724     } else {
16725         *cpThinkOutput = NULLCHAR;
16726     }
16727
16728     /* [AS] Hide thinking from human user */
16729     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16730         *cpThinkOutput = NULLCHAR;
16731         if( thinkOutput[0] != NULLCHAR ) {
16732             int i;
16733
16734             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16735                 cpThinkOutput[i] = '.';
16736             }
16737             cpThinkOutput[i] = NULLCHAR;
16738             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16739         }
16740     }
16741
16742     if (moveNumber == forwardMostMove - 1 &&
16743         gameInfo.resultDetails != NULL) {
16744         if (gameInfo.resultDetails[0] == NULLCHAR) {
16745           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16746         } else {
16747           snprintf(res, MSG_SIZ, " {%s} %s",
16748                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16749         }
16750     } else {
16751         res[0] = NULLCHAR;
16752     }
16753
16754     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16755         DisplayMessage(res, cpThinkOutput);
16756     } else {
16757       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16758                 WhiteOnMove(moveNumber) ? " " : ".. ",
16759                 parseList[moveNumber], res);
16760         DisplayMessage(message, cpThinkOutput);
16761     }
16762 }
16763
16764 void
16765 DisplayComment (int moveNumber, char *text)
16766 {
16767     char title[MSG_SIZ];
16768
16769     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16770       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16771     } else {
16772       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16773               WhiteOnMove(moveNumber) ? " " : ".. ",
16774               parseList[moveNumber]);
16775     }
16776     if (text != NULL && (appData.autoDisplayComment || commentUp))
16777         CommentPopUp(title, text);
16778 }
16779
16780 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16781  * might be busy thinking or pondering.  It can be omitted if your
16782  * gnuchess is configured to stop thinking immediately on any user
16783  * input.  However, that gnuchess feature depends on the FIONREAD
16784  * ioctl, which does not work properly on some flavors of Unix.
16785  */
16786 void
16787 Attention (ChessProgramState *cps)
16788 {
16789 #if ATTENTION
16790     if (!cps->useSigint) return;
16791     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16792     switch (gameMode) {
16793       case MachinePlaysWhite:
16794       case MachinePlaysBlack:
16795       case TwoMachinesPlay:
16796       case IcsPlayingWhite:
16797       case IcsPlayingBlack:
16798       case AnalyzeMode:
16799       case AnalyzeFile:
16800         /* Skip if we know it isn't thinking */
16801         if (!cps->maybeThinking) return;
16802         if (appData.debugMode)
16803           fprintf(debugFP, "Interrupting %s\n", cps->which);
16804         InterruptChildProcess(cps->pr);
16805         cps->maybeThinking = FALSE;
16806         break;
16807       default:
16808         break;
16809     }
16810 #endif /*ATTENTION*/
16811 }
16812
16813 int
16814 CheckFlags ()
16815 {
16816     if (whiteTimeRemaining <= 0) {
16817         if (!whiteFlag) {
16818             whiteFlag = TRUE;
16819             if (appData.icsActive) {
16820                 if (appData.autoCallFlag &&
16821                     gameMode == IcsPlayingBlack && !blackFlag) {
16822                   SendToICS(ics_prefix);
16823                   SendToICS("flag\n");
16824                 }
16825             } else {
16826                 if (blackFlag) {
16827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16828                 } else {
16829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16830                     if (appData.autoCallFlag) {
16831                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16832                         return TRUE;
16833                     }
16834                 }
16835             }
16836         }
16837     }
16838     if (blackTimeRemaining <= 0) {
16839         if (!blackFlag) {
16840             blackFlag = TRUE;
16841             if (appData.icsActive) {
16842                 if (appData.autoCallFlag &&
16843                     gameMode == IcsPlayingWhite && !whiteFlag) {
16844                   SendToICS(ics_prefix);
16845                   SendToICS("flag\n");
16846                 }
16847             } else {
16848                 if (whiteFlag) {
16849                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16850                 } else {
16851                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16852                     if (appData.autoCallFlag) {
16853                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16854                         return TRUE;
16855                     }
16856                 }
16857             }
16858         }
16859     }
16860     return FALSE;
16861 }
16862
16863 void
16864 CheckTimeControl ()
16865 {
16866     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16867         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16868
16869     /*
16870      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16871      */
16872     if ( !WhiteOnMove(forwardMostMove) ) {
16873         /* White made time control */
16874         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16875         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16876         /* [HGM] time odds: correct new time quota for time odds! */
16877                                             / WhitePlayer()->timeOdds;
16878         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16879     } else {
16880         lastBlack -= blackTimeRemaining;
16881         /* Black made time control */
16882         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16883                                             / WhitePlayer()->other->timeOdds;
16884         lastWhite = whiteTimeRemaining;
16885     }
16886 }
16887
16888 void
16889 DisplayBothClocks ()
16890 {
16891     int wom = gameMode == EditPosition ?
16892       !blackPlaysFirst : WhiteOnMove(currentMove);
16893     DisplayWhiteClock(whiteTimeRemaining, wom);
16894     DisplayBlackClock(blackTimeRemaining, !wom);
16895 }
16896
16897
16898 /* Timekeeping seems to be a portability nightmare.  I think everyone
16899    has ftime(), but I'm really not sure, so I'm including some ifdefs
16900    to use other calls if you don't.  Clocks will be less accurate if
16901    you have neither ftime nor gettimeofday.
16902 */
16903
16904 /* VS 2008 requires the #include outside of the function */
16905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16906 #include <sys/timeb.h>
16907 #endif
16908
16909 /* Get the current time as a TimeMark */
16910 void
16911 GetTimeMark (TimeMark *tm)
16912 {
16913 #if HAVE_GETTIMEOFDAY
16914
16915     struct timeval timeVal;
16916     struct timezone timeZone;
16917
16918     gettimeofday(&timeVal, &timeZone);
16919     tm->sec = (long) timeVal.tv_sec;
16920     tm->ms = (int) (timeVal.tv_usec / 1000L);
16921
16922 #else /*!HAVE_GETTIMEOFDAY*/
16923 #if HAVE_FTIME
16924
16925 // include <sys/timeb.h> / moved to just above start of function
16926     struct timeb timeB;
16927
16928     ftime(&timeB);
16929     tm->sec = (long) timeB.time;
16930     tm->ms = (int) timeB.millitm;
16931
16932 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16933     tm->sec = (long) time(NULL);
16934     tm->ms = 0;
16935 #endif
16936 #endif
16937 }
16938
16939 /* Return the difference in milliseconds between two
16940    time marks.  We assume the difference will fit in a long!
16941 */
16942 long
16943 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16944 {
16945     return 1000L*(tm2->sec - tm1->sec) +
16946            (long) (tm2->ms - tm1->ms);
16947 }
16948
16949
16950 /*
16951  * Code to manage the game clocks.
16952  *
16953  * In tournament play, black starts the clock and then white makes a move.
16954  * We give the human user a slight advantage if he is playing white---the
16955  * clocks don't run until he makes his first move, so it takes zero time.
16956  * Also, we don't account for network lag, so we could get out of sync
16957  * with GNU Chess's clock -- but then, referees are always right.
16958  */
16959
16960 static TimeMark tickStartTM;
16961 static long intendedTickLength;
16962
16963 long
16964 NextTickLength (long timeRemaining)
16965 {
16966     long nominalTickLength, nextTickLength;
16967
16968     if (timeRemaining > 0L && timeRemaining <= 10000L)
16969       nominalTickLength = 100L;
16970     else
16971       nominalTickLength = 1000L;
16972     nextTickLength = timeRemaining % nominalTickLength;
16973     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16974
16975     return nextTickLength;
16976 }
16977
16978 /* Adjust clock one minute up or down */
16979 void
16980 AdjustClock (Boolean which, int dir)
16981 {
16982     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16983     if(which) blackTimeRemaining += 60000*dir;
16984     else      whiteTimeRemaining += 60000*dir;
16985     DisplayBothClocks();
16986     adjustedClock = TRUE;
16987 }
16988
16989 /* Stop clocks and reset to a fresh time control */
16990 void
16991 ResetClocks ()
16992 {
16993     (void) StopClockTimer();
16994     if (appData.icsActive) {
16995         whiteTimeRemaining = blackTimeRemaining = 0;
16996     } else if (searchTime) {
16997         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16998         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16999     } else { /* [HGM] correct new time quote for time odds */
17000         whiteTC = blackTC = fullTimeControlString;
17001         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17002         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17003     }
17004     if (whiteFlag || blackFlag) {
17005         DisplayTitle("");
17006         whiteFlag = blackFlag = FALSE;
17007     }
17008     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17009     DisplayBothClocks();
17010     adjustedClock = FALSE;
17011 }
17012
17013 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17014
17015 /* Decrement running clock by amount of time that has passed */
17016 void
17017 DecrementClocks ()
17018 {
17019     long timeRemaining;
17020     long lastTickLength, fudge;
17021     TimeMark now;
17022
17023     if (!appData.clockMode) return;
17024     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17025
17026     GetTimeMark(&now);
17027
17028     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17029
17030     /* Fudge if we woke up a little too soon */
17031     fudge = intendedTickLength - lastTickLength;
17032     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17033
17034     if (WhiteOnMove(forwardMostMove)) {
17035         if(whiteNPS >= 0) lastTickLength = 0;
17036         timeRemaining = whiteTimeRemaining -= lastTickLength;
17037         if(timeRemaining < 0 && !appData.icsActive) {
17038             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17039             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17040                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17041                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17042             }
17043         }
17044         DisplayWhiteClock(whiteTimeRemaining - fudge,
17045                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17046     } else {
17047         if(blackNPS >= 0) lastTickLength = 0;
17048         timeRemaining = blackTimeRemaining -= lastTickLength;
17049         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17050             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17051             if(suddenDeath) {
17052                 blackStartMove = forwardMostMove;
17053                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17054             }
17055         }
17056         DisplayBlackClock(blackTimeRemaining - fudge,
17057                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17058     }
17059     if (CheckFlags()) return;
17060
17061     if(twoBoards) { // count down secondary board's clocks as well
17062         activePartnerTime -= lastTickLength;
17063         partnerUp = 1;
17064         if(activePartner == 'W')
17065             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17066         else
17067             DisplayBlackClock(activePartnerTime, TRUE);
17068         partnerUp = 0;
17069     }
17070
17071     tickStartTM = now;
17072     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17073     StartClockTimer(intendedTickLength);
17074
17075     /* if the time remaining has fallen below the alarm threshold, sound the
17076      * alarm. if the alarm has sounded and (due to a takeback or time control
17077      * with increment) the time remaining has increased to a level above the
17078      * threshold, reset the alarm so it can sound again.
17079      */
17080
17081     if (appData.icsActive && appData.icsAlarm) {
17082
17083         /* make sure we are dealing with the user's clock */
17084         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17085                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17086            )) return;
17087
17088         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17089             alarmSounded = FALSE;
17090         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17091             PlayAlarmSound();
17092             alarmSounded = TRUE;
17093         }
17094     }
17095 }
17096
17097
17098 /* A player has just moved, so stop the previously running
17099    clock and (if in clock mode) start the other one.
17100    We redisplay both clocks in case we're in ICS mode, because
17101    ICS gives us an update to both clocks after every move.
17102    Note that this routine is called *after* forwardMostMove
17103    is updated, so the last fractional tick must be subtracted
17104    from the color that is *not* on move now.
17105 */
17106 void
17107 SwitchClocks (int newMoveNr)
17108 {
17109     long lastTickLength;
17110     TimeMark now;
17111     int flagged = FALSE;
17112
17113     GetTimeMark(&now);
17114
17115     if (StopClockTimer() && appData.clockMode) {
17116         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17117         if (!WhiteOnMove(forwardMostMove)) {
17118             if(blackNPS >= 0) lastTickLength = 0;
17119             blackTimeRemaining -= lastTickLength;
17120            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17121 //         if(pvInfoList[forwardMostMove].time == -1)
17122                  pvInfoList[forwardMostMove].time =               // use GUI time
17123                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17124         } else {
17125            if(whiteNPS >= 0) lastTickLength = 0;
17126            whiteTimeRemaining -= lastTickLength;
17127            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17128 //         if(pvInfoList[forwardMostMove].time == -1)
17129                  pvInfoList[forwardMostMove].time =
17130                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17131         }
17132         flagged = CheckFlags();
17133     }
17134     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17135     CheckTimeControl();
17136
17137     if (flagged || !appData.clockMode) return;
17138
17139     switch (gameMode) {
17140       case MachinePlaysBlack:
17141       case MachinePlaysWhite:
17142       case BeginningOfGame:
17143         if (pausing) return;
17144         break;
17145
17146       case EditGame:
17147       case PlayFromGameFile:
17148       case IcsExamining:
17149         return;
17150
17151       default:
17152         break;
17153     }
17154
17155     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17156         if(WhiteOnMove(forwardMostMove))
17157              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17158         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17159     }
17160
17161     tickStartTM = now;
17162     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17163       whiteTimeRemaining : blackTimeRemaining);
17164     StartClockTimer(intendedTickLength);
17165 }
17166
17167
17168 /* Stop both clocks */
17169 void
17170 StopClocks ()
17171 {
17172     long lastTickLength;
17173     TimeMark now;
17174
17175     if (!StopClockTimer()) return;
17176     if (!appData.clockMode) return;
17177
17178     GetTimeMark(&now);
17179
17180     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17181     if (WhiteOnMove(forwardMostMove)) {
17182         if(whiteNPS >= 0) lastTickLength = 0;
17183         whiteTimeRemaining -= lastTickLength;
17184         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17185     } else {
17186         if(blackNPS >= 0) lastTickLength = 0;
17187         blackTimeRemaining -= lastTickLength;
17188         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17189     }
17190     CheckFlags();
17191 }
17192
17193 /* Start clock of player on move.  Time may have been reset, so
17194    if clock is already running, stop and restart it. */
17195 void
17196 StartClocks ()
17197 {
17198     (void) StopClockTimer(); /* in case it was running already */
17199     DisplayBothClocks();
17200     if (CheckFlags()) return;
17201
17202     if (!appData.clockMode) return;
17203     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17204
17205     GetTimeMark(&tickStartTM);
17206     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17207       whiteTimeRemaining : blackTimeRemaining);
17208
17209    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17210     whiteNPS = blackNPS = -1;
17211     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17212        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17213         whiteNPS = first.nps;
17214     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17215        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17216         blackNPS = first.nps;
17217     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17218         whiteNPS = second.nps;
17219     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17220         blackNPS = second.nps;
17221     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17222
17223     StartClockTimer(intendedTickLength);
17224 }
17225
17226 char *
17227 TimeString (long ms)
17228 {
17229     long second, minute, hour, day;
17230     char *sign = "";
17231     static char buf[32];
17232
17233     if (ms > 0 && ms <= 9900) {
17234       /* convert milliseconds to tenths, rounding up */
17235       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17236
17237       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17238       return buf;
17239     }
17240
17241     /* convert milliseconds to seconds, rounding up */
17242     /* use floating point to avoid strangeness of integer division
17243        with negative dividends on many machines */
17244     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17245
17246     if (second < 0) {
17247         sign = "-";
17248         second = -second;
17249     }
17250
17251     day = second / (60 * 60 * 24);
17252     second = second % (60 * 60 * 24);
17253     hour = second / (60 * 60);
17254     second = second % (60 * 60);
17255     minute = second / 60;
17256     second = second % 60;
17257
17258     if (day > 0)
17259       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17260               sign, day, hour, minute, second);
17261     else if (hour > 0)
17262       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17263     else
17264       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17265
17266     return buf;
17267 }
17268
17269
17270 /*
17271  * This is necessary because some C libraries aren't ANSI C compliant yet.
17272  */
17273 char *
17274 StrStr (char *string, char *match)
17275 {
17276     int i, length;
17277
17278     length = strlen(match);
17279
17280     for (i = strlen(string) - length; i >= 0; i--, string++)
17281       if (!strncmp(match, string, length))
17282         return string;
17283
17284     return NULL;
17285 }
17286
17287 char *
17288 StrCaseStr (char *string, char *match)
17289 {
17290     int i, j, length;
17291
17292     length = strlen(match);
17293
17294     for (i = strlen(string) - length; i >= 0; i--, string++) {
17295         for (j = 0; j < length; j++) {
17296             if (ToLower(match[j]) != ToLower(string[j]))
17297               break;
17298         }
17299         if (j == length) return string;
17300     }
17301
17302     return NULL;
17303 }
17304
17305 #ifndef _amigados
17306 int
17307 StrCaseCmp (char *s1, char *s2)
17308 {
17309     char c1, c2;
17310
17311     for (;;) {
17312         c1 = ToLower(*s1++);
17313         c2 = ToLower(*s2++);
17314         if (c1 > c2) return 1;
17315         if (c1 < c2) return -1;
17316         if (c1 == NULLCHAR) return 0;
17317     }
17318 }
17319
17320
17321 int
17322 ToLower (int c)
17323 {
17324     return isupper(c) ? tolower(c) : c;
17325 }
17326
17327
17328 int
17329 ToUpper (int c)
17330 {
17331     return islower(c) ? toupper(c) : c;
17332 }
17333 #endif /* !_amigados    */
17334
17335 char *
17336 StrSave (char *s)
17337 {
17338   char *ret;
17339
17340   if ((ret = (char *) malloc(strlen(s) + 1)))
17341     {
17342       safeStrCpy(ret, s, strlen(s)+1);
17343     }
17344   return ret;
17345 }
17346
17347 char *
17348 StrSavePtr (char *s, char **savePtr)
17349 {
17350     if (*savePtr) {
17351         free(*savePtr);
17352     }
17353     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17354       safeStrCpy(*savePtr, s, strlen(s)+1);
17355     }
17356     return(*savePtr);
17357 }
17358
17359 char *
17360 PGNDate ()
17361 {
17362     time_t clock;
17363     struct tm *tm;
17364     char buf[MSG_SIZ];
17365
17366     clock = time((time_t *)NULL);
17367     tm = localtime(&clock);
17368     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17369             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17370     return StrSave(buf);
17371 }
17372
17373
17374 char *
17375 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17376 {
17377     int i, j, fromX, fromY, toX, toY;
17378     int whiteToPlay;
17379     char buf[MSG_SIZ];
17380     char *p, *q;
17381     int emptycount;
17382     ChessSquare piece;
17383
17384     whiteToPlay = (gameMode == EditPosition) ?
17385       !blackPlaysFirst : (move % 2 == 0);
17386     p = buf;
17387
17388     /* Piece placement data */
17389     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17390         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17391         emptycount = 0;
17392         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17393             if (boards[move][i][j] == EmptySquare) {
17394                 emptycount++;
17395             } else { ChessSquare piece = boards[move][i][j];
17396                 if (emptycount > 0) {
17397                     if(emptycount<10) /* [HGM] can be >= 10 */
17398                         *p++ = '0' + emptycount;
17399                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17400                     emptycount = 0;
17401                 }
17402                 if(PieceToChar(piece) == '+') {
17403                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17404                     *p++ = '+';
17405                     piece = (ChessSquare)(DEMOTED piece);
17406                 }
17407                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17408                 if(p[-1] == '~') {
17409                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17410                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17411                     *p++ = '~';
17412                 }
17413             }
17414         }
17415         if (emptycount > 0) {
17416             if(emptycount<10) /* [HGM] can be >= 10 */
17417                 *p++ = '0' + emptycount;
17418             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17419             emptycount = 0;
17420         }
17421         *p++ = '/';
17422     }
17423     *(p - 1) = ' ';
17424
17425     /* [HGM] print Crazyhouse or Shogi holdings */
17426     if( gameInfo.holdingsWidth ) {
17427         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17428         q = p;
17429         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17430             piece = boards[move][i][BOARD_WIDTH-1];
17431             if( piece != EmptySquare )
17432               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17433                   *p++ = PieceToChar(piece);
17434         }
17435         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17436             piece = boards[move][BOARD_HEIGHT-i-1][0];
17437             if( piece != EmptySquare )
17438               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17439                   *p++ = PieceToChar(piece);
17440         }
17441
17442         if( q == p ) *p++ = '-';
17443         *p++ = ']';
17444         *p++ = ' ';
17445     }
17446
17447     /* Active color */
17448     *p++ = whiteToPlay ? 'w' : 'b';
17449     *p++ = ' ';
17450
17451   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17452     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17453   } else {
17454   if(nrCastlingRights) {
17455      q = p;
17456      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17457        /* [HGM] write directly from rights */
17458            if(boards[move][CASTLING][2] != NoRights &&
17459               boards[move][CASTLING][0] != NoRights   )
17460                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17461            if(boards[move][CASTLING][2] != NoRights &&
17462               boards[move][CASTLING][1] != NoRights   )
17463                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17464            if(boards[move][CASTLING][5] != NoRights &&
17465               boards[move][CASTLING][3] != NoRights   )
17466                 *p++ = boards[move][CASTLING][3] + AAA;
17467            if(boards[move][CASTLING][5] != NoRights &&
17468               boards[move][CASTLING][4] != NoRights   )
17469                 *p++ = boards[move][CASTLING][4] + AAA;
17470      } else {
17471
17472         /* [HGM] write true castling rights */
17473         if( nrCastlingRights == 6 ) {
17474             int q, k=0;
17475             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17476                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17477             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17478                  boards[move][CASTLING][2] != NoRights  );
17479             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17480                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17481                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17482                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17483                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17484             }
17485             if(q) *p++ = 'Q';
17486             k = 0;
17487             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17488                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17489             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17490                  boards[move][CASTLING][5] != NoRights  );
17491             if(gameInfo.variant == VariantSChess) {
17492                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17493                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17494                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17495                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17496             }
17497             if(q) *p++ = 'q';
17498         }
17499      }
17500      if (q == p) *p++ = '-'; /* No castling rights */
17501      *p++ = ' ';
17502   }
17503
17504   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17505      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17506      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17507     /* En passant target square */
17508     if (move > backwardMostMove) {
17509         fromX = moveList[move - 1][0] - AAA;
17510         fromY = moveList[move - 1][1] - ONE;
17511         toX = moveList[move - 1][2] - AAA;
17512         toY = moveList[move - 1][3] - ONE;
17513         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17514             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17515             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17516             fromX == toX) {
17517             /* 2-square pawn move just happened */
17518             *p++ = toX + AAA;
17519             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17520         } else {
17521             *p++ = '-';
17522         }
17523     } else if(move == backwardMostMove) {
17524         // [HGM] perhaps we should always do it like this, and forget the above?
17525         if((signed char)boards[move][EP_STATUS] >= 0) {
17526             *p++ = boards[move][EP_STATUS] + AAA;
17527             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17528         } else {
17529             *p++ = '-';
17530         }
17531     } else {
17532         *p++ = '-';
17533     }
17534     *p++ = ' ';
17535   }
17536   }
17537
17538     if(moveCounts)
17539     {   int i = 0, j=move;
17540
17541         /* [HGM] find reversible plies */
17542         if (appData.debugMode) { int k;
17543             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17544             for(k=backwardMostMove; k<=forwardMostMove; k++)
17545                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17546
17547         }
17548
17549         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17550         if( j == backwardMostMove ) i += initialRulePlies;
17551         sprintf(p, "%d ", i);
17552         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17553
17554         /* Fullmove number */
17555         sprintf(p, "%d", (move / 2) + 1);
17556     } else *--p = NULLCHAR;
17557
17558     return StrSave(buf);
17559 }
17560
17561 Boolean
17562 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17563 {
17564     int i, j, k, w=0;
17565     char *p, c;
17566     int emptycount, virgin[BOARD_FILES];
17567     ChessSquare piece;
17568
17569     p = fen;
17570
17571     /* Piece placement data */
17572     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17573         j = 0;
17574         for (;;) {
17575             if (*p == '/' || *p == ' ' || *p == '[' ) {
17576                 if(j > w) w = j;
17577                 emptycount = gameInfo.boardWidth - j;
17578                 while (emptycount--)
17579                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17580                 if (*p == '/') p++;
17581                 else if(autoSize) { // we stumbled unexpectedly into end of board
17582                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17583                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17584                     }
17585                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17586                 }
17587                 break;
17588 #if(BOARD_FILES >= 10)
17589             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17590                 p++; emptycount=10;
17591                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17592                 while (emptycount--)
17593                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17594 #endif
17595             } else if (*p == '*') {
17596                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17597             } else if (isdigit(*p)) {
17598                 emptycount = *p++ - '0';
17599                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17600                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17601                 while (emptycount--)
17602                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17603             } else if (*p == '+' || isalpha(*p)) {
17604                 if (j >= gameInfo.boardWidth) return FALSE;
17605                 if(*p=='+') {
17606                     piece = CharToPiece(*++p);
17607                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17608                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17609                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17610                 } else piece = CharToPiece(*p++);
17611
17612                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17613                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17614                     piece = (ChessSquare) (PROMOTED piece);
17615                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17616                     p++;
17617                 }
17618                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17619             } else {
17620                 return FALSE;
17621             }
17622         }
17623     }
17624     while (*p == '/' || *p == ' ') p++;
17625
17626     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17627
17628     /* [HGM] by default clear Crazyhouse holdings, if present */
17629     if(gameInfo.holdingsWidth) {
17630        for(i=0; i<BOARD_HEIGHT; i++) {
17631            board[i][0]             = EmptySquare; /* black holdings */
17632            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17633            board[i][1]             = (ChessSquare) 0; /* black counts */
17634            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17635        }
17636     }
17637
17638     /* [HGM] look for Crazyhouse holdings here */
17639     while(*p==' ') p++;
17640     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17641         if(*p == '[') p++;
17642         if(*p == '-' ) p++; /* empty holdings */ else {
17643             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17644             /* if we would allow FEN reading to set board size, we would   */
17645             /* have to add holdings and shift the board read so far here   */
17646             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17647                 p++;
17648                 if((int) piece >= (int) BlackPawn ) {
17649                     i = (int)piece - (int)BlackPawn;
17650                     i = PieceToNumber((ChessSquare)i);
17651                     if( i >= gameInfo.holdingsSize ) return FALSE;
17652                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17653                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17654                 } else {
17655                     i = (int)piece - (int)WhitePawn;
17656                     i = PieceToNumber((ChessSquare)i);
17657                     if( i >= gameInfo.holdingsSize ) return FALSE;
17658                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17659                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17660                 }
17661             }
17662         }
17663         if(*p == ']') p++;
17664     }
17665
17666     while(*p == ' ') p++;
17667
17668     /* Active color */
17669     c = *p++;
17670     if(appData.colorNickNames) {
17671       if( c == appData.colorNickNames[0] ) c = 'w'; else
17672       if( c == appData.colorNickNames[1] ) c = 'b';
17673     }
17674     switch (c) {
17675       case 'w':
17676         *blackPlaysFirst = FALSE;
17677         break;
17678       case 'b':
17679         *blackPlaysFirst = TRUE;
17680         break;
17681       default:
17682         return FALSE;
17683     }
17684
17685     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17686     /* return the extra info in global variiables             */
17687
17688     /* set defaults in case FEN is incomplete */
17689     board[EP_STATUS] = EP_UNKNOWN;
17690     for(i=0; i<nrCastlingRights; i++ ) {
17691         board[CASTLING][i] =
17692             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17693     }   /* assume possible unless obviously impossible */
17694     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17695     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17696     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17697                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17698     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17699     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17700     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17701                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17702     FENrulePlies = 0;
17703
17704     while(*p==' ') p++;
17705     if(nrCastlingRights) {
17706       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17707       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17708           /* castling indicator present, so default becomes no castlings */
17709           for(i=0; i<nrCastlingRights; i++ ) {
17710                  board[CASTLING][i] = NoRights;
17711           }
17712       }
17713       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17714              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17715              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17716              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17717         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17718
17719         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17720             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17721             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17722         }
17723         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17724             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17725         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17726                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17727         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17728                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17729         switch(c) {
17730           case'K':
17731               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17732               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17733               board[CASTLING][2] = whiteKingFile;
17734               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17735               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17736               break;
17737           case'Q':
17738               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17739               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17740               board[CASTLING][2] = whiteKingFile;
17741               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17742               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17743               break;
17744           case'k':
17745               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17746               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17747               board[CASTLING][5] = blackKingFile;
17748               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17749               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17750               break;
17751           case'q':
17752               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17753               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17754               board[CASTLING][5] = blackKingFile;
17755               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17756               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17757           case '-':
17758               break;
17759           default: /* FRC castlings */
17760               if(c >= 'a') { /* black rights */
17761                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17762                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17763                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17764                   if(i == BOARD_RGHT) break;
17765                   board[CASTLING][5] = i;
17766                   c -= AAA;
17767                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17768                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17769                   if(c > i)
17770                       board[CASTLING][3] = c;
17771                   else
17772                       board[CASTLING][4] = c;
17773               } else { /* white rights */
17774                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17775                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17776                     if(board[0][i] == WhiteKing) break;
17777                   if(i == BOARD_RGHT) break;
17778                   board[CASTLING][2] = i;
17779                   c -= AAA - 'a' + 'A';
17780                   if(board[0][c] >= WhiteKing) break;
17781                   if(c > i)
17782                       board[CASTLING][0] = c;
17783                   else
17784                       board[CASTLING][1] = c;
17785               }
17786         }
17787       }
17788       for(i=0; i<nrCastlingRights; i++)
17789         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17790       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17791     if (appData.debugMode) {
17792         fprintf(debugFP, "FEN castling rights:");
17793         for(i=0; i<nrCastlingRights; i++)
17794         fprintf(debugFP, " %d", board[CASTLING][i]);
17795         fprintf(debugFP, "\n");
17796     }
17797
17798       while(*p==' ') p++;
17799     }
17800
17801     /* read e.p. field in games that know e.p. capture */
17802     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17803        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17804        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17805       if(*p=='-') {
17806         p++; board[EP_STATUS] = EP_NONE;
17807       } else {
17808          char c = *p++ - AAA;
17809
17810          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17811          if(*p >= '0' && *p <='9') p++;
17812          board[EP_STATUS] = c;
17813       }
17814     }
17815
17816
17817     if(sscanf(p, "%d", &i) == 1) {
17818         FENrulePlies = i; /* 50-move ply counter */
17819         /* (The move number is still ignored)    */
17820     }
17821
17822     return TRUE;
17823 }
17824
17825 void
17826 EditPositionPasteFEN (char *fen)
17827 {
17828   if (fen != NULL) {
17829     Board initial_position;
17830
17831     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17832       DisplayError(_("Bad FEN position in clipboard"), 0);
17833       return ;
17834     } else {
17835       int savedBlackPlaysFirst = blackPlaysFirst;
17836       EditPositionEvent();
17837       blackPlaysFirst = savedBlackPlaysFirst;
17838       CopyBoard(boards[0], initial_position);
17839       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17840       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17841       DisplayBothClocks();
17842       DrawPosition(FALSE, boards[currentMove]);
17843     }
17844   }
17845 }
17846
17847 static char cseq[12] = "\\   ";
17848
17849 Boolean
17850 set_cont_sequence (char *new_seq)
17851 {
17852     int len;
17853     Boolean ret;
17854
17855     // handle bad attempts to set the sequence
17856         if (!new_seq)
17857                 return 0; // acceptable error - no debug
17858
17859     len = strlen(new_seq);
17860     ret = (len > 0) && (len < sizeof(cseq));
17861     if (ret)
17862       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17863     else if (appData.debugMode)
17864       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17865     return ret;
17866 }
17867
17868 /*
17869     reformat a source message so words don't cross the width boundary.  internal
17870     newlines are not removed.  returns the wrapped size (no null character unless
17871     included in source message).  If dest is NULL, only calculate the size required
17872     for the dest buffer.  lp argument indicats line position upon entry, and it's
17873     passed back upon exit.
17874 */
17875 int
17876 wrap (char *dest, char *src, int count, int width, int *lp)
17877 {
17878     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17879
17880     cseq_len = strlen(cseq);
17881     old_line = line = *lp;
17882     ansi = len = clen = 0;
17883
17884     for (i=0; i < count; i++)
17885     {
17886         if (src[i] == '\033')
17887             ansi = 1;
17888
17889         // if we hit the width, back up
17890         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17891         {
17892             // store i & len in case the word is too long
17893             old_i = i, old_len = len;
17894
17895             // find the end of the last word
17896             while (i && src[i] != ' ' && src[i] != '\n')
17897             {
17898                 i--;
17899                 len--;
17900             }
17901
17902             // word too long?  restore i & len before splitting it
17903             if ((old_i-i+clen) >= width)
17904             {
17905                 i = old_i;
17906                 len = old_len;
17907             }
17908
17909             // extra space?
17910             if (i && src[i-1] == ' ')
17911                 len--;
17912
17913             if (src[i] != ' ' && src[i] != '\n')
17914             {
17915                 i--;
17916                 if (len)
17917                     len--;
17918             }
17919
17920             // now append the newline and continuation sequence
17921             if (dest)
17922                 dest[len] = '\n';
17923             len++;
17924             if (dest)
17925                 strncpy(dest+len, cseq, cseq_len);
17926             len += cseq_len;
17927             line = cseq_len;
17928             clen = cseq_len;
17929             continue;
17930         }
17931
17932         if (dest)
17933             dest[len] = src[i];
17934         len++;
17935         if (!ansi)
17936             line++;
17937         if (src[i] == '\n')
17938             line = 0;
17939         if (src[i] == 'm')
17940             ansi = 0;
17941     }
17942     if (dest && appData.debugMode)
17943     {
17944         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17945             count, width, line, len, *lp);
17946         show_bytes(debugFP, src, count);
17947         fprintf(debugFP, "\ndest: ");
17948         show_bytes(debugFP, dest, len);
17949         fprintf(debugFP, "\n");
17950     }
17951     *lp = dest ? line : old_line;
17952
17953     return len;
17954 }
17955
17956 // [HGM] vari: routines for shelving variations
17957 Boolean modeRestore = FALSE;
17958
17959 void
17960 PushInner (int firstMove, int lastMove)
17961 {
17962         int i, j, nrMoves = lastMove - firstMove;
17963
17964         // push current tail of game on stack
17965         savedResult[storedGames] = gameInfo.result;
17966         savedDetails[storedGames] = gameInfo.resultDetails;
17967         gameInfo.resultDetails = NULL;
17968         savedFirst[storedGames] = firstMove;
17969         savedLast [storedGames] = lastMove;
17970         savedFramePtr[storedGames] = framePtr;
17971         framePtr -= nrMoves; // reserve space for the boards
17972         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17973             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17974             for(j=0; j<MOVE_LEN; j++)
17975                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17976             for(j=0; j<2*MOVE_LEN; j++)
17977                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17978             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17979             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17980             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17981             pvInfoList[firstMove+i-1].depth = 0;
17982             commentList[framePtr+i] = commentList[firstMove+i];
17983             commentList[firstMove+i] = NULL;
17984         }
17985
17986         storedGames++;
17987         forwardMostMove = firstMove; // truncate game so we can start variation
17988 }
17989
17990 void
17991 PushTail (int firstMove, int lastMove)
17992 {
17993         if(appData.icsActive) { // only in local mode
17994                 forwardMostMove = currentMove; // mimic old ICS behavior
17995                 return;
17996         }
17997         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17998
17999         PushInner(firstMove, lastMove);
18000         if(storedGames == 1) GreyRevert(FALSE);
18001         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18002 }
18003
18004 void
18005 PopInner (Boolean annotate)
18006 {
18007         int i, j, nrMoves;
18008         char buf[8000], moveBuf[20];
18009
18010         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18011         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18012         nrMoves = savedLast[storedGames] - currentMove;
18013         if(annotate) {
18014                 int cnt = 10;
18015                 if(!WhiteOnMove(currentMove))
18016                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18017                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18018                 for(i=currentMove; i<forwardMostMove; i++) {
18019                         if(WhiteOnMove(i))
18020                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18021                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18022                         strcat(buf, moveBuf);
18023                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18024                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18025                 }
18026                 strcat(buf, ")");
18027         }
18028         for(i=1; i<=nrMoves; i++) { // copy last variation back
18029             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18030             for(j=0; j<MOVE_LEN; j++)
18031                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18032             for(j=0; j<2*MOVE_LEN; j++)
18033                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18034             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18035             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18036             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18037             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18038             commentList[currentMove+i] = commentList[framePtr+i];
18039             commentList[framePtr+i] = NULL;
18040         }
18041         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18042         framePtr = savedFramePtr[storedGames];
18043         gameInfo.result = savedResult[storedGames];
18044         if(gameInfo.resultDetails != NULL) {
18045             free(gameInfo.resultDetails);
18046       }
18047         gameInfo.resultDetails = savedDetails[storedGames];
18048         forwardMostMove = currentMove + nrMoves;
18049 }
18050
18051 Boolean
18052 PopTail (Boolean annotate)
18053 {
18054         if(appData.icsActive) return FALSE; // only in local mode
18055         if(!storedGames) return FALSE; // sanity
18056         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18057
18058         PopInner(annotate);
18059         if(currentMove < forwardMostMove) ForwardEvent(); else
18060         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18061
18062         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18063         return TRUE;
18064 }
18065
18066 void
18067 CleanupTail ()
18068 {       // remove all shelved variations
18069         int i;
18070         for(i=0; i<storedGames; i++) {
18071             if(savedDetails[i])
18072                 free(savedDetails[i]);
18073             savedDetails[i] = NULL;
18074         }
18075         for(i=framePtr; i<MAX_MOVES; i++) {
18076                 if(commentList[i]) free(commentList[i]);
18077                 commentList[i] = NULL;
18078         }
18079         framePtr = MAX_MOVES-1;
18080         storedGames = 0;
18081 }
18082
18083 void
18084 LoadVariation (int index, char *text)
18085 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18086         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18087         int level = 0, move;
18088
18089         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18090         // first find outermost bracketing variation
18091         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18092             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18093                 if(*p == '{') wait = '}'; else
18094                 if(*p == '[') wait = ']'; else
18095                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18096                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18097             }
18098             if(*p == wait) wait = NULLCHAR; // closing ]} found
18099             p++;
18100         }
18101         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18102         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18103         end[1] = NULLCHAR; // clip off comment beyond variation
18104         ToNrEvent(currentMove-1);
18105         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18106         // kludge: use ParsePV() to append variation to game
18107         move = currentMove;
18108         ParsePV(start, TRUE, TRUE);
18109         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18110         ClearPremoveHighlights();
18111         CommentPopDown();
18112         ToNrEvent(currentMove+1);
18113 }
18114
18115 void
18116 LoadTheme ()
18117 {
18118     char *p, *q, buf[MSG_SIZ];
18119     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18120         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18121         ParseArgsFromString(buf);
18122         ActivateTheme(TRUE); // also redo colors
18123         return;
18124     }
18125     p = nickName;
18126     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18127     {
18128         int len;
18129         q = appData.themeNames;
18130         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18131       if(appData.useBitmaps) {
18132         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18133                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18134                 appData.liteBackTextureMode,
18135                 appData.darkBackTextureMode );
18136       } else {
18137         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18138                 Col2Text(2),   // lightSquareColor
18139                 Col2Text(3) ); // darkSquareColor
18140       }
18141       if(appData.useBorder) {
18142         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18143                 appData.border);
18144       } else {
18145         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18146       }
18147       if(appData.useFont) {
18148         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18149                 appData.renderPiecesWithFont,
18150                 appData.fontToPieceTable,
18151                 Col2Text(9),    // appData.fontBackColorWhite
18152                 Col2Text(10) ); // appData.fontForeColorBlack
18153       } else {
18154         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18155                 appData.pieceDirectory);
18156         if(!appData.pieceDirectory[0])
18157           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18158                 Col2Text(0),   // whitePieceColor
18159                 Col2Text(1) ); // blackPieceColor
18160       }
18161       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18162                 Col2Text(4),   // highlightSquareColor
18163                 Col2Text(5) ); // premoveHighlightColor
18164         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18165         if(insert != q) insert[-1] = NULLCHAR;
18166         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18167         if(q)   free(q);
18168     }
18169     ActivateTheme(FALSE);
18170 }