Implement LionChess
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569 ChessSquare  lionArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackLion, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackKnight, BlackRook }
574 };
575
576
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
583 };
584
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
590 };
591
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
597 };
598
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
604 };
605
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
611 };
612
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
618 };
619
620 #ifdef GOTHIC
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
626 };
627 #else // !GOTHIC
628 #define GothicArray CapablancaArray
629 #endif // !GOTHIC
630
631 #ifdef FALCON
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
637 };
638 #else // !FALCON
639 #define FalconArray CapablancaArray
640 #endif // !FALCON
641
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
648
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
655 };
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
669 };
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
674
675
676 Board initialPosition;
677
678
679 /* Convert str to a rating. Checks for special cases of "----",
680
681    "++++", etc. Also strips ()'s */
682 int
683 string_to_rating (char *str)
684 {
685   while(*str && !isdigit(*str)) ++str;
686   if (!*str)
687     return 0;   /* One of the special "no rating" cases */
688   else
689     return atoi(str);
690 }
691
692 void
693 ClearProgramStats ()
694 {
695     /* Init programStats */
696     programStats.movelist[0] = 0;
697     programStats.depth = 0;
698     programStats.nr_moves = 0;
699     programStats.moves_left = 0;
700     programStats.nodes = 0;
701     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
702     programStats.score = 0;
703     programStats.got_only_move = 0;
704     programStats.got_fail = 0;
705     programStats.line_is_book = 0;
706 }
707
708 void
709 CommonEngineInit ()
710 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711     if (appData.firstPlaysBlack) {
712         first.twoMachinesColor = "black\n";
713         second.twoMachinesColor = "white\n";
714     } else {
715         first.twoMachinesColor = "white\n";
716         second.twoMachinesColor = "black\n";
717     }
718
719     first.other = &second;
720     second.other = &first;
721
722     { float norm = 1;
723         if(appData.timeOddsMode) {
724             norm = appData.timeOdds[0];
725             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
726         }
727         first.timeOdds  = appData.timeOdds[0]/norm;
728         second.timeOdds = appData.timeOdds[1]/norm;
729     }
730
731     if(programVersion) free(programVersion);
732     if (appData.noChessProgram) {
733         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734         sprintf(programVersion, "%s", PACKAGE_STRING);
735     } else {
736       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
739     }
740 }
741
742 void
743 UnloadEngine (ChessProgramState *cps)
744 {
745         /* Kill off first chess program */
746         if (cps->isr != NULL)
747           RemoveInputSource(cps->isr);
748         cps->isr = NULL;
749
750         if (cps->pr != NoProc) {
751             ExitAnalyzeMode();
752             DoSleep( appData.delayBeforeQuit );
753             SendToProgram("quit\n", cps);
754             DoSleep( appData.delayAfterQuit );
755             DestroyChildProcess(cps->pr, cps->useSigterm);
756         }
757         cps->pr = NoProc;
758         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
759 }
760
761 void
762 ClearOptions (ChessProgramState *cps)
763 {
764     int i;
765     cps->nrOptions = cps->comboCnt = 0;
766     for(i=0; i<MAX_OPTIONS; i++) {
767         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768         cps->option[i].textValue = 0;
769     }
770 }
771
772 char *engineNames[] = {
773   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 N_("first"),
776   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
778 N_("second")
779 };
780
781 void
782 InitEngine (ChessProgramState *cps, int n)
783 {   // [HGM] all engine initialiation put in a function that does one engine
784
785     ClearOptions(cps);
786
787     cps->which = engineNames[n];
788     cps->maybeThinking = FALSE;
789     cps->pr = NoProc;
790     cps->isr = NULL;
791     cps->sendTime = 2;
792     cps->sendDrawOffers = 1;
793
794     cps->program = appData.chessProgram[n];
795     cps->host = appData.host[n];
796     cps->dir = appData.directory[n];
797     cps->initString = appData.engInitString[n];
798     cps->computerString = appData.computerString[n];
799     cps->useSigint  = TRUE;
800     cps->useSigterm = TRUE;
801     cps->reuse = appData.reuse[n];
802     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
803     cps->useSetboard = FALSE;
804     cps->useSAN = FALSE;
805     cps->usePing = FALSE;
806     cps->lastPing = 0;
807     cps->lastPong = 0;
808     cps->usePlayother = FALSE;
809     cps->useColors = TRUE;
810     cps->useUsermove = FALSE;
811     cps->sendICS = FALSE;
812     cps->sendName = appData.icsActive;
813     cps->sdKludge = FALSE;
814     cps->stKludge = FALSE;
815     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816     TidyProgramName(cps->program, cps->host, cps->tidy);
817     cps->matchWins = 0;
818     ASSIGN(cps->variants, appData.variant);
819     cps->analysisSupport = 2; /* detect */
820     cps->analyzing = FALSE;
821     cps->initDone = FALSE;
822     cps->reload = FALSE;
823
824     /* New features added by Tord: */
825     cps->useFEN960 = FALSE;
826     cps->useOOCastle = TRUE;
827     /* End of new features added by Tord. */
828     cps->fenOverride  = appData.fenOverride[n];
829
830     /* [HGM] time odds: set factor for each machine */
831     cps->timeOdds  = appData.timeOdds[n];
832
833     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834     cps->accumulateTC = appData.accumulateTC[n];
835     cps->maxNrOfSessions = 1;
836
837     /* [HGM] debug */
838     cps->debug = FALSE;
839
840     cps->supportsNPS = UNKNOWN;
841     cps->memSize = FALSE;
842     cps->maxCores = FALSE;
843     ASSIGN(cps->egtFormats, "");
844
845     /* [HGM] options */
846     cps->optionSettings  = appData.engOptions[n];
847
848     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849     cps->isUCI = appData.isUCI[n]; /* [AS] */
850     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
851     cps->highlight = 0;
852
853     if (appData.protocolVersion[n] > PROTOVER
854         || appData.protocolVersion[n] < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.protocolVersion[n]);
861         if( (len >= MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         cps->protocolVersion = appData.protocolVersion[n];
869       }
870
871     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
872     ParseFeatures(appData.featureDefaults, cps);
873 }
874
875 ChessProgramState *savCps;
876
877 GameMode oldMode;
878
879 void
880 LoadEngine ()
881 {
882     int i;
883     if(WaitForEngine(savCps, LoadEngine)) return;
884     CommonEngineInit(); // recalculate time odds
885     if(gameInfo.variant != StringToVariant(appData.variant)) {
886         // we changed variant when loading the engine; this forces us to reset
887         Reset(TRUE, savCps != &first);
888         oldMode = BeginningOfGame; // to prevent restoring old mode
889     }
890     InitChessProgram(savCps, FALSE);
891     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892     DisplayMessage("", "");
893     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
895     ThawUI();
896     SetGNUMode();
897     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
898 }
899
900 void
901 ReplaceEngine (ChessProgramState *cps, int n)
902 {
903     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
904     keepInfo = 1;
905     if(oldMode != BeginningOfGame) EditGameEvent();
906     keepInfo = 0;
907     UnloadEngine(cps);
908     appData.noChessProgram = FALSE;
909     appData.clockMode = TRUE;
910     InitEngine(cps, n);
911     UpdateLogos(TRUE);
912     if(n) return; // only startup first engine immediately; second can wait
913     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
914     LoadEngine();
915 }
916
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
919
920 static char resetOptions[] =
921         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
925
926 void
927 FloatToFront(char **list, char *engineLine)
928 {
929     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
930     int i=0;
931     if(appData.recentEngines <= 0) return;
932     TidyProgramName(engineLine, "localhost", tidy+1);
933     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934     strncpy(buf+1, *list, MSG_SIZ-50);
935     if(p = strstr(buf, tidy)) { // tidy name appears in list
936         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937         while(*p++ = *++q); // squeeze out
938     }
939     strcat(tidy, buf+1); // put list behind tidy name
940     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942     ASSIGN(*list, tidy+1);
943 }
944
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
946
947 void
948 Load (ChessProgramState *cps, int i)
949 {
950     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956         appData.firstProtocolVersion = PROTOVER;
957         ParseArgsFromString(buf);
958         SwapEngines(i);
959         ReplaceEngine(cps, i);
960         FloatToFront(&appData.recentEngineList, engineLine);
961         return;
962     }
963     p = engineName;
964     while(q = strchr(p, SLASH)) p = q+1;
965     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966     if(engineDir[0] != NULLCHAR) {
967         ASSIGN(appData.directory[i], engineDir); p = engineName;
968     } else if(p != engineName) { // derive directory from engine path, when not given
969         p[-1] = 0;
970         ASSIGN(appData.directory[i], engineName);
971         p[-1] = SLASH;
972         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973     } else { ASSIGN(appData.directory[i], "."); }
974     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
975     if(params[0]) {
976         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977         snprintf(command, MSG_SIZ, "%s %s", p, params);
978         p = command;
979     }
980     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981     ASSIGN(appData.chessProgram[i], p);
982     appData.isUCI[i] = isUCI;
983     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984     appData.hasOwnBookUCI[i] = hasBook;
985     if(!nickName[0]) useNick = FALSE;
986     if(useNick) ASSIGN(appData.pgnName[i], nickName);
987     if(addToList) {
988         int len;
989         char quote;
990         q = firstChessProgramNames;
991         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994                         quote, p, quote, appData.directory[i],
995                         useNick ? " -fn \"" : "",
996                         useNick ? nickName : "",
997                         useNick ? "\"" : "",
998                         v1 ? " -firstProtocolVersion 1" : "",
999                         hasBook ? "" : " -fNoOwnBookUCI",
1000                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001                         storeVariant ? " -variant " : "",
1002                         storeVariant ? VariantName(gameInfo.variant) : "");
1003         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005         if(insert != q) insert[-1] = NULLCHAR;
1006         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1007         if(q)   free(q);
1008         FloatToFront(&appData.recentEngineList, buf);
1009     }
1010     ReplaceEngine(cps, i);
1011 }
1012
1013 void
1014 InitTimeControls ()
1015 {
1016     int matched, min, sec;
1017     /*
1018      * Parse timeControl resource
1019      */
1020     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021                           appData.movesPerSession)) {
1022         char buf[MSG_SIZ];
1023         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024         DisplayFatalError(buf, 0, 2);
1025     }
1026
1027     /*
1028      * Parse searchTime resource
1029      */
1030     if (*appData.searchTime != NULLCHAR) {
1031         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1032         if (matched == 1) {
1033             searchTime = min * 60;
1034         } else if (matched == 2) {
1035             searchTime = min * 60 + sec;
1036         } else {
1037             char buf[MSG_SIZ];
1038             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039             DisplayFatalError(buf, 0, 2);
1040         }
1041     }
1042 }
1043
1044 void
1045 InitBackEnd1 ()
1046 {
1047
1048     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1050
1051     GetTimeMark(&programStartTime);
1052     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053     appData.seedBase = random() + (random()<<15);
1054     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1055
1056     ClearProgramStats();
1057     programStats.ok_to_send = 1;
1058     programStats.seen_stat = 0;
1059
1060     /*
1061      * Initialize game list
1062      */
1063     ListNew(&gameList);
1064
1065
1066     /*
1067      * Internet chess server status
1068      */
1069     if (appData.icsActive) {
1070         appData.matchMode = FALSE;
1071         appData.matchGames = 0;
1072 #if ZIPPY
1073         appData.noChessProgram = !appData.zippyPlay;
1074 #else
1075         appData.zippyPlay = FALSE;
1076         appData.zippyTalk = FALSE;
1077         appData.noChessProgram = TRUE;
1078 #endif
1079         if (*appData.icsHelper != NULLCHAR) {
1080             appData.useTelnet = TRUE;
1081             appData.telnetProgram = appData.icsHelper;
1082         }
1083     } else {
1084         appData.zippyTalk = appData.zippyPlay = FALSE;
1085     }
1086
1087     /* [AS] Initialize pv info list [HGM] and game state */
1088     {
1089         int i, j;
1090
1091         for( i=0; i<=framePtr; i++ ) {
1092             pvInfoList[i].depth = -1;
1093             boards[i][EP_STATUS] = EP_NONE;
1094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1095         }
1096     }
1097
1098     InitTimeControls();
1099
1100     /* [AS] Adjudication threshold */
1101     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1102
1103     InitEngine(&first, 0);
1104     InitEngine(&second, 1);
1105     CommonEngineInit();
1106
1107     pairing.which = "pairing"; // pairing engine
1108     pairing.pr = NoProc;
1109     pairing.isr = NULL;
1110     pairing.program = appData.pairingEngine;
1111     pairing.host = "localhost";
1112     pairing.dir = ".";
1113
1114     if (appData.icsActive) {
1115         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1116     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117         appData.clockMode = FALSE;
1118         first.sendTime = second.sendTime = 0;
1119     }
1120
1121 #if ZIPPY
1122     /* Override some settings from environment variables, for backward
1123        compatibility.  Unfortunately it's not feasible to have the env
1124        vars just set defaults, at least in xboard.  Ugh.
1125     */
1126     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1127       ZippyInit();
1128     }
1129 #endif
1130
1131     if (!appData.icsActive) {
1132       char buf[MSG_SIZ];
1133       int len;
1134
1135       /* Check for variants that are supported only in ICS mode,
1136          or not at all.  Some that are accepted here nevertheless
1137          have bugs; see comments below.
1138       */
1139       VariantClass variant = StringToVariant(appData.variant);
1140       switch (variant) {
1141       case VariantBughouse:     /* need four players and two boards */
1142       case VariantKriegspiel:   /* need to hide pieces and move details */
1143         /* case VariantFischeRandom: (Fabien: moved below) */
1144         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145         if( (len >= MSG_SIZ) && appData.debugMode )
1146           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1147
1148         DisplayFatalError(buf, 0, 2);
1149         return;
1150
1151       case VariantUnknown:
1152       case VariantLoadable:
1153       case Variant29:
1154       case Variant30:
1155       case Variant31:
1156       case Variant32:
1157       case Variant33:
1158       case Variant34:
1159       case Variant35:
1160       case Variant36:
1161       default:
1162         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163         if( (len >= MSG_SIZ) && appData.debugMode )
1164           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1165
1166         DisplayFatalError(buf, 0, 2);
1167         return;
1168
1169       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1170       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1171       case VariantGothic:     /* [HGM] should work */
1172       case VariantCapablanca: /* [HGM] should work */
1173       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1174       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1175       case VariantChu:        /* [HGM] experimental */
1176       case VariantKnightmate: /* [HGM] should work */
1177       case VariantCylinder:   /* [HGM] untested */
1178       case VariantFalcon:     /* [HGM] untested */
1179       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180                                  offboard interposition not understood */
1181       case VariantNormal:     /* definitely works! */
1182       case VariantWildCastle: /* pieces not automatically shuffled */
1183       case VariantNoCastle:   /* pieces not automatically shuffled */
1184       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185       case VariantLosers:     /* should work except for win condition,
1186                                  and doesn't know captures are mandatory */
1187       case VariantSuicide:    /* should work except for win condition,
1188                                  and doesn't know captures are mandatory */
1189       case VariantGiveaway:   /* should work except for win condition,
1190                                  and doesn't know captures are mandatory */
1191       case VariantTwoKings:   /* should work */
1192       case VariantAtomic:     /* should work except for win condition */
1193       case Variant3Check:     /* should work except for win condition */
1194       case VariantShatranj:   /* should work except for all win conditions */
1195       case VariantMakruk:     /* should work except for draw countdown */
1196       case VariantASEAN :     /* should work except for draw countdown */
1197       case VariantBerolina:   /* might work if TestLegality is off */
1198       case VariantCapaRandom: /* should work */
1199       case VariantJanus:      /* should work */
1200       case VariantSuper:      /* experimental */
1201       case VariantGreat:      /* experimental, requires legality testing to be off */
1202       case VariantSChess:     /* S-Chess, should work */
1203       case VariantGrand:      /* should work */
1204       case VariantSpartan:    /* should work */
1205       case VariantLion:       /* should work */
1206         break;
1207       }
1208     }
1209
1210 }
1211
1212 int
1213 NextIntegerFromString (char ** str, long * value)
1214 {
1215     int result = -1;
1216     char * s = *str;
1217
1218     while( *s == ' ' || *s == '\t' ) {
1219         s++;
1220     }
1221
1222     *value = 0;
1223
1224     if( *s >= '0' && *s <= '9' ) {
1225         while( *s >= '0' && *s <= '9' ) {
1226             *value = *value * 10 + (*s - '0');
1227             s++;
1228         }
1229
1230         result = 0;
1231     }
1232
1233     *str = s;
1234
1235     return result;
1236 }
1237
1238 int
1239 NextTimeControlFromString (char ** str, long * value)
1240 {
1241     long temp;
1242     int result = NextIntegerFromString( str, &temp );
1243
1244     if( result == 0 ) {
1245         *value = temp * 60; /* Minutes */
1246         if( **str == ':' ) {
1247             (*str)++;
1248             result = NextIntegerFromString( str, &temp );
1249             *value += temp; /* Seconds */
1250         }
1251     }
1252
1253     return result;
1254 }
1255
1256 int
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259     int result = -1, type = 0; long temp, temp2;
1260
1261     if(**str != ':') return -1; // old params remain in force!
1262     (*str)++;
1263     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264     if( NextIntegerFromString( str, &temp ) ) return -1;
1265     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1266
1267     if(**str != '/') {
1268         /* time only: incremental or sudden-death time control */
1269         if(**str == '+') { /* increment follows; read it */
1270             (*str)++;
1271             if(**str == '!') type = *(*str)++; // Bronstein TC
1272             if(result = NextIntegerFromString( str, &temp2)) return -1;
1273             *inc = temp2 * 1000;
1274             if(**str == '.') { // read fraction of increment
1275                 char *start = ++(*str);
1276                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1277                 temp2 *= 1000;
1278                 while(start++ < *str) temp2 /= 10;
1279                 *inc += temp2;
1280             }
1281         } else *inc = 0;
1282         *moves = 0; *tc = temp * 1000; *incType = type;
1283         return 0;
1284     }
1285
1286     (*str)++; /* classical time control */
1287     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1288
1289     if(result == 0) {
1290         *moves = temp;
1291         *tc    = temp2 * 1000;
1292         *inc   = 0;
1293         *incType = type;
1294     }
1295     return result;
1296 }
1297
1298 int
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 {   /* [HGM] get time to add from the multi-session time-control string */
1301     int incType, moves=1; /* kludge to force reading of first session */
1302     long time, increment;
1303     char *s = tcString;
1304
1305     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1306     do {
1307         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309         if(movenr == -1) return time;    /* last move before new session     */
1310         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312         if(!moves) return increment;     /* current session is incremental   */
1313         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314     } while(movenr >= -1);               /* try again for next session       */
1315
1316     return 0; // no new time quota on this move
1317 }
1318
1319 int
1320 ParseTimeControl (char *tc, float ti, int mps)
1321 {
1322   long tc1;
1323   long tc2;
1324   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1325   int min, sec=0;
1326
1327   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1330   if(ti > 0) {
1331
1332     if(mps)
1333       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1334     else
1335       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1336   } else {
1337     if(mps)
1338       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1339     else
1340       snprintf(buf, MSG_SIZ, ":%s", mytc);
1341   }
1342   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1343
1344   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1345     return FALSE;
1346   }
1347
1348   if( *tc == '/' ) {
1349     /* Parse second time control */
1350     tc++;
1351
1352     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1353       return FALSE;
1354     }
1355
1356     if( tc2 == 0 ) {
1357       return FALSE;
1358     }
1359
1360     timeControl_2 = tc2 * 1000;
1361   }
1362   else {
1363     timeControl_2 = 0;
1364   }
1365
1366   if( tc1 == 0 ) {
1367     return FALSE;
1368   }
1369
1370   timeControl = tc1 * 1000;
1371
1372   if (ti >= 0) {
1373     timeIncrement = ti * 1000;  /* convert to ms */
1374     movesPerSession = 0;
1375   } else {
1376     timeIncrement = 0;
1377     movesPerSession = mps;
1378   }
1379   return TRUE;
1380 }
1381
1382 void
1383 InitBackEnd2 ()
1384 {
1385     if (appData.debugMode) {
1386 #    ifdef __GIT_VERSION
1387       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1388 #    else
1389       fprintf(debugFP, "Version: %s\n", programVersion);
1390 #    endif
1391     }
1392     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1393
1394     set_cont_sequence(appData.wrapContSeq);
1395     if (appData.matchGames > 0) {
1396         appData.matchMode = TRUE;
1397     } else if (appData.matchMode) {
1398         appData.matchGames = 1;
1399     }
1400     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401         appData.matchGames = appData.sameColorGames;
1402     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1405     }
1406     Reset(TRUE, FALSE);
1407     if (appData.noChessProgram || first.protocolVersion == 1) {
1408       InitBackEnd3();
1409     } else {
1410       /* kludge: allow timeout for initial "feature" commands */
1411       FreezeUI();
1412       DisplayMessage("", _("Starting chess program"));
1413       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1414     }
1415 }
1416
1417 int
1418 CalculateIndex (int index, int gameNr)
1419 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1420     int res;
1421     if(index > 0) return index; // fixed nmber
1422     if(index == 0) return 1;
1423     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1425     return res;
1426 }
1427
1428 int
1429 LoadGameOrPosition (int gameNr)
1430 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431     if (*appData.loadGameFile != NULLCHAR) {
1432         if (!LoadGameFromFile(appData.loadGameFile,
1433                 CalculateIndex(appData.loadGameIndex, gameNr),
1434                               appData.loadGameFile, FALSE)) {
1435             DisplayFatalError(_("Bad game file"), 0, 1);
1436             return 0;
1437         }
1438     } else if (*appData.loadPositionFile != NULLCHAR) {
1439         if (!LoadPositionFromFile(appData.loadPositionFile,
1440                 CalculateIndex(appData.loadPositionIndex, gameNr),
1441                                   appData.loadPositionFile)) {
1442             DisplayFatalError(_("Bad position file"), 0, 1);
1443             return 0;
1444         }
1445     }
1446     return 1;
1447 }
1448
1449 void
1450 ReserveGame (int gameNr, char resChar)
1451 {
1452     FILE *tf = fopen(appData.tourneyFile, "r+");
1453     char *p, *q, c, buf[MSG_SIZ];
1454     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455     safeStrCpy(buf, lastMsg, MSG_SIZ);
1456     DisplayMessage(_("Pick new game"), "");
1457     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458     ParseArgsFromFile(tf);
1459     p = q = appData.results;
1460     if(appData.debugMode) {
1461       char *r = appData.participants;
1462       fprintf(debugFP, "results = '%s'\n", p);
1463       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464       fprintf(debugFP, "\n");
1465     }
1466     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1467     nextGame = q - p;
1468     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469     safeStrCpy(q, p, strlen(p) + 2);
1470     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1474         q[nextGame] = '*';
1475     }
1476     fseek(tf, -(strlen(p)+4), SEEK_END);
1477     c = fgetc(tf);
1478     if(c != '"') // depending on DOS or Unix line endings we can be one off
1479          fseek(tf, -(strlen(p)+2), SEEK_END);
1480     else fseek(tf, -(strlen(p)+3), SEEK_END);
1481     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482     DisplayMessage(buf, "");
1483     free(p); appData.results = q;
1484     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486       int round = appData.defaultMatchGames * appData.tourneyType;
1487       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1488          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489         UnloadEngine(&first);  // next game belongs to other pairing;
1490         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1491     }
1492     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1493 }
1494
1495 void
1496 MatchEvent (int mode)
1497 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1498         int dummy;
1499         if(matchMode) { // already in match mode: switch it off
1500             abortMatch = TRUE;
1501             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1502             return;
1503         }
1504 //      if(gameMode != BeginningOfGame) {
1505 //          DisplayError(_("You can only start a match from the initial position."), 0);
1506 //          return;
1507 //      }
1508         abortMatch = FALSE;
1509         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510         /* Set up machine vs. machine match */
1511         nextGame = 0;
1512         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513         if(appData.tourneyFile[0]) {
1514             ReserveGame(-1, 0);
1515             if(nextGame > appData.matchGames) {
1516                 char buf[MSG_SIZ];
1517                 if(strchr(appData.results, '*') == NULL) {
1518                     FILE *f;
1519                     appData.tourneyCycles++;
1520                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1521                         fclose(f);
1522                         NextTourneyGame(-1, &dummy);
1523                         ReserveGame(-1, 0);
1524                         if(nextGame <= appData.matchGames) {
1525                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1526                             matchMode = mode;
1527                             ScheduleDelayedEvent(NextMatchGame, 10000);
1528                             return;
1529                         }
1530                     }
1531                 }
1532                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533                 DisplayError(buf, 0);
1534                 appData.tourneyFile[0] = 0;
1535                 return;
1536             }
1537         } else
1538         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1539             DisplayFatalError(_("Can't have a match with no chess programs"),
1540                               0, 2);
1541             return;
1542         }
1543         matchMode = mode;
1544         matchGame = roundNr = 1;
1545         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1546         NextMatchGame();
1547 }
1548
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1550
1551 void
1552 InitBackEnd3 P((void))
1553 {
1554     GameMode initialMode;
1555     char buf[MSG_SIZ];
1556     int err, len;
1557
1558     InitChessProgram(&first, startedFromSetupPosition);
1559
1560     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1561         free(programVersion);
1562         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1565     }
1566
1567     if (appData.icsActive) {
1568 #ifdef WIN32
1569         /* [DM] Make a console window if needed [HGM] merged ifs */
1570         ConsoleCreate();
1571 #endif
1572         err = establish();
1573         if (err != 0)
1574           {
1575             if (*appData.icsCommPort != NULLCHAR)
1576               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577                              appData.icsCommPort);
1578             else
1579               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580                         appData.icsHost, appData.icsPort);
1581
1582             if( (len >= MSG_SIZ) && appData.debugMode )
1583               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585             DisplayFatalError(buf, err, 1);
1586             return;
1587         }
1588         SetICSMode();
1589         telnetISR =
1590           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1591         fromUserISR =
1592           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595     } else if (appData.noChessProgram) {
1596         SetNCPMode();
1597     } else {
1598         SetGNUMode();
1599     }
1600
1601     if (*appData.cmailGameName != NULLCHAR) {
1602         SetCmailMode();
1603         OpenLoopback(&cmailPR);
1604         cmailISR =
1605           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1606     }
1607
1608     ThawUI();
1609     DisplayMessage("", "");
1610     if (StrCaseCmp(appData.initialMode, "") == 0) {
1611       initialMode = BeginningOfGame;
1612       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1616         ModeHighlight();
1617       }
1618     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619       initialMode = TwoMachinesPlay;
1620     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621       initialMode = AnalyzeFile;
1622     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623       initialMode = AnalyzeMode;
1624     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625       initialMode = MachinePlaysWhite;
1626     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627       initialMode = MachinePlaysBlack;
1628     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629       initialMode = EditGame;
1630     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631       initialMode = EditPosition;
1632     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633       initialMode = Training;
1634     } else {
1635       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636       if( (len >= MSG_SIZ) && appData.debugMode )
1637         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638
1639       DisplayFatalError(buf, 0, 2);
1640       return;
1641     }
1642
1643     if (appData.matchMode) {
1644         if(appData.tourneyFile[0]) { // start tourney from command line
1645             FILE *f;
1646             if(f = fopen(appData.tourneyFile, "r")) {
1647                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1648                 fclose(f);
1649                 appData.clockMode = TRUE;
1650                 SetGNUMode();
1651             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1652         }
1653         MatchEvent(TRUE);
1654     } else if (*appData.cmailGameName != NULLCHAR) {
1655         /* Set up cmail mode */
1656         ReloadCmailMsgEvent(TRUE);
1657     } else {
1658         /* Set up other modes */
1659         if (initialMode == AnalyzeFile) {
1660           if (*appData.loadGameFile == NULLCHAR) {
1661             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1662             return;
1663           }
1664         }
1665         if (*appData.loadGameFile != NULLCHAR) {
1666             (void) LoadGameFromFile(appData.loadGameFile,
1667                                     appData.loadGameIndex,
1668                                     appData.loadGameFile, TRUE);
1669         } else if (*appData.loadPositionFile != NULLCHAR) {
1670             (void) LoadPositionFromFile(appData.loadPositionFile,
1671                                         appData.loadPositionIndex,
1672                                         appData.loadPositionFile);
1673             /* [HGM] try to make self-starting even after FEN load */
1674             /* to allow automatic setup of fairy variants with wtm */
1675             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676                 gameMode = BeginningOfGame;
1677                 setboardSpoiledMachineBlack = 1;
1678             }
1679             /* [HGM] loadPos: make that every new game uses the setup */
1680             /* from file as long as we do not switch variant          */
1681             if(!blackPlaysFirst) {
1682                 startedFromPositionFile = TRUE;
1683                 CopyBoard(filePosition, boards[0]);
1684             }
1685         }
1686         if (initialMode == AnalyzeMode) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1693             return;
1694           }
1695           AnalyzeModeEvent();
1696         } else if (initialMode == AnalyzeFile) {
1697           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698           ShowThinkingEvent();
1699           AnalyzeFileEvent();
1700           AnalysisPeriodicEvent(1);
1701         } else if (initialMode == MachinePlaysWhite) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           MachineWhiteEvent();
1713         } else if (initialMode == MachinePlaysBlack) {
1714           if (appData.noChessProgram) {
1715             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1716                               0, 2);
1717             return;
1718           }
1719           if (appData.icsActive) {
1720             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1721                               0, 2);
1722             return;
1723           }
1724           MachineBlackEvent();
1725         } else if (initialMode == TwoMachinesPlay) {
1726           if (appData.noChessProgram) {
1727             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1728                               0, 2);
1729             return;
1730           }
1731           if (appData.icsActive) {
1732             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1733                               0, 2);
1734             return;
1735           }
1736           TwoMachinesEvent();
1737         } else if (initialMode == EditGame) {
1738           EditGameEvent();
1739         } else if (initialMode == EditPosition) {
1740           EditPositionEvent();
1741         } else if (initialMode == Training) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744             return;
1745           }
1746           TrainingEvent();
1747         }
1748     }
1749 }
1750
1751 void
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1753 {
1754     DisplayBook(current+1);
1755
1756     MoveHistorySet( movelist, first, last, current, pvInfoList );
1757
1758     EvalGraphSet( first, last, current, pvInfoList );
1759
1760     MakeEngineOutputTitle();
1761 }
1762
1763 /*
1764  * Establish will establish a contact to a remote host.port.
1765  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766  *  used to talk to the host.
1767  * Returns 0 if okay, error code if not.
1768  */
1769 int
1770 establish ()
1771 {
1772     char buf[MSG_SIZ];
1773
1774     if (*appData.icsCommPort != NULLCHAR) {
1775         /* Talk to the host through a serial comm port */
1776         return OpenCommPort(appData.icsCommPort, &icsPR);
1777
1778     } else if (*appData.gateway != NULLCHAR) {
1779         if (*appData.remoteShell == NULLCHAR) {
1780             /* Use the rcmd protocol to run telnet program on a gateway host */
1781             snprintf(buf, sizeof(buf), "%s %s %s",
1782                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1783             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1784
1785         } else {
1786             /* Use the rsh program to run telnet program on a gateway host */
1787             if (*appData.remoteUser == NULLCHAR) {
1788                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789                         appData.gateway, appData.telnetProgram,
1790                         appData.icsHost, appData.icsPort);
1791             } else {
1792                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793                         appData.remoteShell, appData.gateway,
1794                         appData.remoteUser, appData.telnetProgram,
1795                         appData.icsHost, appData.icsPort);
1796             }
1797             return StartChildProcess(buf, "", &icsPR);
1798
1799         }
1800     } else if (appData.useTelnet) {
1801         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1802
1803     } else {
1804         /* TCP socket interface differs somewhat between
1805            Unix and NT; handle details in the front end.
1806            */
1807         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1808     }
1809 }
1810
1811 void
1812 EscapeExpand (char *p, char *q)
1813 {       // [HGM] initstring: routine to shape up string arguments
1814         while(*p++ = *q++) if(p[-1] == '\\')
1815             switch(*q++) {
1816                 case 'n': p[-1] = '\n'; break;
1817                 case 'r': p[-1] = '\r'; break;
1818                 case 't': p[-1] = '\t'; break;
1819                 case '\\': p[-1] = '\\'; break;
1820                 case 0: *p = 0; return;
1821                 default: p[-1] = q[-1]; break;
1822             }
1823 }
1824
1825 void
1826 show_bytes (FILE *fp, char *buf, int count)
1827 {
1828     while (count--) {
1829         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830             fprintf(fp, "\\%03o", *buf & 0xff);
1831         } else {
1832             putc(*buf, fp);
1833         }
1834         buf++;
1835     }
1836     fflush(fp);
1837 }
1838
1839 /* Returns an errno value */
1840 int
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1842 {
1843     char buf[8192], *p, *q, *buflim;
1844     int left, newcount, outcount;
1845
1846     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847         *appData.gateway != NULLCHAR) {
1848         if (appData.debugMode) {
1849             fprintf(debugFP, ">ICS: ");
1850             show_bytes(debugFP, message, count);
1851             fprintf(debugFP, "\n");
1852         }
1853         return OutputToProcess(pr, message, count, outError);
1854     }
1855
1856     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1857     p = message;
1858     q = buf;
1859     left = count;
1860     newcount = 0;
1861     while (left) {
1862         if (q >= buflim) {
1863             if (appData.debugMode) {
1864                 fprintf(debugFP, ">ICS: ");
1865                 show_bytes(debugFP, buf, newcount);
1866                 fprintf(debugFP, "\n");
1867             }
1868             outcount = OutputToProcess(pr, buf, newcount, outError);
1869             if (outcount < newcount) return -1; /* to be sure */
1870             q = buf;
1871             newcount = 0;
1872         }
1873         if (*p == '\n') {
1874             *q++ = '\r';
1875             newcount++;
1876         } else if (((unsigned char) *p) == TN_IAC) {
1877             *q++ = (char) TN_IAC;
1878             newcount ++;
1879         }
1880         *q++ = *p++;
1881         newcount++;
1882         left--;
1883     }
1884     if (appData.debugMode) {
1885         fprintf(debugFP, ">ICS: ");
1886         show_bytes(debugFP, buf, newcount);
1887         fprintf(debugFP, "\n");
1888     }
1889     outcount = OutputToProcess(pr, buf, newcount, outError);
1890     if (outcount < newcount) return -1; /* to be sure */
1891     return count;
1892 }
1893
1894 void
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1896 {
1897     int outError, outCount;
1898     static int gotEof = 0;
1899     static FILE *ini;
1900
1901     /* Pass data read from player on to ICS */
1902     if (count > 0) {
1903         gotEof = 0;
1904         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905         if (outCount < count) {
1906             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1907         }
1908         if(have_sent_ICS_logon == 2) {
1909           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910             fprintf(ini, "%s", message);
1911             have_sent_ICS_logon = 3;
1912           } else
1913             have_sent_ICS_logon = 1;
1914         } else if(have_sent_ICS_logon == 3) {
1915             fprintf(ini, "%s", message);
1916             fclose(ini);
1917           have_sent_ICS_logon = 1;
1918         }
1919     } else if (count < 0) {
1920         RemoveInputSource(isr);
1921         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922     } else if (gotEof++ > 0) {
1923         RemoveInputSource(isr);
1924         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1925     }
1926 }
1927
1928 void
1929 KeepAlive ()
1930 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933     SendToICS("date\n");
1934     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1935 }
1936
1937 /* added routine for printf style output to ics */
1938 void
1939 ics_printf (char *format, ...)
1940 {
1941     char buffer[MSG_SIZ];
1942     va_list args;
1943
1944     va_start(args, format);
1945     vsnprintf(buffer, sizeof(buffer), format, args);
1946     buffer[sizeof(buffer)-1] = '\0';
1947     SendToICS(buffer);
1948     va_end(args);
1949 }
1950
1951 void
1952 SendToICS (char *s)
1953 {
1954     int count, outCount, outError;
1955
1956     if (icsPR == NoProc) return;
1957
1958     count = strlen(s);
1959     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960     if (outCount < count) {
1961         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1962     }
1963 }
1964
1965 /* This is used for sending logon scripts to the ICS. Sending
1966    without a delay causes problems when using timestamp on ICC
1967    (at least on my machine). */
1968 void
1969 SendToICSDelayed (char *s, long msdelay)
1970 {
1971     int count, outCount, outError;
1972
1973     if (icsPR == NoProc) return;
1974
1975     count = strlen(s);
1976     if (appData.debugMode) {
1977         fprintf(debugFP, ">ICS: ");
1978         show_bytes(debugFP, s, count);
1979         fprintf(debugFP, "\n");
1980     }
1981     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1982                                       msdelay);
1983     if (outCount < count) {
1984         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1985     }
1986 }
1987
1988
1989 /* Remove all highlighting escape sequences in s
1990    Also deletes any suffix starting with '('
1991    */
1992 char *
1993 StripHighlightAndTitle (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             if (*s == '(' || *s == '[') {
2005                 *p = NULLCHAR;
2006                 return retbuf;
2007             }
2008             *p++ = *s++;
2009         }
2010     }
2011     *p = NULLCHAR;
2012     return retbuf;
2013 }
2014
2015 /* Remove all highlighting escape sequences in s */
2016 char *
2017 StripHighlight (char *s)
2018 {
2019     static char retbuf[MSG_SIZ];
2020     char *p = retbuf;
2021
2022     while (*s != NULLCHAR) {
2023         while (*s == '\033') {
2024             while (*s != NULLCHAR && !isalpha(*s)) s++;
2025             if (*s != NULLCHAR) s++;
2026         }
2027         while (*s != NULLCHAR && *s != '\033') {
2028             *p++ = *s++;
2029         }
2030     }
2031     *p = NULLCHAR;
2032     return retbuf;
2033 }
2034
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2037 char *
2038 VariantName (VariantClass v)
2039 {
2040     if(v == VariantUnknown || *engineVariant) return engineVariant;
2041     return variantNames[v];
2042 }
2043
2044
2045 /* Identify a variant from the strings the chess servers use or the
2046    PGN Variant tag names we use. */
2047 VariantClass
2048 StringToVariant (char *e)
2049 {
2050     char *p;
2051     int wnum = -1;
2052     VariantClass v = VariantNormal;
2053     int i, found = FALSE;
2054     char buf[MSG_SIZ];
2055     int len;
2056
2057     if (!e) return v;
2058
2059     /* [HGM] skip over optional board-size prefixes */
2060     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062         while( *e++ != '_');
2063     }
2064
2065     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2066         v = VariantNormal;
2067         found = TRUE;
2068     } else
2069     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070       if (StrCaseStr(e, variantNames[i])) {
2071         v = (VariantClass) i;
2072         found = TRUE;
2073         break;
2074       }
2075     }
2076
2077     if (!found) {
2078       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079           || StrCaseStr(e, "wild/fr")
2080           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081         v = VariantFischeRandom;
2082       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083                  (i = 1, p = StrCaseStr(e, "w"))) {
2084         p += i;
2085         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2086         if (isdigit(*p)) {
2087           wnum = atoi(p);
2088         } else {
2089           wnum = -1;
2090         }
2091         switch (wnum) {
2092         case 0: /* FICS only, actually */
2093         case 1:
2094           /* Castling legal even if K starts on d-file */
2095           v = VariantWildCastle;
2096           break;
2097         case 2:
2098         case 3:
2099         case 4:
2100           /* Castling illegal even if K & R happen to start in
2101              normal positions. */
2102           v = VariantNoCastle;
2103           break;
2104         case 5:
2105         case 7:
2106         case 8:
2107         case 10:
2108         case 11:
2109         case 12:
2110         case 13:
2111         case 14:
2112         case 15:
2113         case 18:
2114         case 19:
2115           /* Castling legal iff K & R start in normal positions */
2116           v = VariantNormal;
2117           break;
2118         case 6:
2119         case 20:
2120         case 21:
2121           /* Special wilds for position setup; unclear what to do here */
2122           v = VariantLoadable;
2123           break;
2124         case 9:
2125           /* Bizarre ICC game */
2126           v = VariantTwoKings;
2127           break;
2128         case 16:
2129           v = VariantKriegspiel;
2130           break;
2131         case 17:
2132           v = VariantLosers;
2133           break;
2134         case 22:
2135           v = VariantFischeRandom;
2136           break;
2137         case 23:
2138           v = VariantCrazyhouse;
2139           break;
2140         case 24:
2141           v = VariantBughouse;
2142           break;
2143         case 25:
2144           v = Variant3Check;
2145           break;
2146         case 26:
2147           /* Not quite the same as FICS suicide! */
2148           v = VariantGiveaway;
2149           break;
2150         case 27:
2151           v = VariantAtomic;
2152           break;
2153         case 28:
2154           v = VariantShatranj;
2155           break;
2156
2157         /* Temporary names for future ICC types.  The name *will* change in
2158            the next xboard/WinBoard release after ICC defines it. */
2159         case 29:
2160           v = Variant29;
2161           break;
2162         case 30:
2163           v = Variant30;
2164           break;
2165         case 31:
2166           v = Variant31;
2167           break;
2168         case 32:
2169           v = Variant32;
2170           break;
2171         case 33:
2172           v = Variant33;
2173           break;
2174         case 34:
2175           v = Variant34;
2176           break;
2177         case 35:
2178           v = Variant35;
2179           break;
2180         case 36:
2181           v = Variant36;
2182           break;
2183         case 37:
2184           v = VariantShogi;
2185           break;
2186         case 38:
2187           v = VariantXiangqi;
2188           break;
2189         case 39:
2190           v = VariantCourier;
2191           break;
2192         case 40:
2193           v = VariantGothic;
2194           break;
2195         case 41:
2196           v = VariantCapablanca;
2197           break;
2198         case 42:
2199           v = VariantKnightmate;
2200           break;
2201         case 43:
2202           v = VariantFairy;
2203           break;
2204         case 44:
2205           v = VariantCylinder;
2206           break;
2207         case 45:
2208           v = VariantFalcon;
2209           break;
2210         case 46:
2211           v = VariantCapaRandom;
2212           break;
2213         case 47:
2214           v = VariantBerolina;
2215           break;
2216         case 48:
2217           v = VariantJanus;
2218           break;
2219         case 49:
2220           v = VariantSuper;
2221           break;
2222         case 50:
2223           v = VariantGreat;
2224           break;
2225         case -1:
2226           /* Found "wild" or "w" in the string but no number;
2227              must assume it's normal chess. */
2228           v = VariantNormal;
2229           break;
2230         default:
2231           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232           if( (len >= MSG_SIZ) && appData.debugMode )
2233             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2234
2235           DisplayError(buf, 0);
2236           v = VariantUnknown;
2237           break;
2238         }
2239       }
2240     }
2241     if (appData.debugMode) {
2242       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243               e, wnum, VariantName(v));
2244     }
2245     return v;
2246 }
2247
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2250
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252    advance *index beyond it, and set leftover_start to the new value of
2253    *index; else return FALSE.  If pattern contains the character '*', it
2254    matches any sequence of characters not containing '\r', '\n', or the
2255    character following the '*' (if any), and the matched sequence(s) are
2256    copied into star_match.
2257    */
2258 int
2259 looking_at ( char *buf, int *index, char *pattern)
2260 {
2261     char *bufp = &buf[*index], *patternp = pattern;
2262     int star_count = 0;
2263     char *matchp = star_match[0];
2264
2265     for (;;) {
2266         if (*patternp == NULLCHAR) {
2267             *index = leftover_start = bufp - buf;
2268             *matchp = NULLCHAR;
2269             return TRUE;
2270         }
2271         if (*bufp == NULLCHAR) return FALSE;
2272         if (*patternp == '*') {
2273             if (*bufp == *(patternp + 1)) {
2274                 *matchp = NULLCHAR;
2275                 matchp = star_match[++star_count];
2276                 patternp += 2;
2277                 bufp++;
2278                 continue;
2279             } else if (*bufp == '\n' || *bufp == '\r') {
2280                 patternp++;
2281                 if (*patternp == NULLCHAR)
2282                   continue;
2283                 else
2284                   return FALSE;
2285             } else {
2286                 *matchp++ = *bufp++;
2287                 continue;
2288             }
2289         }
2290         if (*patternp != *bufp) return FALSE;
2291         patternp++;
2292         bufp++;
2293     }
2294 }
2295
2296 void
2297 SendToPlayer (char *data, int length)
2298 {
2299     int error, outCount;
2300     outCount = OutputToProcess(NoProc, data, length, &error);
2301     if (outCount < length) {
2302         DisplayFatalError(_("Error writing to display"), error, 1);
2303     }
2304 }
2305
2306 void
2307 PackHolding (char packed[], char *holding)
2308 {
2309     char *p = holding;
2310     char *q = packed;
2311     int runlength = 0;
2312     int curr = 9999;
2313     do {
2314         if (*p == curr) {
2315             runlength++;
2316         } else {
2317             switch (runlength) {
2318               case 0:
2319                 break;
2320               case 1:
2321                 *q++ = curr;
2322                 break;
2323               case 2:
2324                 *q++ = curr;
2325                 *q++ = curr;
2326                 break;
2327               default:
2328                 sprintf(q, "%d", runlength);
2329                 while (*q) q++;
2330                 *q++ = curr;
2331                 break;
2332             }
2333             runlength = 1;
2334             curr = *p;
2335         }
2336     } while (*p++);
2337     *q = NULLCHAR;
2338 }
2339
2340 /* Telnet protocol requests from the front end */
2341 void
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2343 {
2344     unsigned char msg[3];
2345     int outCount, outError;
2346
2347     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2348
2349     if (appData.debugMode) {
2350         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2351         switch (ddww) {
2352           case TN_DO:
2353             ddwwStr = "DO";
2354             break;
2355           case TN_DONT:
2356             ddwwStr = "DONT";
2357             break;
2358           case TN_WILL:
2359             ddwwStr = "WILL";
2360             break;
2361           case TN_WONT:
2362             ddwwStr = "WONT";
2363             break;
2364           default:
2365             ddwwStr = buf1;
2366             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367             break;
2368         }
2369         switch (option) {
2370           case TN_ECHO:
2371             optionStr = "ECHO";
2372             break;
2373           default:
2374             optionStr = buf2;
2375             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2376             break;
2377         }
2378         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2379     }
2380     msg[0] = TN_IAC;
2381     msg[1] = ddww;
2382     msg[2] = option;
2383     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2384     if (outCount < 3) {
2385         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2386     }
2387 }
2388
2389 void
2390 DoEcho ()
2391 {
2392     if (!appData.icsActive) return;
2393     TelnetRequest(TN_DO, TN_ECHO);
2394 }
2395
2396 void
2397 DontEcho ()
2398 {
2399     if (!appData.icsActive) return;
2400     TelnetRequest(TN_DONT, TN_ECHO);
2401 }
2402
2403 void
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2405 {
2406     /* put the holdings sent to us by the server on the board holdings area */
2407     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2408     char p;
2409     ChessSquare piece;
2410
2411     if(gameInfo.holdingsWidth < 2)  return;
2412     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413         return; // prevent overwriting by pre-board holdings
2414
2415     if( (int)lowestPiece >= BlackPawn ) {
2416         holdingsColumn = 0;
2417         countsColumn = 1;
2418         holdingsStartRow = BOARD_HEIGHT-1;
2419         direction = -1;
2420     } else {
2421         holdingsColumn = BOARD_WIDTH-1;
2422         countsColumn = BOARD_WIDTH-2;
2423         holdingsStartRow = 0;
2424         direction = 1;
2425     }
2426
2427     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428         board[i][holdingsColumn] = EmptySquare;
2429         board[i][countsColumn]   = (ChessSquare) 0;
2430     }
2431     while( (p=*holdings++) != NULLCHAR ) {
2432         piece = CharToPiece( ToUpper(p) );
2433         if(piece == EmptySquare) continue;
2434         /*j = (int) piece - (int) WhitePawn;*/
2435         j = PieceToNumber(piece);
2436         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437         if(j < 0) continue;               /* should not happen */
2438         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440         board[holdingsStartRow+j*direction][countsColumn]++;
2441     }
2442 }
2443
2444
2445 void
2446 VariantSwitch (Board board, VariantClass newVariant)
2447 {
2448    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449    static Board oldBoard;
2450
2451    startedFromPositionFile = FALSE;
2452    if(gameInfo.variant == newVariant) return;
2453
2454    /* [HGM] This routine is called each time an assignment is made to
2455     * gameInfo.variant during a game, to make sure the board sizes
2456     * are set to match the new variant. If that means adding or deleting
2457     * holdings, we shift the playing board accordingly
2458     * This kludge is needed because in ICS observe mode, we get boards
2459     * of an ongoing game without knowing the variant, and learn about the
2460     * latter only later. This can be because of the move list we requested,
2461     * in which case the game history is refilled from the beginning anyway,
2462     * but also when receiving holdings of a crazyhouse game. In the latter
2463     * case we want to add those holdings to the already received position.
2464     */
2465
2466
2467    if (appData.debugMode) {
2468      fprintf(debugFP, "Switch board from %s to %s\n",
2469              VariantName(gameInfo.variant), VariantName(newVariant));
2470      setbuf(debugFP, NULL);
2471    }
2472    shuffleOpenings = 0;       /* [HGM] shuffle */
2473    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2474    switch(newVariant)
2475      {
2476      case VariantShogi:
2477        newWidth = 9;  newHeight = 9;
2478        gameInfo.holdingsSize = 7;
2479      case VariantBughouse:
2480      case VariantCrazyhouse:
2481        newHoldingsWidth = 2; break;
2482      case VariantGreat:
2483        newWidth = 10;
2484      case VariantSuper:
2485        newHoldingsWidth = 2;
2486        gameInfo.holdingsSize = 8;
2487        break;
2488      case VariantGothic:
2489      case VariantCapablanca:
2490      case VariantCapaRandom:
2491        newWidth = 10;
2492      default:
2493        newHoldingsWidth = gameInfo.holdingsSize = 0;
2494      };
2495
2496    if(newWidth  != gameInfo.boardWidth  ||
2497       newHeight != gameInfo.boardHeight ||
2498       newHoldingsWidth != gameInfo.holdingsWidth ) {
2499
2500      /* shift position to new playing area, if needed */
2501      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502        for(i=0; i<BOARD_HEIGHT; i++)
2503          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2505              board[i][j];
2506        for(i=0; i<newHeight; i++) {
2507          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2509        }
2510      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511        for(i=0; i<BOARD_HEIGHT; i++)
2512          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2514              board[i][j];
2515      }
2516      board[HOLDINGS_SET] = 0;
2517      gameInfo.boardWidth  = newWidth;
2518      gameInfo.boardHeight = newHeight;
2519      gameInfo.holdingsWidth = newHoldingsWidth;
2520      gameInfo.variant = newVariant;
2521      InitDrawingSizes(-2, 0);
2522    } else gameInfo.variant = newVariant;
2523    CopyBoard(oldBoard, board);   // remember correctly formatted board
2524      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2525    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2526 }
2527
2528 static int loggedOn = FALSE;
2529
2530 /*-- Game start info cache: --*/
2531 int gs_gamenum;
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\   ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2539
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2542
2543 // [HGM] seekgraph
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2547 #define SQUARE 0x80
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2556
2557 void
2558 PlotSeekAd (int i)
2559 {
2560         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562         if(r < minRating+100 && r >=0 ) r = minRating+100;
2563         if(r > maxRating) r = maxRating;
2564         if(tc < 1.f) tc = 1.f;
2565         if(tc > 95.f) tc = 95.f;
2566         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567         y = ((double)r - minRating)/(maxRating - minRating)
2568             * (h-vMargin-squareSize/8-1) + vMargin;
2569         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570         if(strstr(seekAdList[i], " u ")) color = 1;
2571         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572            !strstr(seekAdList[i], "bullet") &&
2573            !strstr(seekAdList[i], "blitz") &&
2574            !strstr(seekAdList[i], "standard") ) color = 2;
2575         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2577 }
2578
2579 void
2580 PlotSingleSeekAd (int i)
2581 {
2582         PlotSeekAd(i);
2583 }
2584
2585 void
2586 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2587 {
2588         char buf[MSG_SIZ], *ext = "";
2589         VariantClass v = StringToVariant(type);
2590         if(strstr(type, "wild")) {
2591             ext = type + 4; // append wild number
2592             if(v == VariantFischeRandom) type = "chess960"; else
2593             if(v == VariantLoadable) type = "setup"; else
2594             type = VariantName(v);
2595         }
2596         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602             seekNrList[nrOfSeekAds] = nr;
2603             zList[nrOfSeekAds] = 0;
2604             seekAdList[nrOfSeekAds++] = StrSave(buf);
2605             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2606         }
2607 }
2608
2609 void
2610 EraseSeekDot (int i)
2611 {
2612     int x = xList[i], y = yList[i], d=squareSize/4, k;
2613     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615     // now replot every dot that overlapped
2616     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617         int xx = xList[k], yy = yList[k];
2618         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619             DrawSeekDot(xx, yy, colorList[k]);
2620     }
2621 }
2622
2623 void
2624 RemoveSeekAd (int nr)
2625 {
2626         int i;
2627         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2628             EraseSeekDot(i);
2629             if(seekAdList[i]) free(seekAdList[i]);
2630             seekAdList[i] = seekAdList[--nrOfSeekAds];
2631             seekNrList[i] = seekNrList[nrOfSeekAds];
2632             ratingList[i] = ratingList[nrOfSeekAds];
2633             colorList[i]  = colorList[nrOfSeekAds];
2634             tcList[i] = tcList[nrOfSeekAds];
2635             xList[i]  = xList[nrOfSeekAds];
2636             yList[i]  = yList[nrOfSeekAds];
2637             zList[i]  = zList[nrOfSeekAds];
2638             seekAdList[nrOfSeekAds] = NULL;
2639             break;
2640         }
2641 }
2642
2643 Boolean
2644 MatchSoughtLine (char *line)
2645 {
2646     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647     int nr, base, inc, u=0; char dummy;
2648
2649     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2651        (u=1) &&
2652        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2654         // match: compact and save the line
2655         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2656         return TRUE;
2657     }
2658     return FALSE;
2659 }
2660
2661 int
2662 DrawSeekGraph ()
2663 {
2664     int i;
2665     if(!seekGraphUp) return FALSE;
2666     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2668
2669     DrawSeekBackground(0, 0, w, h);
2670     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2674         yy = h-1-yy;
2675         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2676         if(i%500 == 0) {
2677             char buf[MSG_SIZ];
2678             snprintf(buf, MSG_SIZ, "%d", i);
2679             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2680         }
2681     }
2682     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683     for(i=1; i<100; i+=(i<10?1:5)) {
2684         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2690         }
2691     }
2692     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2693     return TRUE;
2694 }
2695
2696 int
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2698 {
2699     static int lastDown = 0, displayed = 0, lastSecond;
2700     if(y < 0) return FALSE;
2701     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703         if(!seekGraphUp) return FALSE;
2704         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705         DrawPosition(TRUE, NULL);
2706         return TRUE;
2707     }
2708     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709         if(click == Release || moving) return FALSE;
2710         nrOfSeekAds = 0;
2711         soughtPending = TRUE;
2712         SendToICS(ics_prefix);
2713         SendToICS("sought\n"); // should this be "sought all"?
2714     } else { // issue challenge based on clicked ad
2715         int dist = 10000; int i, closest = 0, second = 0;
2716         for(i=0; i<nrOfSeekAds; i++) {
2717             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2718             if(d < dist) { dist = d; closest = i; }
2719             second += (d - zList[i] < 120); // count in-range ads
2720             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2721         }
2722         if(dist < 120) {
2723             char buf[MSG_SIZ];
2724             second = (second > 1);
2725             if(displayed != closest || second != lastSecond) {
2726                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727                 lastSecond = second; displayed = closest;
2728             }
2729             if(click == Press) {
2730                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2731                 lastDown = closest;
2732                 return TRUE;
2733             } // on press 'hit', only show info
2734             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736             SendToICS(ics_prefix);
2737             SendToICS(buf);
2738             return TRUE; // let incoming board of started game pop down the graph
2739         } else if(click == Release) { // release 'miss' is ignored
2740             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741             if(moving == 2) { // right up-click
2742                 nrOfSeekAds = 0; // refresh graph
2743                 soughtPending = TRUE;
2744                 SendToICS(ics_prefix);
2745                 SendToICS("sought\n"); // should this be "sought all"?
2746             }
2747             return TRUE;
2748         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749         // press miss or release hit 'pop down' seek graph
2750         seekGraphUp = FALSE;
2751         DrawPosition(TRUE, NULL);
2752     }
2753     return TRUE;
2754 }
2755
2756 void
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2758 {
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2768
2769     static int started = STARTED_NONE;
2770     static char parse[20000];
2771     static int parse_pos = 0;
2772     static char buf[BUF_SIZE + 1];
2773     static int firstTime = TRUE, intfSet = FALSE;
2774     static ColorClass prevColor = ColorNormal;
2775     static int savingComment = FALSE;
2776     static int cmatch = 0; // continuation sequence match
2777     char *bp;
2778     char str[MSG_SIZ];
2779     int i, oldi;
2780     int buf_len;
2781     int next_out;
2782     int tkind;
2783     int backup;    /* [DM] For zippy color lines */
2784     char *p;
2785     char talker[MSG_SIZ]; // [HGM] chat
2786     int channel;
2787
2788     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2789
2790     if (appData.debugMode) {
2791       if (!error) {
2792         fprintf(debugFP, "<ICS: ");
2793         show_bytes(debugFP, data, count);
2794         fprintf(debugFP, "\n");
2795       }
2796     }
2797
2798     if (appData.debugMode) { int f = forwardMostMove;
2799         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2802     }
2803     if (count > 0) {
2804         /* If last read ended with a partial line that we couldn't parse,
2805            prepend it to the new read and try again. */
2806         if (leftover_len > 0) {
2807             for (i=0; i<leftover_len; i++)
2808               buf[i] = buf[leftover_start + i];
2809         }
2810
2811     /* copy new characters into the buffer */
2812     bp = buf + leftover_len;
2813     buf_len=leftover_len;
2814     for (i=0; i<count; i++)
2815     {
2816         // ignore these
2817         if (data[i] == '\r')
2818             continue;
2819
2820         // join lines split by ICS?
2821         if (!appData.noJoin)
2822         {
2823             /*
2824                 Joining just consists of finding matches against the
2825                 continuation sequence, and discarding that sequence
2826                 if found instead of copying it.  So, until a match
2827                 fails, there's nothing to do since it might be the
2828                 complete sequence, and thus, something we don't want
2829                 copied.
2830             */
2831             if (data[i] == cont_seq[cmatch])
2832             {
2833                 cmatch++;
2834                 if (cmatch == strlen(cont_seq))
2835                 {
2836                     cmatch = 0; // complete match.  just reset the counter
2837
2838                     /*
2839                         it's possible for the ICS to not include the space
2840                         at the end of the last word, making our [correct]
2841                         join operation fuse two separate words.  the server
2842                         does this when the space occurs at the width setting.
2843                     */
2844                     if (!buf_len || buf[buf_len-1] != ' ')
2845                     {
2846                         *bp++ = ' ';
2847                         buf_len++;
2848                     }
2849                 }
2850                 continue;
2851             }
2852             else if (cmatch)
2853             {
2854                 /*
2855                     match failed, so we have to copy what matched before
2856                     falling through and copying this character.  In reality,
2857                     this will only ever be just the newline character, but
2858                     it doesn't hurt to be precise.
2859                 */
2860                 strncpy(bp, cont_seq, cmatch);
2861                 bp += cmatch;
2862                 buf_len += cmatch;
2863                 cmatch = 0;
2864             }
2865         }
2866
2867         // copy this char
2868         *bp++ = data[i];
2869         buf_len++;
2870     }
2871
2872         buf[buf_len] = NULLCHAR;
2873 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2874         next_out = 0;
2875         leftover_start = 0;
2876
2877         i = 0;
2878         while (i < buf_len) {
2879             /* Deal with part of the TELNET option negotiation
2880                protocol.  We refuse to do anything beyond the
2881                defaults, except that we allow the WILL ECHO option,
2882                which ICS uses to turn off password echoing when we are
2883                directly connected to it.  We reject this option
2884                if localLineEditing mode is on (always on in xboard)
2885                and we are talking to port 23, which might be a real
2886                telnet server that will try to keep WILL ECHO on permanently.
2887              */
2888             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890                 unsigned char option;
2891                 oldi = i;
2892                 switch ((unsigned char) buf[++i]) {
2893                   case TN_WILL:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<WILL ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       case TN_ECHO:
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "ECHO ");
2900                         /* Reply only if this is a change, according
2901                            to the protocol rules. */
2902                         if (remoteEchoOption) break;
2903                         if (appData.localLineEditing &&
2904                             atoi(appData.icsPort) == TN_PORT) {
2905                             TelnetRequest(TN_DONT, TN_ECHO);
2906                         } else {
2907                             EchoOff();
2908                             TelnetRequest(TN_DO, TN_ECHO);
2909                             remoteEchoOption = TRUE;
2910                         }
2911                         break;
2912                       default:
2913                         if (appData.debugMode)
2914                           fprintf(debugFP, "%d ", option);
2915                         /* Whatever this is, we don't want it. */
2916                         TelnetRequest(TN_DONT, option);
2917                         break;
2918                     }
2919                     break;
2920                   case TN_WONT:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WONT ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (!remoteEchoOption) break;
2930                         EchoOn();
2931                         TelnetRequest(TN_DONT, TN_ECHO);
2932                         remoteEchoOption = FALSE;
2933                         break;
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", (unsigned char) option);
2937                         /* Whatever this is, it must already be turned
2938                            off, because we never agree to turn on
2939                            anything non-default, so according to the
2940                            protocol rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_DO:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<DO ");
2947                     switch (option = (unsigned char) buf[++i]) {
2948                       default:
2949                         /* Whatever this is, we refuse to do it. */
2950                         if (appData.debugMode)
2951                           fprintf(debugFP, "%d ", option);
2952                         TelnetRequest(TN_WONT, option);
2953                         break;
2954                     }
2955                     break;
2956                   case TN_DONT:
2957                     if (appData.debugMode)
2958                       fprintf(debugFP, "\n<DONT ");
2959                     switch (option = (unsigned char) buf[++i]) {
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we are already not doing
2964                            it, because we never agree to do anything
2965                            non-default, so according to the protocol
2966                            rules, we don't reply. */
2967                         break;
2968                     }
2969                     break;
2970                   case TN_IAC:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<IAC ");
2973                     /* Doubled IAC; pass it through */
2974                     i--;
2975                     break;
2976                   default:
2977                     if (appData.debugMode)
2978                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979                     /* Drop all other telnet commands on the floor */
2980                     break;
2981                 }
2982                 if (oldi > next_out)
2983                   SendToPlayer(&buf[next_out], oldi - next_out);
2984                 if (++i > next_out)
2985                   next_out = i;
2986                 continue;
2987             }
2988
2989             /* OK, this at least will *usually* work */
2990             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2991                 loggedOn = TRUE;
2992             }
2993
2994             if (loggedOn && !intfSet) {
2995                 if (ics_type == ICS_ICC) {
2996                   snprintf(str, MSG_SIZ,
2997                           "/set-quietly interface %s\n/set-quietly style 12\n",
2998                           programVersion);
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3001                 } else if (ics_type == ICS_CHESSNET) {
3002                   snprintf(str, MSG_SIZ, "/style 12\n");
3003                 } else {
3004                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005                   strcat(str, programVersion);
3006                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3009 #ifdef WIN32
3010                   strcat(str, "$iset nohighlight 1\n");
3011 #endif
3012                   strcat(str, "$iset lock 1\n$style 12\n");
3013                 }
3014                 SendToICS(str);
3015                 NotifyFrontendLogin();
3016                 intfSet = TRUE;
3017             }
3018
3019             if (started == STARTED_COMMENT) {
3020                 /* Accumulate characters in comment */
3021                 parse[parse_pos++] = buf[i];
3022                 if (buf[i] == '\n') {
3023                     parse[parse_pos] = NULLCHAR;
3024                     if(chattingPartner>=0) {
3025                         char mess[MSG_SIZ];
3026                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027                         OutputChatMessage(chattingPartner, mess);
3028                         chattingPartner = -1;
3029                         next_out = i+1; // [HGM] suppress printing in ICS window
3030                     } else
3031                     if(!suppressKibitz) // [HGM] kibitz
3032                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034                         int nrDigit = 0, nrAlph = 0, j;
3035                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037                         parse[parse_pos] = NULLCHAR;
3038                         // try to be smart: if it does not look like search info, it should go to
3039                         // ICS interaction window after all, not to engine-output window.
3040                         for(j=0; j<parse_pos; j++) { // count letters and digits
3041                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3043                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3044                         }
3045                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046                             int depth=0; float score;
3047                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049                                 pvInfoList[forwardMostMove-1].depth = depth;
3050                                 pvInfoList[forwardMostMove-1].score = 100*score;
3051                             }
3052                             OutputKibitz(suppressKibitz, parse);
3053                         } else {
3054                             char tmp[MSG_SIZ];
3055                             if(gameMode == IcsObserving) // restore original ICS messages
3056                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3058                             else
3059                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061                             SendToPlayer(tmp, strlen(tmp));
3062                         }
3063                         next_out = i+1; // [HGM] suppress printing in ICS window
3064                     }
3065                     started = STARTED_NONE;
3066                 } else {
3067                     /* Don't match patterns against characters in comment */
3068                     i++;
3069                     continue;
3070                 }
3071             }
3072             if (started == STARTED_CHATTER) {
3073                 if (buf[i] != '\n') {
3074                     /* Don't match patterns against characters in chatter */
3075                     i++;
3076                     continue;
3077                 }
3078                 started = STARTED_NONE;
3079                 if(suppressKibitz) next_out = i+1;
3080             }
3081
3082             /* Kludge to deal with rcmd protocol */
3083             if (firstTime && looking_at(buf, &i, "\001*")) {
3084                 DisplayFatalError(&buf[1], 0, 1);
3085                 continue;
3086             } else {
3087                 firstTime = FALSE;
3088             }
3089
3090             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3091                 ics_type = ICS_ICC;
3092                 ics_prefix = "/";
3093                 if (appData.debugMode)
3094                   fprintf(debugFP, "ics_type %d\n", ics_type);
3095                 continue;
3096             }
3097             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098                 ics_type = ICS_FICS;
3099                 ics_prefix = "$";
3100                 if (appData.debugMode)
3101                   fprintf(debugFP, "ics_type %d\n", ics_type);
3102                 continue;
3103             }
3104             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105                 ics_type = ICS_CHESSNET;
3106                 ics_prefix = "/";
3107                 if (appData.debugMode)
3108                   fprintf(debugFP, "ics_type %d\n", ics_type);
3109                 continue;
3110             }
3111
3112             if (!loggedOn &&
3113                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3115                  looking_at(buf, &i, "will be \"*\""))) {
3116               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3117               continue;
3118             }
3119
3120             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3121               char buf[MSG_SIZ];
3122               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123               DisplayIcsInteractionTitle(buf);
3124               have_set_title = TRUE;
3125             }
3126
3127             /* skip finger notes */
3128             if (started == STARTED_NONE &&
3129                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130                  (buf[i] == '1' && buf[i+1] == '0')) &&
3131                 buf[i+2] == ':' && buf[i+3] == ' ') {
3132               started = STARTED_CHATTER;
3133               i += 3;
3134               continue;
3135             }
3136
3137             oldi = i;
3138             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139             if(appData.seekGraph) {
3140                 if(soughtPending && MatchSoughtLine(buf+i)) {
3141                     i = strstr(buf+i, "rated") - buf;
3142                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                     next_out = leftover_start = i;
3144                     started = STARTED_CHATTER;
3145                     suppressKibitz = TRUE;
3146                     continue;
3147                 }
3148                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149                         && looking_at(buf, &i, "* ads displayed")) {
3150                     soughtPending = FALSE;
3151                     seekGraphUp = TRUE;
3152                     DrawSeekGraph();
3153                     continue;
3154                 }
3155                 if(appData.autoRefresh) {
3156                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157                         int s = (ics_type == ICS_ICC); // ICC format differs
3158                         if(seekGraphUp)
3159                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161                         looking_at(buf, &i, "*% "); // eat prompt
3162                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = i; // suppress
3165                         continue;
3166                     }
3167                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168                         char *p = star_match[0];
3169                         while(*p) {
3170                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3171                             while(*p && *p++ != ' '); // next
3172                         }
3173                         looking_at(buf, &i, "*% "); // eat prompt
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         continue;
3177                     }
3178                 }
3179             }
3180
3181             /* skip formula vars */
3182             if (started == STARTED_NONE &&
3183                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184               started = STARTED_CHATTER;
3185               i += 3;
3186               continue;
3187             }
3188
3189             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190             if (appData.autoKibitz && started == STARTED_NONE &&
3191                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3192                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3197                         suppressKibitz = TRUE;
3198                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = i;
3200                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201                                 && (gameMode == IcsPlayingWhite)) ||
3202                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3204                             started = STARTED_CHATTER; // own kibitz we simply discard
3205                         else {
3206                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207                             parse_pos = 0; parse[0] = NULLCHAR;
3208                             savingComment = TRUE;
3209                             suppressKibitz = gameMode != IcsObserving ? 2 :
3210                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3211                         }
3212                         continue;
3213                 } else
3214                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216                          && atoi(star_match[0])) {
3217                     // suppress the acknowledgements of our own autoKibitz
3218                     char *p;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221                     SendToPlayer(star_match[0], strlen(star_match[0]));
3222                     if(looking_at(buf, &i, "*% ")) // eat prompt
3223                         suppressKibitz = FALSE;
3224                     next_out = i;
3225                     continue;
3226                 }
3227             } // [HGM] kibitz: end of patch
3228
3229             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3230
3231             // [HGM] chat: intercept tells by users for which we have an open chat window
3232             channel = -1;
3233             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234                                            looking_at(buf, &i, "* whispers:") ||
3235                                            looking_at(buf, &i, "* kibitzes:") ||
3236                                            looking_at(buf, &i, "* shouts:") ||
3237                                            looking_at(buf, &i, "* c-shouts:") ||
3238                                            looking_at(buf, &i, "--> * ") ||
3239                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3243                 int p;
3244                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245                 chattingPartner = -1;
3246
3247                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250                     talker[0] = '['; strcat(talker, "] ");
3251                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252                     chattingPartner = p; break;
3253                     }
3254                 } else
3255                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256                 for(p=0; p<MAX_CHAT; p++) {
3257                     if(!strcmp("kibitzes", chatPartner[p])) {
3258                         talker[0] = '['; strcat(talker, "] ");
3259                         chattingPartner = p; break;
3260                     }
3261                 } else
3262                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263                 for(p=0; p<MAX_CHAT; p++) {
3264                     if(!strcmp("whispers", chatPartner[p])) {
3265                         talker[0] = '['; strcat(talker, "] ");
3266                         chattingPartner = p; break;
3267                     }
3268                 } else
3269                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270                   if(buf[i-8] == '-' && buf[i-3] == 't')
3271                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272                     if(!strcmp("c-shouts", chatPartner[p])) {
3273                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274                         chattingPartner = p; break;
3275                     }
3276                   }
3277                   if(chattingPartner < 0)
3278                   for(p=0; p<MAX_CHAT; p++) {
3279                     if(!strcmp("shouts", chatPartner[p])) {
3280                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283                         chattingPartner = p; break;
3284                     }
3285                   }
3286                 }
3287                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289                     talker[0] = 0; Colorize(ColorTell, FALSE);
3290                     chattingPartner = p; break;
3291                 }
3292                 if(chattingPartner<0) i = oldi; else {
3293                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296                     started = STARTED_COMMENT;
3297                     parse_pos = 0; parse[0] = NULLCHAR;
3298                     savingComment = 3 + chattingPartner; // counts as TRUE
3299                     suppressKibitz = TRUE;
3300                     continue;
3301                 }
3302             } // [HGM] chat: end of patch
3303
3304           backup = i;
3305             if (appData.zippyTalk || appData.zippyPlay) {
3306                 /* [DM] Backup address for color zippy lines */
3307 #if ZIPPY
3308                if (loggedOn == TRUE)
3309                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3311 #endif
3312             } // [DM] 'else { ' deleted
3313                 if (
3314                     /* Regular tells and says */
3315                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3317                     looking_at(buf, &i, "* says: ") ||
3318                     /* Don't color "message" or "messages" output */
3319                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320                     looking_at(buf, &i, "*. * at *:*: ") ||
3321                     looking_at(buf, &i, "--* (*:*): ") ||
3322                     /* Message notifications (same color as tells) */
3323                     looking_at(buf, &i, "* has left a message ") ||
3324                     looking_at(buf, &i, "* just sent you a message:\n") ||
3325                     /* Whispers and kibitzes */
3326                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327                     looking_at(buf, &i, "* kibitzes: ") ||
3328                     /* Channel tells */
3329                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3330
3331                   if (tkind == 1 && strchr(star_match[0], ':')) {
3332                       /* Avoid "tells you:" spoofs in channels */
3333                      tkind = 3;
3334                   }
3335                   if (star_match[0][0] == NULLCHAR ||
3336                       strchr(star_match[0], ' ') ||
3337                       (tkind == 3 && strchr(star_match[1], ' '))) {
3338                     /* Reject bogus matches */
3339                     i = oldi;
3340                   } else {
3341                     if (appData.colorize) {
3342                       if (oldi > next_out) {
3343                         SendToPlayer(&buf[next_out], oldi - next_out);
3344                         next_out = oldi;
3345                       }
3346                       switch (tkind) {
3347                       case 1:
3348                         Colorize(ColorTell, FALSE);
3349                         curColor = ColorTell;
3350                         break;
3351                       case 2:
3352                         Colorize(ColorKibitz, FALSE);
3353                         curColor = ColorKibitz;
3354                         break;
3355                       case 3:
3356                         p = strrchr(star_match[1], '(');
3357                         if (p == NULL) {
3358                           p = star_match[1];
3359                         } else {
3360                           p++;
3361                         }
3362                         if (atoi(p) == 1) {
3363                           Colorize(ColorChannel1, FALSE);
3364                           curColor = ColorChannel1;
3365                         } else {
3366                           Colorize(ColorChannel, FALSE);
3367                           curColor = ColorChannel;
3368                         }
3369                         break;
3370                       case 5:
3371                         curColor = ColorNormal;
3372                         break;
3373                       }
3374                     }
3375                     if (started == STARTED_NONE && appData.autoComment &&
3376                         (gameMode == IcsObserving ||
3377                          gameMode == IcsPlayingWhite ||
3378                          gameMode == IcsPlayingBlack)) {
3379                       parse_pos = i - oldi;
3380                       memcpy(parse, &buf[oldi], parse_pos);
3381                       parse[parse_pos] = NULLCHAR;
3382                       started = STARTED_COMMENT;
3383                       savingComment = TRUE;
3384                     } else {
3385                       started = STARTED_CHATTER;
3386                       savingComment = FALSE;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                   }
3391                 }
3392
3393                 if (looking_at(buf, &i, "* s-shouts: ") ||
3394                     looking_at(buf, &i, "* c-shouts: ")) {
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorSShout, FALSE);
3401                         curColor = ColorSShout;
3402                     }
3403                     loggedOn = TRUE;
3404                     started = STARTED_CHATTER;
3405                     continue;
3406                 }
3407
3408                 if (looking_at(buf, &i, "--->")) {
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* shouts: ") ||
3414                     looking_at(buf, &i, "--> ")) {
3415                     if (appData.colorize) {
3416                         if (oldi > next_out) {
3417                             SendToPlayer(&buf[next_out], oldi - next_out);
3418                             next_out = oldi;
3419                         }
3420                         Colorize(ColorShout, FALSE);
3421                         curColor = ColorShout;
3422                     }
3423                     loggedOn = TRUE;
3424                     started = STARTED_CHATTER;
3425                     continue;
3426                 }
3427
3428                 if (looking_at( buf, &i, "Challenge:")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorChallenge, FALSE);
3435                         curColor = ColorChallenge;
3436                     }
3437                     loggedOn = TRUE;
3438                     continue;
3439                 }
3440
3441                 if (looking_at(buf, &i, "* offers you") ||
3442                     looking_at(buf, &i, "* offers to be") ||
3443                     looking_at(buf, &i, "* would like to") ||
3444                     looking_at(buf, &i, "* requests to") ||
3445                     looking_at(buf, &i, "Your opponent offers") ||
3446                     looking_at(buf, &i, "Your opponent requests")) {
3447
3448                     if (appData.colorize) {
3449                         if (oldi > next_out) {
3450                             SendToPlayer(&buf[next_out], oldi - next_out);
3451                             next_out = oldi;
3452                         }
3453                         Colorize(ColorRequest, FALSE);
3454                         curColor = ColorRequest;
3455                     }
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* (*) seeking")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorSeek, FALSE);
3466                         curColor = ColorSeek;
3467                     }
3468                     continue;
3469             }
3470
3471           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3472
3473             if (looking_at(buf, &i, "\\   ")) {
3474                 if (prevColor != ColorNormal) {
3475                     if (oldi > next_out) {
3476                         SendToPlayer(&buf[next_out], oldi - next_out);
3477                         next_out = oldi;
3478                     }
3479                     Colorize(prevColor, TRUE);
3480                     curColor = prevColor;
3481                 }
3482                 if (savingComment) {
3483                     parse_pos = i - oldi;
3484                     memcpy(parse, &buf[oldi], parse_pos);
3485                     parse[parse_pos] = NULLCHAR;
3486                     started = STARTED_COMMENT;
3487                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488                         chattingPartner = savingComment - 3; // kludge to remember the box
3489                 } else {
3490                     started = STARTED_CHATTER;
3491                 }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "Black Strength :") ||
3496                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497                 looking_at(buf, &i, "<10>") ||
3498                 looking_at(buf, &i, "#@#")) {
3499                 /* Wrong board style */
3500                 loggedOn = TRUE;
3501                 SendToICS(ics_prefix);
3502                 SendToICS("set style 12\n");
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i, "login:")) {
3509               if (!have_sent_ICS_logon) {
3510                 if(ICSInitScript())
3511                   have_sent_ICS_logon = 1;
3512                 else // no init script was found
3513                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3516               }
3517                 continue;
3518             }
3519
3520             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521                 (looking_at(buf, &i, "\n<12> ") ||
3522                  looking_at(buf, &i, "<12> "))) {
3523                 loggedOn = TRUE;
3524                 if (oldi > next_out) {
3525                     SendToPlayer(&buf[next_out], oldi - next_out);
3526                 }
3527                 next_out = i;
3528                 started = STARTED_BOARD;
3529                 parse_pos = 0;
3530                 continue;
3531             }
3532
3533             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534                 looking_at(buf, &i, "<b1> ")) {
3535                 if (oldi > next_out) {
3536                     SendToPlayer(&buf[next_out], oldi - next_out);
3537                 }
3538                 next_out = i;
3539                 started = STARTED_HOLDINGS;
3540                 parse_pos = 0;
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3545                 loggedOn = TRUE;
3546                 /* Header for a move list -- first line */
3547
3548                 switch (ics_getting_history) {
3549                   case H_FALSE:
3550                     switch (gameMode) {
3551                       case IcsIdle:
3552                       case BeginningOfGame:
3553                         /* User typed "moves" or "oldmoves" while we
3554                            were idle.  Pretend we asked for these
3555                            moves and soak them up so user can step
3556                            through them and/or save them.
3557                            */
3558                         Reset(FALSE, TRUE);
3559                         gameMode = IcsObserving;
3560                         ModeHighlight();
3561                         ics_gamenum = -1;
3562                         ics_getting_history = H_GOT_UNREQ_HEADER;
3563                         break;
3564                       case EditGame: /*?*/
3565                       case EditPosition: /*?*/
3566                         /* Should above feature work in these modes too? */
3567                         /* For now it doesn't */
3568                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3569                         break;
3570                       default:
3571                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3572                         break;
3573                     }
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Is this the right one? */
3577                     if (gameInfo.white && gameInfo.black &&
3578                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3579                         strcmp(gameInfo.black, star_match[2]) == 0) {
3580                         /* All is well */
3581                         ics_getting_history = H_GOT_REQ_HEADER;
3582                     }
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                   case H_GOT_UNREQ_HEADER:
3586                   case H_GOT_UNWANTED_HEADER:
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: two headers"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                 }
3593
3594                 /* Save player ratings into gameInfo if needed */
3595                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597                     (gameInfo.whiteRating == -1 ||
3598                      gameInfo.blackRating == -1)) {
3599
3600                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3601                     gameInfo.blackRating = string_to_rating(star_match[3]);
3602                     if (appData.debugMode)
3603                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604                               gameInfo.whiteRating, gameInfo.blackRating);
3605                 }
3606                 continue;
3607             }
3608
3609             if (looking_at(buf, &i,
3610               "* * match, initial time: * minute*, increment: * second")) {
3611                 /* Header for a move list -- second line */
3612                 /* Initial board will follow if this is a wild game */
3613                 if (gameInfo.event != NULL) free(gameInfo.event);
3614                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615                 gameInfo.event = StrSave(str);
3616                 /* [HGM] we switched variant. Translate boards if needed. */
3617                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3618                 continue;
3619             }
3620
3621             if (looking_at(buf, &i, "Move  ")) {
3622                 /* Beginning of a move list */
3623                 switch (ics_getting_history) {
3624                   case H_FALSE:
3625                     /* Normally should not happen */
3626                     /* Maybe user hit reset while we were parsing */
3627                     break;
3628                   case H_REQUESTED:
3629                     /* Happens if we are ignoring a move list that is not
3630                      * the one we just requested.  Common if the user
3631                      * tries to observe two games without turning off
3632                      * getMoveList */
3633                     break;
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: nested"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                   case H_GOT_REQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES;
3642                     parse_pos = 0;
3643                     if (oldi > next_out) {
3644                         SendToPlayer(&buf[next_out], oldi - next_out);
3645                     }
3646                     break;
3647                   case H_GOT_UNREQ_HEADER:
3648                     ics_getting_history = H_GETTING_MOVES;
3649                     started = STARTED_MOVES_NOHIDE;
3650                     parse_pos = 0;
3651                     break;
3652                   case H_GOT_UNWANTED_HEADER:
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655                 }
3656                 continue;
3657             }
3658
3659             if (looking_at(buf, &i, "% ") ||
3660                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663                     soughtPending = FALSE;
3664                     seekGraphUp = TRUE;
3665                     DrawSeekGraph();
3666                 }
3667                 if(suppressKibitz) next_out = i;
3668                 savingComment = FALSE;
3669                 suppressKibitz = 0;
3670                 switch (started) {
3671                   case STARTED_MOVES:
3672                   case STARTED_MOVES_NOHIDE:
3673                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674                     parse[parse_pos + i - oldi] = NULLCHAR;
3675                     ParseGameHistory(parse);
3676 #if ZIPPY
3677                     if (appData.zippyPlay && first.initDone) {
3678                         FeedMovesToProgram(&first, forwardMostMove);
3679                         if (gameMode == IcsPlayingWhite) {
3680                             if (WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("black\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, TRUE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, TRUE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         } else if (gameMode == IcsPlayingBlack) {
3704                             if (!WhiteOnMove(forwardMostMove)) {
3705                                 if (first.sendTime) {
3706                                   if (first.useColors) {
3707                                     SendToProgram("white\n", &first);
3708                                   }
3709                                   SendTimeRemaining(&first, FALSE);
3710                                 }
3711                                 if (first.useColors) {
3712                                   SendToProgram("black\n", &first);
3713                                 }
3714                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715                                 first.maybeThinking = TRUE;
3716                             } else {
3717                                 if (first.usePlayother) {
3718                                   if (first.sendTime) {
3719                                     SendTimeRemaining(&first, FALSE);
3720                                   }
3721                                   SendToProgram("playother\n", &first);
3722                                   firstMove = FALSE;
3723                                 } else {
3724                                   firstMove = TRUE;
3725                                 }
3726                             }
3727                         }
3728                     }
3729 #endif
3730                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3731                         /* Moves came from oldmoves or moves command
3732                            while we weren't doing anything else.
3733                            */
3734                         currentMove = forwardMostMove;
3735                         ClearHighlights();/*!!could figure this out*/
3736                         flipView = appData.flipView;
3737                         DrawPosition(TRUE, boards[currentMove]);
3738                         DisplayBothClocks();
3739                         snprintf(str, MSG_SIZ, "%s %s %s",
3740                                 gameInfo.white, _("vs."),  gameInfo.black);
3741                         DisplayTitle(str);
3742                         gameMode = IcsIdle;
3743                     } else {
3744                         /* Moves were history of an active game */
3745                         if (gameInfo.resultDetails != NULL) {
3746                             free(gameInfo.resultDetails);
3747                             gameInfo.resultDetails = NULL;
3748                         }
3749                     }
3750                     HistorySet(parseList, backwardMostMove,
3751                                forwardMostMove, currentMove-1);
3752                     DisplayMove(currentMove - 1);
3753                     if (started == STARTED_MOVES) next_out = i;
3754                     started = STARTED_NONE;
3755                     ics_getting_history = H_FALSE;
3756                     break;
3757
3758                   case STARTED_OBSERVE:
3759                     started = STARTED_NONE;
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                     break;
3763
3764                   default:
3765                     break;
3766                 }
3767                 if(bookHit) { // [HGM] book: simulate book reply
3768                     static char bookMove[MSG_SIZ]; // a bit generous?
3769
3770                     programStats.nodes = programStats.depth = programStats.time =
3771                     programStats.score = programStats.got_only_move = 0;
3772                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3773
3774                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775                     strcat(bookMove, bookHit);
3776                     HandleMachineMove(bookMove, &first);
3777                 }
3778                 continue;
3779             }
3780
3781             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782                  started == STARTED_HOLDINGS ||
3783                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784                 /* Accumulate characters in move list or board */
3785                 parse[parse_pos++] = buf[i];
3786             }
3787
3788             /* Start of game messages.  Mostly we detect start of game
3789                when the first board image arrives.  On some versions
3790                of the ICS, though, we need to do a "refresh" after starting
3791                to observe in order to get the current board right away. */
3792             if (looking_at(buf, &i, "Adding game * to observation list")) {
3793                 started = STARTED_OBSERVE;
3794                 continue;
3795             }
3796
3797             /* Handle auto-observe */
3798             if (appData.autoObserve &&
3799                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3801                 char *player;
3802                 /* Choose the player that was highlighted, if any. */
3803                 if (star_match[0][0] == '\033' ||
3804                     star_match[1][0] != '\033') {
3805                     player = star_match[0];
3806                 } else {
3807                     player = star_match[2];
3808                 }
3809                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810                         ics_prefix, StripHighlightAndTitle(player));
3811                 SendToICS(str);
3812
3813                 /* Save ratings from notify string */
3814                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815                 player1Rating = string_to_rating(star_match[1]);
3816                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817                 player2Rating = string_to_rating(star_match[3]);
3818
3819                 if (appData.debugMode)
3820                   fprintf(debugFP,
3821                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3822                           player1Name, player1Rating,
3823                           player2Name, player2Rating);
3824
3825                 continue;
3826             }
3827
3828             /* Deal with automatic examine mode after a game,
3829                and with IcsObserving -> IcsExamining transition */
3830             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831                 looking_at(buf, &i, "has made you an examiner of game *")) {
3832
3833                 int gamenum = atoi(star_match[0]);
3834                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835                     gamenum == ics_gamenum) {
3836                     /* We were already playing or observing this game;
3837                        no need to refetch history */
3838                     gameMode = IcsExamining;
3839                     if (pausing) {
3840                         pauseExamForwardMostMove = forwardMostMove;
3841                     } else if (currentMove < forwardMostMove) {
3842                         ForwardInner(forwardMostMove);
3843                     }
3844                 } else {
3845                     /* I don't think this case really can happen */
3846                     SendToICS(ics_prefix);
3847                     SendToICS("refresh\n");
3848                 }
3849                 continue;
3850             }
3851
3852             /* Error messages */
3853 //          if (ics_user_moved) {
3854             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855                 if (looking_at(buf, &i, "Illegal move") ||
3856                     looking_at(buf, &i, "Not a legal move") ||
3857                     looking_at(buf, &i, "Your king is in check") ||
3858                     looking_at(buf, &i, "It isn't your turn") ||
3859                     looking_at(buf, &i, "It is not your move")) {
3860                     /* Illegal move */
3861                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862                         currentMove = forwardMostMove-1;
3863                         DisplayMove(currentMove - 1); /* before DMError */
3864                         DrawPosition(FALSE, boards[currentMove]);
3865                         SwitchClocks(forwardMostMove-1); // [HGM] race
3866                         DisplayBothClocks();
3867                     }
3868                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3869                     ics_user_moved = 0;
3870                     continue;
3871                 }
3872             }
3873
3874             if (looking_at(buf, &i, "still have time") ||
3875                 looking_at(buf, &i, "not out of time") ||
3876                 looking_at(buf, &i, "either player is out of time") ||
3877                 looking_at(buf, &i, "has timeseal; checking")) {
3878                 /* We must have called his flag a little too soon */
3879                 whiteFlag = blackFlag = FALSE;
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "added * seconds to") ||
3884                 looking_at(buf, &i, "seconds were added to")) {
3885                 /* Update the clocks */
3886                 SendToICS(ics_prefix);
3887                 SendToICS("refresh\n");
3888                 continue;
3889             }
3890
3891             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892                 ics_clock_paused = TRUE;
3893                 StopClocks();
3894                 continue;
3895             }
3896
3897             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898                 ics_clock_paused = FALSE;
3899                 StartClocks();
3900                 continue;
3901             }
3902
3903             /* Grab player ratings from the Creating: message.
3904                Note we have to check for the special case when
3905                the ICS inserts things like [white] or [black]. */
3906             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3908                 /* star_matches:
3909                    0    player 1 name (not necessarily white)
3910                    1    player 1 rating
3911                    2    empty, white, or black (IGNORED)
3912                    3    player 2 name (not necessarily black)
3913                    4    player 2 rating
3914
3915                    The names/ratings are sorted out when the game
3916                    actually starts (below).
3917                 */
3918                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919                 player1Rating = string_to_rating(star_match[1]);
3920                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921                 player2Rating = string_to_rating(star_match[4]);
3922
3923                 if (appData.debugMode)
3924                   fprintf(debugFP,
3925                           "Ratings from 'Creating:' %s %d, %s %d\n",
3926                           player1Name, player1Rating,
3927                           player2Name, player2Rating);
3928
3929                 continue;
3930             }
3931
3932             /* Improved generic start/end-of-game messages */
3933             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935                 /* If tkind == 0: */
3936                 /* star_match[0] is the game number */
3937                 /*           [1] is the white player's name */
3938                 /*           [2] is the black player's name */
3939                 /* For end-of-game: */
3940                 /*           [3] is the reason for the game end */
3941                 /*           [4] is a PGN end game-token, preceded by " " */
3942                 /* For start-of-game: */
3943                 /*           [3] begins with "Creating" or "Continuing" */
3944                 /*           [4] is " *" or empty (don't care). */
3945                 int gamenum = atoi(star_match[0]);
3946                 char *whitename, *blackname, *why, *endtoken;
3947                 ChessMove endtype = EndOfFile;
3948
3949                 if (tkind == 0) {
3950                   whitename = star_match[1];
3951                   blackname = star_match[2];
3952                   why = star_match[3];
3953                   endtoken = star_match[4];
3954                 } else {
3955                   whitename = star_match[1];
3956                   blackname = star_match[3];
3957                   why = star_match[5];
3958                   endtoken = star_match[6];
3959                 }
3960
3961                 /* Game start messages */
3962                 if (strncmp(why, "Creating ", 9) == 0 ||
3963                     strncmp(why, "Continuing ", 11) == 0) {
3964                     gs_gamenum = gamenum;
3965                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3968 #if ZIPPY
3969                     if (appData.zippyPlay) {
3970                         ZippyGameStart(whitename, blackname);
3971                     }
3972 #endif /*ZIPPY*/
3973                     partnerBoardValid = FALSE; // [HGM] bughouse
3974                     continue;
3975                 }
3976
3977                 /* Game end messages */
3978                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979                     ics_gamenum != gamenum) {
3980                     continue;
3981                 }
3982                 while (endtoken[0] == ' ') endtoken++;
3983                 switch (endtoken[0]) {
3984                   case '*':
3985                   default:
3986                     endtype = GameUnfinished;
3987                     break;
3988                   case '0':
3989                     endtype = BlackWins;
3990                     break;
3991                   case '1':
3992                     if (endtoken[1] == '/')
3993                       endtype = GameIsDrawn;
3994                     else
3995                       endtype = WhiteWins;
3996                     break;
3997                 }
3998                 GameEnds(endtype, why, GE_ICS);
3999 #if ZIPPY
4000                 if (appData.zippyPlay && first.initDone) {
4001                     ZippyGameEnd(endtype, why);
4002                     if (first.pr == NoProc) {
4003                       /* Start the next process early so that we'll
4004                          be ready for the next challenge */
4005                       StartChessProgram(&first);
4006                     }
4007                     /* Send "new" early, in case this command takes
4008                        a long time to finish, so that we'll be ready
4009                        for the next challenge. */
4010                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4011                     Reset(TRUE, TRUE);
4012                 }
4013 #endif /*ZIPPY*/
4014                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4015                 continue;
4016             }
4017
4018             if (looking_at(buf, &i, "Removing game * from observation") ||
4019                 looking_at(buf, &i, "no longer observing game *") ||
4020                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021                 if (gameMode == IcsObserving &&
4022                     atoi(star_match[0]) == ics_gamenum)
4023                   {
4024                       /* icsEngineAnalyze */
4025                       if (appData.icsEngineAnalyze) {
4026                             ExitAnalyzeMode();
4027                             ModeHighlight();
4028                       }
4029                       StopClocks();
4030                       gameMode = IcsIdle;
4031                       ics_gamenum = -1;
4032                       ics_user_moved = FALSE;
4033                   }
4034                 continue;
4035             }
4036
4037             if (looking_at(buf, &i, "no longer examining game *")) {
4038                 if (gameMode == IcsExamining &&
4039                     atoi(star_match[0]) == ics_gamenum)
4040                   {
4041                       gameMode = IcsIdle;
4042                       ics_gamenum = -1;
4043                       ics_user_moved = FALSE;
4044                   }
4045                 continue;
4046             }
4047
4048             /* Advance leftover_start past any newlines we find,
4049                so only partial lines can get reparsed */
4050             if (looking_at(buf, &i, "\n")) {
4051                 prevColor = curColor;
4052                 if (curColor != ColorNormal) {
4053                     if (oldi > next_out) {
4054                         SendToPlayer(&buf[next_out], oldi - next_out);
4055                         next_out = oldi;
4056                     }
4057                     Colorize(ColorNormal, FALSE);
4058                     curColor = ColorNormal;
4059                 }
4060                 if (started == STARTED_BOARD) {
4061                     started = STARTED_NONE;
4062                     parse[parse_pos] = NULLCHAR;
4063                     ParseBoard12(parse);
4064                     ics_user_moved = 0;
4065
4066                     /* Send premove here */
4067                     if (appData.premove) {
4068                       char str[MSG_SIZ];
4069                       if (currentMove == 0 &&
4070                           gameMode == IcsPlayingWhite &&
4071                           appData.premoveWhite) {
4072                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073                         if (appData.debugMode)
4074                           fprintf(debugFP, "Sending premove:\n");
4075                         SendToICS(str);
4076                       } else if (currentMove == 1 &&
4077                                  gameMode == IcsPlayingBlack &&
4078                                  appData.premoveBlack) {
4079                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080                         if (appData.debugMode)
4081                           fprintf(debugFP, "Sending premove:\n");
4082                         SendToICS(str);
4083                       } else if (gotPremove) {
4084                         gotPremove = 0;
4085                         ClearPremoveHighlights();
4086                         if (appData.debugMode)
4087                           fprintf(debugFP, "Sending premove:\n");
4088                           UserMoveEvent(premoveFromX, premoveFromY,
4089                                         premoveToX, premoveToY,
4090                                         premovePromoChar);
4091                       }
4092                     }
4093
4094                     /* Usually suppress following prompt */
4095                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097                         if (looking_at(buf, &i, "*% ")) {
4098                             savingComment = FALSE;
4099                             suppressKibitz = 0;
4100                         }
4101                     }
4102                     next_out = i;
4103                 } else if (started == STARTED_HOLDINGS) {
4104                     int gamenum;
4105                     char new_piece[MSG_SIZ];
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     if (appData.debugMode)
4109                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110                                                         parse, currentMove);
4111                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4112                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113                         if (gameInfo.variant == VariantNormal) {
4114                           /* [HGM] We seem to switch variant during a game!
4115                            * Presumably no holdings were displayed, so we have
4116                            * to move the position two files to the right to
4117                            * create room for them!
4118                            */
4119                           VariantClass newVariant;
4120                           switch(gameInfo.boardWidth) { // base guess on board width
4121                                 case 9:  newVariant = VariantShogi; break;
4122                                 case 10: newVariant = VariantGreat; break;
4123                                 default: newVariant = VariantCrazyhouse; break;
4124                           }
4125                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126                           /* Get a move list just to see the header, which
4127                              will tell us whether this is really bug or zh */
4128                           if (ics_getting_history == H_FALSE) {
4129                             ics_getting_history = H_REQUESTED;
4130                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4131                             SendToICS(str);
4132                           }
4133                         }
4134                         new_piece[0] = NULLCHAR;
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to board holdings area */
4141                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4144 #if ZIPPY
4145                         if (appData.zippyPlay && first.initDone) {
4146                             ZippyHoldings(white_holding, black_holding,
4147                                           new_piece);
4148                         }
4149 #endif /*ZIPPY*/
4150                         if (tinyLayout || smallLayout) {
4151                             char wh[16], bh[16];
4152                             PackHolding(wh, white_holding);
4153                             PackHolding(bh, black_holding);
4154                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155                                     gameInfo.white, gameInfo.black);
4156                         } else {
4157                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158                                     gameInfo.white, white_holding, _("vs."),
4159                                     gameInfo.black, black_holding);
4160                         }
4161                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162                         DrawPosition(FALSE, boards[currentMove]);
4163                         DisplayTitle(str);
4164                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165                         sscanf(parse, "game %d white [%s black [%s <- %s",
4166                                &gamenum, white_holding, black_holding,
4167                                new_piece);
4168                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4169                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4170                         /* [HGM] copy holdings to partner-board holdings area */
4171                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4176                       }
4177                     }
4178                     /* Suppress following prompt */
4179                     if (looking_at(buf, &i, "*% ")) {
4180                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181                         savingComment = FALSE;
4182                         suppressKibitz = 0;
4183                     }
4184                     next_out = i;
4185                 }
4186                 continue;
4187             }
4188
4189             i++;                /* skip unparsed character and loop back */
4190         }
4191
4192         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 //          SendToPlayer(&buf[next_out], i - next_out);
4195             started != STARTED_HOLDINGS && leftover_start > next_out) {
4196             SendToPlayer(&buf[next_out], leftover_start - next_out);
4197             next_out = i;
4198         }
4199
4200         leftover_len = buf_len - leftover_start;
4201         /* if buffer ends with something we couldn't parse,
4202            reparse it after appending the next read */
4203
4204     } else if (count == 0) {
4205         RemoveInputSource(isr);
4206         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4207     } else {
4208         DisplayFatalError(_("Error reading from ICS"), error, 1);
4209     }
4210 }
4211
4212
4213 /* Board style 12 looks like this:
4214
4215    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4216
4217  * The "<12> " is stripped before it gets to this routine.  The two
4218  * trailing 0's (flip state and clock ticking) are later addition, and
4219  * some chess servers may not have them, or may have only the first.
4220  * Additional trailing fields may be added in the future.
4221  */
4222
4223 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4224
4225 #define RELATION_OBSERVING_PLAYED    0
4226 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE      1
4228 #define RELATION_PLAYING_NOTMYMOVE  -1
4229 #define RELATION_EXAMINING           2
4230 #define RELATION_ISOLATED_BOARD     -3
4231 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4232
4233 void
4234 ParseBoard12 (char *string)
4235 {
4236 #if ZIPPY
4237     int i, takeback;
4238     char *bookHit = NULL; // [HGM] book
4239 #endif
4240     GameMode newGameMode;
4241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244     char to_play, board_chars[200];
4245     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246     char black[32], white[32];
4247     Board board;
4248     int prevMove = currentMove;
4249     int ticking = 2;
4250     ChessMove moveType;
4251     int fromX, fromY, toX, toY;
4252     char promoChar;
4253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254     Boolean weird = FALSE, reqFlag = FALSE;
4255
4256     fromX = fromY = toX = toY = -1;
4257
4258     newGame = FALSE;
4259
4260     if (appData.debugMode)
4261       fprintf(debugFP, "Parsing board: %s\n", string);
4262
4263     move_str[0] = NULLCHAR;
4264     elapsed_time[0] = NULLCHAR;
4265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4266         int  i = 0, j;
4267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268             if(string[i] == ' ') { ranks++; files = 0; }
4269             else files++;
4270             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4271             i++;
4272         }
4273         for(j = 0; j <i; j++) board_chars[j] = string[j];
4274         board_chars[i] = '\0';
4275         string += i + 1;
4276     }
4277     n = sscanf(string, PATTERN, &to_play, &double_push,
4278                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279                &gamenum, white, black, &relation, &basetime, &increment,
4280                &white_stren, &black_stren, &white_time, &black_time,
4281                &moveNum, str, elapsed_time, move_str, &ics_flip,
4282                &ticking);
4283
4284     if (n < 21) {
4285         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286         DisplayError(str, 0);
4287         return;
4288     }
4289
4290     /* Convert the move number to internal form */
4291     moveNum = (moveNum - 1) * 2;
4292     if (to_play == 'B') moveNum++;
4293     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4295                         0, 1);
4296       return;
4297     }
4298
4299     switch (relation) {
4300       case RELATION_OBSERVING_PLAYED:
4301       case RELATION_OBSERVING_STATIC:
4302         if (gamenum == -1) {
4303             /* Old ICC buglet */
4304             relation = RELATION_OBSERVING_STATIC;
4305         }
4306         newGameMode = IcsObserving;
4307         break;
4308       case RELATION_PLAYING_MYMOVE:
4309       case RELATION_PLAYING_NOTMYMOVE:
4310         newGameMode =
4311           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312             IcsPlayingWhite : IcsPlayingBlack;
4313         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4314         break;
4315       case RELATION_EXAMINING:
4316         newGameMode = IcsExamining;
4317         break;
4318       case RELATION_ISOLATED_BOARD:
4319       default:
4320         /* Just display this board.  If user was doing something else,
4321            we will forget about it until the next board comes. */
4322         newGameMode = IcsIdle;
4323         break;
4324       case RELATION_STARTING_POSITION:
4325         newGameMode = gameMode;
4326         break;
4327     }
4328
4329     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334       static int lastBgGame = -1;
4335       char *toSqr;
4336       for (k = 0; k < ranks; k++) {
4337         for (j = 0; j < files; j++)
4338           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339         if(gameInfo.holdingsWidth > 1) {
4340              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4342         }
4343       }
4344       CopyBoard(partnerBoard, board);
4345       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349       if(toSqr = strchr(str, '-')) {
4350         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4356       if(twoBoards) {
4357           DisplayWhiteClock(white_time*fac, to_play == 'W');
4358           DisplayBlackClock(black_time*fac, to_play != 'W');
4359           activePartner = to_play;
4360           if(gamenum != lastBgGame) {
4361               char buf[MSG_SIZ];
4362               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4363               DisplayTitle(buf);
4364           }
4365           lastBgGame = gamenum;
4366           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370       if(!twoBoards) DisplayMessage(partnerStatus, "");
4371         partnerBoardValid = TRUE;
4372       return;
4373     }
4374
4375     if(appData.dualBoard && appData.bgObserve) {
4376         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377             SendToICS(ics_prefix), SendToICS("pobserve\n");
4378         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4379             char buf[MSG_SIZ];
4380             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4381             SendToICS(buf);
4382         }
4383     }
4384
4385     /* Modify behavior for initial board display on move listing
4386        of wild games.
4387        */
4388     switch (ics_getting_history) {
4389       case H_FALSE:
4390       case H_REQUESTED:
4391         break;
4392       case H_GOT_REQ_HEADER:
4393       case H_GOT_UNREQ_HEADER:
4394         /* This is the initial position of the current game */
4395         gamenum = ics_gamenum;
4396         moveNum = 0;            /* old ICS bug workaround */
4397         if (to_play == 'B') {
4398           startedFromSetupPosition = TRUE;
4399           blackPlaysFirst = TRUE;
4400           moveNum = 1;
4401           if (forwardMostMove == 0) forwardMostMove = 1;
4402           if (backwardMostMove == 0) backwardMostMove = 1;
4403           if (currentMove == 0) currentMove = 1;
4404         }
4405         newGameMode = gameMode;
4406         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4407         break;
4408       case H_GOT_UNWANTED_HEADER:
4409         /* This is an initial board that we don't want */
4410         return;
4411       case H_GETTING_MOVES:
4412         /* Should not happen */
4413         DisplayError(_("Error gathering move list: extra board"), 0);
4414         ics_getting_history = H_FALSE;
4415         return;
4416     }
4417
4418    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4421      /* [HGM] We seem to have switched variant unexpectedly
4422       * Try to guess new variant from board size
4423       */
4424           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4429           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432           /* Get a move list just to see the header, which
4433              will tell us whether this is really bug or zh */
4434           if (ics_getting_history == H_FALSE) {
4435             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437             SendToICS(str);
4438           }
4439     }
4440
4441     /* Take action if this is the first board of a new game, or of a
4442        different game than is currently being displayed.  */
4443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444         relation == RELATION_ISOLATED_BOARD) {
4445
4446         /* Forget the old game and get the history (if any) of the new one */
4447         if (gameMode != BeginningOfGame) {
4448           Reset(TRUE, TRUE);
4449         }
4450         newGame = TRUE;
4451         if (appData.autoRaiseBoard) BoardToTop();
4452         prevMove = -3;
4453         if (gamenum == -1) {
4454             newGameMode = IcsIdle;
4455         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456                    appData.getMoveList && !reqFlag) {
4457             /* Need to get game history */
4458             ics_getting_history = H_REQUESTED;
4459             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4460             SendToICS(str);
4461         }
4462
4463         /* Initially flip the board to have black on the bottom if playing
4464            black or if the ICS flip flag is set, but let the user change
4465            it with the Flip View button. */
4466         flipView = appData.autoFlipView ?
4467           (newGameMode == IcsPlayingBlack) || ics_flip :
4468           appData.flipView;
4469
4470         /* Done with values from previous mode; copy in new ones */
4471         gameMode = newGameMode;
4472         ModeHighlight();
4473         ics_gamenum = gamenum;
4474         if (gamenum == gs_gamenum) {
4475             int klen = strlen(gs_kind);
4476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478             gameInfo.event = StrSave(str);
4479         } else {
4480             gameInfo.event = StrSave("ICS game");
4481         }
4482         gameInfo.site = StrSave(appData.icsHost);
4483         gameInfo.date = PGNDate();
4484         gameInfo.round = StrSave("-");
4485         gameInfo.white = StrSave(white);
4486         gameInfo.black = StrSave(black);
4487         timeControl = basetime * 60 * 1000;
4488         timeControl_2 = 0;
4489         timeIncrement = increment * 1000;
4490         movesPerSession = 0;
4491         gameInfo.timeControl = TimeControlTagValue();
4492         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496     setbuf(debugFP, NULL);
4497   }
4498
4499         gameInfo.outOfBook = NULL;
4500
4501         /* Do we have the ratings? */
4502         if (strcmp(player1Name, white) == 0 &&
4503             strcmp(player2Name, black) == 0) {
4504             if (appData.debugMode)
4505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506                       player1Rating, player2Rating);
4507             gameInfo.whiteRating = player1Rating;
4508             gameInfo.blackRating = player2Rating;
4509         } else if (strcmp(player2Name, white) == 0 &&
4510                    strcmp(player1Name, black) == 0) {
4511             if (appData.debugMode)
4512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513                       player2Rating, player1Rating);
4514             gameInfo.whiteRating = player2Rating;
4515             gameInfo.blackRating = player1Rating;
4516         }
4517         player1Name[0] = player2Name[0] = NULLCHAR;
4518
4519         /* Silence shouts if requested */
4520         if (appData.quietPlay &&
4521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522             SendToICS(ics_prefix);
4523             SendToICS("set shout 0\n");
4524         }
4525     }
4526
4527     /* Deal with midgame name changes */
4528     if (!newGame) {
4529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530             if (gameInfo.white) free(gameInfo.white);
4531             gameInfo.white = StrSave(white);
4532         }
4533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534             if (gameInfo.black) free(gameInfo.black);
4535             gameInfo.black = StrSave(black);
4536         }
4537     }
4538
4539     /* Throw away game result if anything actually changes in examine mode */
4540     if (gameMode == IcsExamining && !newGame) {
4541         gameInfo.result = GameUnfinished;
4542         if (gameInfo.resultDetails != NULL) {
4543             free(gameInfo.resultDetails);
4544             gameInfo.resultDetails = NULL;
4545         }
4546     }
4547
4548     /* In pausing && IcsExamining mode, we ignore boards coming
4549        in if they are in a different variation than we are. */
4550     if (pauseExamInvalid) return;
4551     if (pausing && gameMode == IcsExamining) {
4552         if (moveNum <= pauseExamForwardMostMove) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557     }
4558
4559   if (appData.debugMode) {
4560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4561   }
4562     /* Parse the board */
4563     for (k = 0; k < ranks; k++) {
4564       for (j = 0; j < files; j++)
4565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566       if(gameInfo.holdingsWidth > 1) {
4567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4569       }
4570     }
4571     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572       board[5][BOARD_RGHT+1] = WhiteAngel;
4573       board[6][BOARD_RGHT+1] = WhiteMarshall;
4574       board[1][0] = BlackMarshall;
4575       board[2][0] = BlackAngel;
4576       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4577     }
4578     CopyBoard(boards[moveNum], board);
4579     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4580     if (moveNum == 0) {
4581         startedFromSetupPosition =
4582           !CompareBoards(board, initialPosition);
4583         if(startedFromSetupPosition)
4584             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4585     }
4586
4587     /* [HGM] Set castling rights. Take the outermost Rooks,
4588        to make it also work for FRC opening positions. Note that board12
4589        is really defective for later FRC positions, as it has no way to
4590        indicate which Rook can castle if they are on the same side of King.
4591        For the initial position we grant rights to the outermost Rooks,
4592        and remember thos rights, and we then copy them on positions
4593        later in an FRC game. This means WB might not recognize castlings with
4594        Rooks that have moved back to their original position as illegal,
4595        but in ICS mode that is not its job anyway.
4596     */
4597     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4599
4600         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601             if(board[0][i] == WhiteRook) j = i;
4602         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604             if(board[0][i] == WhiteRook) j = i;
4605         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4612
4613         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618             if(board[BOARD_HEIGHT-1][k] == bKing)
4619                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620         if(gameInfo.variant == VariantTwoKings) {
4621             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4624         }
4625     } else { int r;
4626         r = boards[moveNum][CASTLING][0] = initialRights[0];
4627         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628         r = boards[moveNum][CASTLING][1] = initialRights[1];
4629         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630         r = boards[moveNum][CASTLING][3] = initialRights[3];
4631         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632         r = boards[moveNum][CASTLING][4] = initialRights[4];
4633         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634         /* wildcastle kludge: always assume King has rights */
4635         r = boards[moveNum][CASTLING][2] = initialRights[2];
4636         r = boards[moveNum][CASTLING][5] = initialRights[5];
4637     }
4638     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639     boards[moveNum][EP_STATUS] = EP_NONE;
4640     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4643
4644
4645     if (ics_getting_history == H_GOT_REQ_HEADER ||
4646         ics_getting_history == H_GOT_UNREQ_HEADER) {
4647         /* This was an initial position from a move list, not
4648            the current position */
4649         return;
4650     }
4651
4652     /* Update currentMove and known move number limits */
4653     newMove = newGame || moveNum > forwardMostMove;
4654
4655     if (newGame) {
4656         forwardMostMove = backwardMostMove = currentMove = moveNum;
4657         if (gameMode == IcsExamining && moveNum == 0) {
4658           /* Workaround for ICS limitation: we are not told the wild
4659              type when starting to examine a game.  But if we ask for
4660              the move list, the move list header will tell us */
4661             ics_getting_history = H_REQUESTED;
4662             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4663             SendToICS(str);
4664         }
4665     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4667 #if ZIPPY
4668         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669         /* [HGM] applied this also to an engine that is silently watching        */
4670         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672             gameInfo.variant == currentlyInitializedVariant) {
4673           takeback = forwardMostMove - moveNum;
4674           for (i = 0; i < takeback; i++) {
4675             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676             SendToProgram("undo\n", &first);
4677           }
4678         }
4679 #endif
4680
4681         forwardMostMove = moveNum;
4682         if (!pausing || currentMove > forwardMostMove)
4683           currentMove = forwardMostMove;
4684     } else {
4685         /* New part of history that is not contiguous with old part */
4686         if (pausing && gameMode == IcsExamining) {
4687             pauseExamInvalid = TRUE;
4688             forwardMostMove = pauseExamForwardMostMove;
4689             return;
4690         }
4691         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4692 #if ZIPPY
4693             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694                 // [HGM] when we will receive the move list we now request, it will be
4695                 // fed to the engine from the first move on. So if the engine is not
4696                 // in the initial position now, bring it there.
4697                 InitChessProgram(&first, 0);
4698             }
4699 #endif
4700             ics_getting_history = H_REQUESTED;
4701             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4702             SendToICS(str);
4703         }
4704         forwardMostMove = backwardMostMove = currentMove = moveNum;
4705     }
4706
4707     /* Update the clocks */
4708     if (strchr(elapsed_time, '.')) {
4709       /* Time is in ms */
4710       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4712     } else {
4713       /* Time is in seconds */
4714       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4716     }
4717
4718
4719 #if ZIPPY
4720     if (appData.zippyPlay && newGame &&
4721         gameMode != IcsObserving && gameMode != IcsIdle &&
4722         gameMode != IcsExamining)
4723       ZippyFirstBoard(moveNum, basetime, increment);
4724 #endif
4725
4726     /* Put the move on the move list, first converting
4727        to canonical algebraic form. */
4728     if (moveNum > 0) {
4729   if (appData.debugMode) {
4730     int f = forwardMostMove;
4731     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735     fprintf(debugFP, "moveNum = %d\n", moveNum);
4736     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737     setbuf(debugFP, NULL);
4738   }
4739         if (moveNum <= backwardMostMove) {
4740             /* We don't know what the board looked like before
4741                this move.  Punt. */
4742           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746         } else if (strcmp(move_str, "none") == 0) {
4747             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748             /* Again, we don't know what the board looked like;
4749                this is really the start of the game. */
4750             parseList[moveNum - 1][0] = NULLCHAR;
4751             moveList[moveNum - 1][0] = NULLCHAR;
4752             backwardMostMove = moveNum;
4753             startedFromSetupPosition = TRUE;
4754             fromX = fromY = toX = toY = -1;
4755         } else {
4756           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757           //                 So we parse the long-algebraic move string in stead of the SAN move
4758           int valid; char buf[MSG_SIZ], *prom;
4759
4760           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762           // str looks something like "Q/a1-a2"; kill the slash
4763           if(str[1] == '/')
4764             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767                 strcat(buf, prom); // long move lacks promo specification!
4768           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769                 if(appData.debugMode)
4770                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771                 safeStrCpy(move_str, buf, MSG_SIZ);
4772           }
4773           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774                                 &fromX, &fromY, &toX, &toY, &promoChar)
4775                || ParseOneMove(buf, moveNum - 1, &moveType,
4776                                 &fromX, &fromY, &toX, &toY, &promoChar);
4777           // end of long SAN patch
4778           if (valid) {
4779             (void) CoordsToAlgebraic(boards[moveNum - 1],
4780                                      PosFlags(moveNum - 1),
4781                                      fromY, fromX, toY, toX, promoChar,
4782                                      parseList[moveNum-1]);
4783             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4784               case MT_NONE:
4785               case MT_STALEMATE:
4786               default:
4787                 break;
4788               case MT_CHECK:
4789                 if(gameInfo.variant != VariantShogi)
4790                     strcat(parseList[moveNum - 1], "+");
4791                 break;
4792               case MT_CHECKMATE:
4793               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794                 strcat(parseList[moveNum - 1], "#");
4795                 break;
4796             }
4797             strcat(parseList[moveNum - 1], " ");
4798             strcat(parseList[moveNum - 1], elapsed_time);
4799             /* currentMoveString is set as a side-effect of ParseOneMove */
4800             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802             strcat(moveList[moveNum - 1], "\n");
4803
4804             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807                 ChessSquare old, new = boards[moveNum][k][j];
4808                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810                   if(old == new) continue;
4811                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812                   else if(new == WhiteWazir || new == BlackWazir) {
4813                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815                       else boards[moveNum][k][j] = old; // preserve type of Gold
4816                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4818               }
4819           } else {
4820             /* Move from ICS was illegal!?  Punt. */
4821             if (appData.debugMode) {
4822               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4824             }
4825             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826             strcat(parseList[moveNum - 1], " ");
4827             strcat(parseList[moveNum - 1], elapsed_time);
4828             moveList[moveNum - 1][0] = NULLCHAR;
4829             fromX = fromY = toX = toY = -1;
4830           }
4831         }
4832   if (appData.debugMode) {
4833     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834     setbuf(debugFP, NULL);
4835   }
4836
4837 #if ZIPPY
4838         /* Send move to chess program (BEFORE animating it). */
4839         if (appData.zippyPlay && !newGame && newMove &&
4840            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4841
4842             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4846                             move_str);
4847                     DisplayError(str, 0);
4848                 } else {
4849                     if (first.sendTime) {
4850                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4851                     }
4852                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853                     if (firstMove && !bookHit) {
4854                         firstMove = FALSE;
4855                         if (first.useColors) {
4856                           SendToProgram(gameMode == IcsPlayingWhite ?
4857                                         "white\ngo\n" :
4858                                         "black\ngo\n", &first);
4859                         } else {
4860                           SendToProgram("go\n", &first);
4861                         }
4862                         first.maybeThinking = TRUE;
4863                     }
4864                 }
4865             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866               if (moveList[moveNum - 1][0] == NULLCHAR) {
4867                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868                 DisplayError(str, 0);
4869               } else {
4870                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871                 SendMoveToProgram(moveNum - 1, &first);
4872               }
4873             }
4874         }
4875 #endif
4876     }
4877
4878     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879         /* If move comes from a remote source, animate it.  If it
4880            isn't remote, it will have already been animated. */
4881         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4883         }
4884         if (!pausing && appData.highlightLastMove) {
4885             SetHighlights(fromX, fromY, toX, toY);
4886         }
4887     }
4888
4889     /* Start the clocks */
4890     whiteFlag = blackFlag = FALSE;
4891     appData.clockMode = !(basetime == 0 && increment == 0);
4892     if (ticking == 0) {
4893       ics_clock_paused = TRUE;
4894       StopClocks();
4895     } else if (ticking == 1) {
4896       ics_clock_paused = FALSE;
4897     }
4898     if (gameMode == IcsIdle ||
4899         relation == RELATION_OBSERVING_STATIC ||
4900         relation == RELATION_EXAMINING ||
4901         ics_clock_paused)
4902       DisplayBothClocks();
4903     else
4904       StartClocks();
4905
4906     /* Display opponents and material strengths */
4907     if (gameInfo.variant != VariantBughouse &&
4908         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909         if (tinyLayout || smallLayout) {
4910             if(gameInfo.variant == VariantNormal)
4911               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4913                     basetime, increment);
4914             else
4915               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4917                     basetime, increment, (int) gameInfo.variant);
4918         } else {
4919             if(gameInfo.variant == VariantNormal)
4920               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922                     basetime, increment);
4923             else
4924               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926                     basetime, increment, VariantName(gameInfo.variant));
4927         }
4928         DisplayTitle(str);
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4931   }
4932     }
4933
4934
4935     /* Display the board */
4936     if (!pausing && !appData.noGUI) {
4937
4938       if (appData.premove)
4939           if (!gotPremove ||
4940              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942               ClearPremoveHighlights();
4943
4944       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946       DrawPosition(j, boards[currentMove]);
4947
4948       DisplayMove(moveNum - 1);
4949       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4952         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4953       }
4954     }
4955
4956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4957 #if ZIPPY
4958     if(bookHit) { // [HGM] book: simulate book reply
4959         static char bookMove[MSG_SIZ]; // a bit generous?
4960
4961         programStats.nodes = programStats.depth = programStats.time =
4962         programStats.score = programStats.got_only_move = 0;
4963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4964
4965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966         strcat(bookMove, bookHit);
4967         HandleMachineMove(bookMove, &first);
4968     }
4969 #endif
4970 }
4971
4972 void
4973 GetMoveListEvent ()
4974 {
4975     char buf[MSG_SIZ];
4976     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977         ics_getting_history = H_REQUESTED;
4978         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4979         SendToICS(buf);
4980     }
4981 }
4982
4983 void
4984 SendToBoth (char *msg)
4985 {   // to make it easy to keep two engines in step in dual analysis
4986     SendToProgram(msg, &first);
4987     if(second.analyzing) SendToProgram(msg, &second);
4988 }
4989
4990 void
4991 AnalysisPeriodicEvent (int force)
4992 {
4993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994          && !force) || !appData.periodicUpdates)
4995       return;
4996
4997     /* Send . command to Crafty to collect stats */
4998     SendToBoth(".\n");
4999
5000     /* Don't send another until we get a response (this makes
5001        us stop sending to old Crafty's which don't understand
5002        the "." command (sending illegal cmds resets node count & time,
5003        which looks bad)) */
5004     programStats.ok_to_send = 0;
5005 }
5006
5007 void
5008 ics_update_width (int new_width)
5009 {
5010         ics_printf("set width %d\n", new_width);
5011 }
5012
5013 void
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5015 {
5016     char buf[MSG_SIZ];
5017
5018     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019         // null move in variant where engine does not understand it (for analysis purposes)
5020         SendBoard(cps, moveNum + 1); // send position after move in stead.
5021         return;
5022     }
5023     if (cps->useUsermove) {
5024       SendToProgram("usermove ", cps);
5025     }
5026     if (cps->useSAN) {
5027       char *space;
5028       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5029         int len = space - parseList[moveNum];
5030         memcpy(buf, parseList[moveNum], len);
5031         buf[len++] = '\n';
5032         buf[len] = NULLCHAR;
5033       } else {
5034         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5035       }
5036       SendToProgram(buf, cps);
5037     } else {
5038       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5039         AlphaRank(moveList[moveNum], 4);
5040         SendToProgram(moveList[moveNum], cps);
5041         AlphaRank(moveList[moveNum], 4); // and back
5042       } else
5043       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5044        * the engine. It would be nice to have a better way to identify castle
5045        * moves here. */
5046       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5047                                                                          && cps->useOOCastle) {
5048         int fromX = moveList[moveNum][0] - AAA;
5049         int fromY = moveList[moveNum][1] - ONE;
5050         int toX = moveList[moveNum][2] - AAA;
5051         int toY = moveList[moveNum][3] - ONE;
5052         if((boards[moveNum][fromY][fromX] == WhiteKing
5053             && boards[moveNum][toY][toX] == WhiteRook)
5054            || (boards[moveNum][fromY][fromX] == BlackKing
5055                && boards[moveNum][toY][toX] == BlackRook)) {
5056           if(toX > fromX) SendToProgram("O-O\n", cps);
5057           else SendToProgram("O-O-O\n", cps);
5058         }
5059         else SendToProgram(moveList[moveNum], cps);
5060       } else
5061       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5062         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5063           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5064           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5065                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066         } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5071         } else
5072           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5073                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074         SendToProgram(buf, cps);
5075       }
5076       else SendToProgram(moveList[moveNum], cps);
5077       /* End of additions by Tord */
5078     }
5079
5080     /* [HGM] setting up the opening has brought engine in force mode! */
5081     /*       Send 'go' if we are in a mode where machine should play. */
5082     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5083         (gameMode == TwoMachinesPlay   ||
5084 #if ZIPPY
5085          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5086 #endif
5087          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5088         SendToProgram("go\n", cps);
5089   if (appData.debugMode) {
5090     fprintf(debugFP, "(extra)\n");
5091   }
5092     }
5093     setboardSpoiledMachineBlack = 0;
5094 }
5095
5096 void
5097 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5098 {
5099     char user_move[MSG_SIZ];
5100     char suffix[4];
5101
5102     if(gameInfo.variant == VariantSChess && promoChar) {
5103         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5104         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5105     } else suffix[0] = NULLCHAR;
5106
5107     switch (moveType) {
5108       default:
5109         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5110                 (int)moveType, fromX, fromY, toX, toY);
5111         DisplayError(user_move + strlen("say "), 0);
5112         break;
5113       case WhiteKingSideCastle:
5114       case BlackKingSideCastle:
5115       case WhiteQueenSideCastleWild:
5116       case BlackQueenSideCastleWild:
5117       /* PUSH Fabien */
5118       case WhiteHSideCastleFR:
5119       case BlackHSideCastleFR:
5120       /* POP Fabien */
5121         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5122         break;
5123       case WhiteQueenSideCastle:
5124       case BlackQueenSideCastle:
5125       case WhiteKingSideCastleWild:
5126       case BlackKingSideCastleWild:
5127       /* PUSH Fabien */
5128       case WhiteASideCastleFR:
5129       case BlackASideCastleFR:
5130       /* POP Fabien */
5131         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5132         break;
5133       case WhiteNonPromotion:
5134       case BlackNonPromotion:
5135         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5136         break;
5137       case WhitePromotion:
5138       case BlackPromotion:
5139         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5140            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5141           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143                 PieceToChar(WhiteFerz));
5144         else if(gameInfo.variant == VariantGreat)
5145           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5146                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5147                 PieceToChar(WhiteMan));
5148         else
5149           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5151                 promoChar);
5152         break;
5153       case WhiteDrop:
5154       case BlackDrop:
5155       drop:
5156         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5157                  ToUpper(PieceToChar((ChessSquare) fromX)),
5158                  AAA + toX, ONE + toY);
5159         break;
5160       case IllegalMove:  /* could be a variant we don't quite understand */
5161         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5162       case NormalMove:
5163       case WhiteCapturesEnPassant:
5164       case BlackCapturesEnPassant:
5165         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5166                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5167         break;
5168     }
5169     SendToICS(user_move);
5170     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5171         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5172 }
5173
5174 void
5175 UploadGameEvent ()
5176 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5177     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5178     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5179     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5180       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5181       return;
5182     }
5183     if(gameMode != IcsExamining) { // is this ever not the case?
5184         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5185
5186         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5187           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5188         } else { // on FICS we must first go to general examine mode
5189           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5190         }
5191         if(gameInfo.variant != VariantNormal) {
5192             // try figure out wild number, as xboard names are not always valid on ICS
5193             for(i=1; i<=36; i++) {
5194               snprintf(buf, MSG_SIZ, "wild/%d", i);
5195                 if(StringToVariant(buf) == gameInfo.variant) break;
5196             }
5197             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5198             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5199             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5200         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5201         SendToICS(ics_prefix);
5202         SendToICS(buf);
5203         if(startedFromSetupPosition || backwardMostMove != 0) {
5204           fen = PositionToFEN(backwardMostMove, NULL, 1);
5205           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5206             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5207             SendToICS(buf);
5208           } else { // FICS: everything has to set by separate bsetup commands
5209             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5210             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5211             SendToICS(buf);
5212             if(!WhiteOnMove(backwardMostMove)) {
5213                 SendToICS("bsetup tomove black\n");
5214             }
5215             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5216             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5217             SendToICS(buf);
5218             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5219             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5220             SendToICS(buf);
5221             i = boards[backwardMostMove][EP_STATUS];
5222             if(i >= 0) { // set e.p.
5223               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5224                 SendToICS(buf);
5225             }
5226             bsetup++;
5227           }
5228         }
5229       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5230             SendToICS("bsetup done\n"); // switch to normal examining.
5231     }
5232     for(i = backwardMostMove; i<last; i++) {
5233         char buf[20];
5234         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5235         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5236             int len = strlen(moveList[i]);
5237             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5238             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5239         }
5240         SendToICS(buf);
5241     }
5242     SendToICS(ics_prefix);
5243     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5244 }
5245
5246 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5247
5248 void
5249 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5250 {
5251     if (rf == DROP_RANK) {
5252       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5253       sprintf(move, "%c@%c%c\n",
5254                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5255     } else {
5256         if (promoChar == 'x' || promoChar == NULLCHAR) {
5257           sprintf(move, "%c%c%c%c\n",
5258                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5259           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5260         } else {
5261             sprintf(move, "%c%c%c%c%c\n",
5262                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5263         }
5264     }
5265 }
5266
5267 void
5268 ProcessICSInitScript (FILE *f)
5269 {
5270     char buf[MSG_SIZ];
5271
5272     while (fgets(buf, MSG_SIZ, f)) {
5273         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5274     }
5275
5276     fclose(f);
5277 }
5278
5279
5280 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5281 static ClickType lastClickType;
5282
5283 void
5284 Sweep (int step)
5285 {
5286     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5287     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5288     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5289     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5290     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5291     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5292     do {
5293         promoSweep -= step;
5294         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5295         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5296         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5297         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5298         if(!step) step = -1;
5299     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5300             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5301             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5302     if(toX >= 0) {
5303         int victim = boards[currentMove][toY][toX];
5304         boards[currentMove][toY][toX] = promoSweep;
5305         DrawPosition(FALSE, boards[currentMove]);
5306         boards[currentMove][toY][toX] = victim;
5307     } else
5308     ChangeDragPiece(promoSweep);
5309 }
5310
5311 int
5312 PromoScroll (int x, int y)
5313 {
5314   int step = 0;
5315
5316   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5317   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5318   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5319   if(!step) return FALSE;
5320   lastX = x; lastY = y;
5321   if((promoSweep < BlackPawn) == flipView) step = -step;
5322   if(step > 0) selectFlag = 1;
5323   if(!selectFlag) Sweep(step);
5324   return FALSE;
5325 }
5326
5327 void
5328 NextPiece (int step)
5329 {
5330     ChessSquare piece = boards[currentMove][toY][toX];
5331     do {
5332         pieceSweep -= step;
5333         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5334         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5335         if(!step) step = -1;
5336     } while(PieceToChar(pieceSweep) == '.');
5337     boards[currentMove][toY][toX] = pieceSweep;
5338     DrawPosition(FALSE, boards[currentMove]);
5339     boards[currentMove][toY][toX] = piece;
5340 }
5341 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5342 void
5343 AlphaRank (char *move, int n)
5344 {
5345 //    char *p = move, c; int x, y;
5346
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5349     }
5350
5351     if(move[1]=='*' &&
5352        move[2]>='0' && move[2]<='9' &&
5353        move[3]>='a' && move[3]<='x'    ) {
5354         move[1] = '@';
5355         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5356         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5357     } else
5358     if(move[0]>='0' && move[0]<='9' &&
5359        move[1]>='a' && move[1]<='x' &&
5360        move[2]>='0' && move[2]<='9' &&
5361        move[3]>='a' && move[3]<='x'    ) {
5362         /* input move, Shogi -> normal */
5363         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5364         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5365         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5366         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5367     } else
5368     if(move[1]=='@' &&
5369        move[3]>='0' && move[3]<='9' &&
5370        move[2]>='a' && move[2]<='x'    ) {
5371         move[1] = '*';
5372         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5373         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5374     } else
5375     if(
5376        move[0]>='a' && move[0]<='x' &&
5377        move[3]>='0' && move[3]<='9' &&
5378        move[2]>='a' && move[2]<='x'    ) {
5379          /* output move, normal -> Shogi */
5380         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5381         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5382         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5383         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5385     }
5386     if (appData.debugMode) {
5387         fprintf(debugFP, "   out = '%s'\n", move);
5388     }
5389 }
5390
5391 char yy_textstr[8000];
5392
5393 /* Parser for moves from gnuchess, ICS, or user typein box */
5394 Boolean
5395 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5396 {
5397     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5398
5399     switch (*moveType) {
5400       case WhitePromotion:
5401       case BlackPromotion:
5402       case WhiteNonPromotion:
5403       case BlackNonPromotion:
5404       case NormalMove:
5405       case WhiteCapturesEnPassant:
5406       case BlackCapturesEnPassant:
5407       case WhiteKingSideCastle:
5408       case WhiteQueenSideCastle:
5409       case BlackKingSideCastle:
5410       case BlackQueenSideCastle:
5411       case WhiteKingSideCastleWild:
5412       case WhiteQueenSideCastleWild:
5413       case BlackKingSideCastleWild:
5414       case BlackQueenSideCastleWild:
5415       /* Code added by Tord: */
5416       case WhiteHSideCastleFR:
5417       case WhiteASideCastleFR:
5418       case BlackHSideCastleFR:
5419       case BlackASideCastleFR:
5420       /* End of code added by Tord */
5421       case IllegalMove:         /* bug or odd chess variant */
5422         *fromX = currentMoveString[0] - AAA;
5423         *fromY = currentMoveString[1] - ONE;
5424         *toX = currentMoveString[2] - AAA;
5425         *toY = currentMoveString[3] - ONE;
5426         *promoChar = currentMoveString[4];
5427         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5428             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5429     if (appData.debugMode) {
5430         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5431     }
5432             *fromX = *fromY = *toX = *toY = 0;
5433             return FALSE;
5434         }
5435         if (appData.testLegality) {
5436           return (*moveType != IllegalMove);
5437         } else {
5438           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5439                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5440         }
5441
5442       case WhiteDrop:
5443       case BlackDrop:
5444         *fromX = *moveType == WhiteDrop ?
5445           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5446           (int) CharToPiece(ToLower(currentMoveString[0]));
5447         *fromY = DROP_RANK;
5448         *toX = currentMoveString[2] - AAA;
5449         *toY = currentMoveString[3] - ONE;
5450         *promoChar = NULLCHAR;
5451         return TRUE;
5452
5453       case AmbiguousMove:
5454       case ImpossibleMove:
5455       case EndOfFile:
5456       case ElapsedTime:
5457       case Comment:
5458       case PGNTag:
5459       case NAG:
5460       case WhiteWins:
5461       case BlackWins:
5462       case GameIsDrawn:
5463       default:
5464     if (appData.debugMode) {
5465         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5466     }
5467         /* bug? */
5468         *fromX = *fromY = *toX = *toY = 0;
5469         *promoChar = NULLCHAR;
5470         return FALSE;
5471     }
5472 }
5473
5474 Boolean pushed = FALSE;
5475 char *lastParseAttempt;
5476
5477 void
5478 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5479 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5480   int fromX, fromY, toX, toY; char promoChar;
5481   ChessMove moveType;
5482   Boolean valid;
5483   int nr = 0;
5484
5485   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5486   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5487     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5488     pushed = TRUE;
5489   }
5490   endPV = forwardMostMove;
5491   do {
5492     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5493     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5494     lastParseAttempt = pv;
5495     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5496     if(!valid && nr == 0 &&
5497        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5498         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5499         // Hande case where played move is different from leading PV move
5500         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5501         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5502         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5503         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5504           endPV += 2; // if position different, keep this
5505           moveList[endPV-1][0] = fromX + AAA;
5506           moveList[endPV-1][1] = fromY + ONE;
5507           moveList[endPV-1][2] = toX + AAA;
5508           moveList[endPV-1][3] = toY + ONE;
5509           parseList[endPV-1][0] = NULLCHAR;
5510           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5511         }
5512       }
5513     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5514     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5515     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5516     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5517         valid++; // allow comments in PV
5518         continue;
5519     }
5520     nr++;
5521     if(endPV+1 > framePtr) break; // no space, truncate
5522     if(!valid) break;
5523     endPV++;
5524     CopyBoard(boards[endPV], boards[endPV-1]);
5525     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5526     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5527     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5528     CoordsToAlgebraic(boards[endPV - 1],
5529                              PosFlags(endPV - 1),
5530                              fromY, fromX, toY, toX, promoChar,
5531                              parseList[endPV - 1]);
5532   } while(valid);
5533   if(atEnd == 2) return; // used hidden, for PV conversion
5534   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5535   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5536   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5537                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5538   DrawPosition(TRUE, boards[currentMove]);
5539 }
5540
5541 int
5542 MultiPV (ChessProgramState *cps)
5543 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5544         int i;
5545         for(i=0; i<cps->nrOptions; i++)
5546             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5547                 return i;
5548         return -1;
5549 }
5550
5551 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5552
5553 Boolean
5554 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5555 {
5556         int startPV, multi, lineStart, origIndex = index;
5557         char *p, buf2[MSG_SIZ];
5558         ChessProgramState *cps = (pane ? &second : &first);
5559
5560         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5561         lastX = x; lastY = y;
5562         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5563         lineStart = startPV = index;
5564         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5565         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5566         index = startPV;
5567         do{ while(buf[index] && buf[index] != '\n') index++;
5568         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5569         buf[index] = 0;
5570         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5571                 int n = cps->option[multi].value;
5572                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5573                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5574                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5575                 cps->option[multi].value = n;
5576                 *start = *end = 0;
5577                 return FALSE;
5578         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5579                 ExcludeClick(origIndex - lineStart);
5580                 return FALSE;
5581         }
5582         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5583         *start = startPV; *end = index-1;
5584         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5585         return TRUE;
5586 }
5587
5588 char *
5589 PvToSAN (char *pv)
5590 {
5591         static char buf[10*MSG_SIZ];
5592         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5593         *buf = NULLCHAR;
5594         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5595         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5596         for(i = forwardMostMove; i<endPV; i++){
5597             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5598             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5599             k += strlen(buf+k);
5600         }
5601         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5602         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5603         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5604         endPV = savedEnd;
5605         return buf;
5606 }
5607
5608 Boolean
5609 LoadPV (int x, int y)
5610 { // called on right mouse click to load PV
5611   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5612   lastX = x; lastY = y;
5613   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5614   extendGame = FALSE;
5615   return TRUE;
5616 }
5617
5618 void
5619 UnLoadPV ()
5620 {
5621   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5622   if(endPV < 0) return;
5623   if(appData.autoCopyPV) CopyFENToClipboard();
5624   endPV = -1;
5625   if(extendGame && currentMove > forwardMostMove) {
5626         Boolean saveAnimate = appData.animate;
5627         if(pushed) {
5628             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5629                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5630             } else storedGames--; // abandon shelved tail of original game
5631         }
5632         pushed = FALSE;
5633         forwardMostMove = currentMove;
5634         currentMove = oldFMM;
5635         appData.animate = FALSE;
5636         ToNrEvent(forwardMostMove);
5637         appData.animate = saveAnimate;
5638   }
5639   currentMove = forwardMostMove;
5640   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5641   ClearPremoveHighlights();
5642   DrawPosition(TRUE, boards[currentMove]);
5643 }
5644
5645 void
5646 MovePV (int x, int y, int h)
5647 { // step through PV based on mouse coordinates (called on mouse move)
5648   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5649
5650   // we must somehow check if right button is still down (might be released off board!)
5651   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5652   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5653   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5654   if(!step) return;
5655   lastX = x; lastY = y;
5656
5657   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5658   if(endPV < 0) return;
5659   if(y < margin) step = 1; else
5660   if(y > h - margin) step = -1;
5661   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5662   currentMove += step;
5663   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5664   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5665                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5666   DrawPosition(FALSE, boards[currentMove]);
5667 }
5668
5669
5670 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5671 // All positions will have equal probability, but the current method will not provide a unique
5672 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5673 #define DARK 1
5674 #define LITE 2
5675 #define ANY 3
5676
5677 int squaresLeft[4];
5678 int piecesLeft[(int)BlackPawn];
5679 int seed, nrOfShuffles;
5680
5681 void
5682 GetPositionNumber ()
5683 {       // sets global variable seed
5684         int i;
5685
5686         seed = appData.defaultFrcPosition;
5687         if(seed < 0) { // randomize based on time for negative FRC position numbers
5688                 for(i=0; i<50; i++) seed += random();
5689                 seed = random() ^ random() >> 8 ^ random() << 8;
5690                 if(seed<0) seed = -seed;
5691         }
5692 }
5693
5694 int
5695 put (Board board, int pieceType, int rank, int n, int shade)
5696 // put the piece on the (n-1)-th empty squares of the given shade
5697 {
5698         int i;
5699
5700         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5701                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5702                         board[rank][i] = (ChessSquare) pieceType;
5703                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5704                         squaresLeft[ANY]--;
5705                         piecesLeft[pieceType]--;
5706                         return i;
5707                 }
5708         }
5709         return -1;
5710 }
5711
5712
5713 void
5714 AddOnePiece (Board board, int pieceType, int rank, int shade)
5715 // calculate where the next piece goes, (any empty square), and put it there
5716 {
5717         int i;
5718
5719         i = seed % squaresLeft[shade];
5720         nrOfShuffles *= squaresLeft[shade];
5721         seed /= squaresLeft[shade];
5722         put(board, pieceType, rank, i, shade);
5723 }
5724
5725 void
5726 AddTwoPieces (Board board, int pieceType, int rank)
5727 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5728 {
5729         int i, n=squaresLeft[ANY], j=n-1, k;
5730
5731         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5732         i = seed % k;  // pick one
5733         nrOfShuffles *= k;
5734         seed /= k;
5735         while(i >= j) i -= j--;
5736         j = n - 1 - j; i += j;
5737         put(board, pieceType, rank, j, ANY);
5738         put(board, pieceType, rank, i, ANY);
5739 }
5740
5741 void
5742 SetUpShuffle (Board board, int number)
5743 {
5744         int i, p, first=1;
5745
5746         GetPositionNumber(); nrOfShuffles = 1;
5747
5748         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5749         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5750         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5751
5752         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5753
5754         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5755             p = (int) board[0][i];
5756             if(p < (int) BlackPawn) piecesLeft[p] ++;
5757             board[0][i] = EmptySquare;
5758         }
5759
5760         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5761             // shuffles restricted to allow normal castling put KRR first
5762             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5763                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5764             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5765                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5766             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5767                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5768             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5769                 put(board, WhiteRook, 0, 0, ANY);
5770             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5771         }
5772
5773         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5774             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5775             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5776                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5777                 while(piecesLeft[p] >= 2) {
5778                     AddOnePiece(board, p, 0, LITE);
5779                     AddOnePiece(board, p, 0, DARK);
5780                 }
5781                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5782             }
5783
5784         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5785             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5786             // but we leave King and Rooks for last, to possibly obey FRC restriction
5787             if(p == (int)WhiteRook) continue;
5788             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5789             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5790         }
5791
5792         // now everything is placed, except perhaps King (Unicorn) and Rooks
5793
5794         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5795             // Last King gets castling rights
5796             while(piecesLeft[(int)WhiteUnicorn]) {
5797                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5798                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5799             }
5800
5801             while(piecesLeft[(int)WhiteKing]) {
5802                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5803                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5804             }
5805
5806
5807         } else {
5808             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5809             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5810         }
5811
5812         // Only Rooks can be left; simply place them all
5813         while(piecesLeft[(int)WhiteRook]) {
5814                 i = put(board, WhiteRook, 0, 0, ANY);
5815                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5816                         if(first) {
5817                                 first=0;
5818                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5819                         }
5820                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5821                 }
5822         }
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5824             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5825         }
5826
5827         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5828 }
5829
5830 int
5831 SetCharTable (char *table, const char * map)
5832 /* [HGM] moved here from winboard.c because of its general usefulness */
5833 /*       Basically a safe strcpy that uses the last character as King */
5834 {
5835     int result = FALSE; int NrPieces;
5836
5837     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5838                     && NrPieces >= 12 && !(NrPieces&1)) {
5839         int i; /* [HGM] Accept even length from 12 to 34 */
5840
5841         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5842         for( i=0; i<NrPieces/2-1; i++ ) {
5843             table[i] = map[i];
5844             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5845         }
5846         table[(int) WhiteKing]  = map[NrPieces/2-1];
5847         table[(int) BlackKing]  = map[NrPieces-1];
5848
5849         result = TRUE;
5850     }
5851
5852     return result;
5853 }
5854
5855 void
5856 Prelude (Board board)
5857 {       // [HGM] superchess: random selection of exo-pieces
5858         int i, j, k; ChessSquare p;
5859         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5860
5861         GetPositionNumber(); // use FRC position number
5862
5863         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5864             SetCharTable(pieceToChar, appData.pieceToCharTable);
5865             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5866                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5867         }
5868
5869         j = seed%4;                 seed /= 4;
5870         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5871         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5872         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5873         j = seed%3 + (seed%3 >= j); seed /= 3;
5874         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5875         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5876         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5877         j = seed%3;                 seed /= 3;
5878         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5879         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5880         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5881         j = seed%2 + (seed%2 >= j); seed /= 2;
5882         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5883         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5884         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5885         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5886         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5887         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5888         put(board, exoPieces[0],    0, 0, ANY);
5889         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5890 }
5891
5892 void
5893 InitPosition (int redraw)
5894 {
5895     ChessSquare (* pieces)[BOARD_FILES];
5896     int i, j, pawnRow=1, pieceRows=1, overrule,
5897     oldx = gameInfo.boardWidth,
5898     oldy = gameInfo.boardHeight,
5899     oldh = gameInfo.holdingsWidth;
5900     static int oldv;
5901
5902     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5903
5904     /* [AS] Initialize pv info list [HGM] and game status */
5905     {
5906         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5907             pvInfoList[i].depth = 0;
5908             boards[i][EP_STATUS] = EP_NONE;
5909             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5910         }
5911
5912         initialRulePlies = 0; /* 50-move counter start */
5913
5914         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5915         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5916     }
5917
5918
5919     /* [HGM] logic here is completely changed. In stead of full positions */
5920     /* the initialized data only consist of the two backranks. The switch */
5921     /* selects which one we will use, which is than copied to the Board   */
5922     /* initialPosition, which for the rest is initialized by Pawns and    */
5923     /* empty squares. This initial position is then copied to boards[0],  */
5924     /* possibly after shuffling, so that it remains available.            */
5925
5926     gameInfo.holdingsWidth = 0; /* default board sizes */
5927     gameInfo.boardWidth    = 8;
5928     gameInfo.boardHeight   = 8;
5929     gameInfo.holdingsSize  = 0;
5930     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5931     for(i=0; i<BOARD_FILES-2; i++)
5932       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5933     initialPosition[EP_STATUS] = EP_NONE;
5934     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5935     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5936          SetCharTable(pieceNickName, appData.pieceNickNames);
5937     else SetCharTable(pieceNickName, "............");
5938     pieces = FIDEArray;
5939
5940     switch (gameInfo.variant) {
5941     case VariantFischeRandom:
5942       shuffleOpenings = TRUE;
5943     default:
5944       break;
5945     case VariantShatranj:
5946       pieces = ShatranjArray;
5947       nrCastlingRights = 0;
5948       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5949       break;
5950     case VariantMakruk:
5951       pieces = makrukArray;
5952       nrCastlingRights = 0;
5953       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5954       break;
5955     case VariantASEAN:
5956       pieces = aseanArray;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5959       break;
5960     case VariantTwoKings:
5961       pieces = twoKingsArray;
5962       break;
5963     case VariantGrand:
5964       pieces = GrandArray;
5965       nrCastlingRights = 0;
5966       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5967       gameInfo.boardWidth = 10;
5968       gameInfo.boardHeight = 10;
5969       gameInfo.holdingsSize = 7;
5970       break;
5971     case VariantCapaRandom:
5972       shuffleOpenings = TRUE;
5973     case VariantCapablanca:
5974       pieces = CapablancaArray;
5975       gameInfo.boardWidth = 10;
5976       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5977       break;
5978     case VariantGothic:
5979       pieces = GothicArray;
5980       gameInfo.boardWidth = 10;
5981       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5982       break;
5983     case VariantSChess:
5984       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5985       gameInfo.holdingsSize = 7;
5986       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5987       break;
5988     case VariantJanus:
5989       pieces = JanusArray;
5990       gameInfo.boardWidth = 10;
5991       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5992       nrCastlingRights = 6;
5993         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5994         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5995         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5996         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5997         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5998         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5999       break;
6000     case VariantFalcon:
6001       pieces = FalconArray;
6002       gameInfo.boardWidth = 10;
6003       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6004       break;
6005     case VariantXiangqi:
6006       pieces = XiangqiArray;
6007       gameInfo.boardWidth  = 9;
6008       gameInfo.boardHeight = 10;
6009       nrCastlingRights = 0;
6010       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6011       break;
6012     case VariantShogi:
6013       pieces = ShogiArray;
6014       gameInfo.boardWidth  = 9;
6015       gameInfo.boardHeight = 9;
6016       gameInfo.holdingsSize = 7;
6017       nrCastlingRights = 0;
6018       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6019       break;
6020     case VariantChu:
6021       pieces = ChuArray; pieceRows = 3;
6022       gameInfo.boardWidth  = 12;
6023       gameInfo.boardHeight = 12;
6024       nrCastlingRights = 0;
6025       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6026                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6027       break;
6028     case VariantCourier:
6029       pieces = CourierArray;
6030       gameInfo.boardWidth  = 12;
6031       nrCastlingRights = 0;
6032       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6033       break;
6034     case VariantKnightmate:
6035       pieces = KnightmateArray;
6036       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6037       break;
6038     case VariantSpartan:
6039       pieces = SpartanArray;
6040       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6041       break;
6042     case VariantLion:
6043       pieces = lionArray;
6044       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6045       break;
6046     case VariantFairy:
6047       pieces = fairyArray;
6048       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6049       break;
6050     case VariantGreat:
6051       pieces = GreatArray;
6052       gameInfo.boardWidth = 10;
6053       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6054       gameInfo.holdingsSize = 8;
6055       break;
6056     case VariantSuper:
6057       pieces = FIDEArray;
6058       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6059       gameInfo.holdingsSize = 8;
6060       startedFromSetupPosition = TRUE;
6061       break;
6062     case VariantCrazyhouse:
6063     case VariantBughouse:
6064       pieces = FIDEArray;
6065       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6066       gameInfo.holdingsSize = 5;
6067       break;
6068     case VariantWildCastle:
6069       pieces = FIDEArray;
6070       /* !!?shuffle with kings guaranteed to be on d or e file */
6071       shuffleOpenings = 1;
6072       break;
6073     case VariantNoCastle:
6074       pieces = FIDEArray;
6075       nrCastlingRights = 0;
6076       /* !!?unconstrained back-rank shuffle */
6077       shuffleOpenings = 1;
6078       break;
6079     }
6080
6081     overrule = 0;
6082     if(appData.NrFiles >= 0) {
6083         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6084         gameInfo.boardWidth = appData.NrFiles;
6085     }
6086     if(appData.NrRanks >= 0) {
6087         gameInfo.boardHeight = appData.NrRanks;
6088     }
6089     if(appData.holdingsSize >= 0) {
6090         i = appData.holdingsSize;
6091         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6092         gameInfo.holdingsSize = i;
6093     }
6094     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6095     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6096         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6097
6098     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6099     if(pawnRow < 1) pawnRow = 1;
6100     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6101     if(gameInfo.variant == VariantChu) pawnRow = 3;
6102
6103     /* User pieceToChar list overrules defaults */
6104     if(appData.pieceToCharTable != NULL)
6105         SetCharTable(pieceToChar, appData.pieceToCharTable);
6106
6107     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6108
6109         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6110             s = (ChessSquare) 0; /* account holding counts in guard band */
6111         for( i=0; i<BOARD_HEIGHT; i++ )
6112             initialPosition[i][j] = s;
6113
6114         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6115         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6116         initialPosition[pawnRow][j] = WhitePawn;
6117         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6118         if(gameInfo.variant == VariantXiangqi) {
6119             if(j&1) {
6120                 initialPosition[pawnRow][j] =
6121                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6122                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6123                    initialPosition[2][j] = WhiteCannon;
6124                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6125                 }
6126             }
6127         }
6128         if(gameInfo.variant == VariantChu) {
6129              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6130                initialPosition[pawnRow+1][j] = WhiteCobra,
6131                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6132              for(i=1; i<pieceRows; i++) {
6133                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6134                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6135              }
6136         }
6137         if(gameInfo.variant == VariantGrand) {
6138             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6139                initialPosition[0][j] = WhiteRook;
6140                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6141             }
6142         }
6143         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6144     }
6145     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6146
6147             j=BOARD_LEFT+1;
6148             initialPosition[1][j] = WhiteBishop;
6149             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6150             j=BOARD_RGHT-2;
6151             initialPosition[1][j] = WhiteRook;
6152             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6153     }
6154
6155     if( nrCastlingRights == -1) {
6156         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6157         /*       This sets default castling rights from none to normal corners   */
6158         /* Variants with other castling rights must set them themselves above    */
6159         nrCastlingRights = 6;
6160
6161         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6162         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6163         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6164         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6165         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6166         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6167      }
6168
6169      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6170      if(gameInfo.variant == VariantGreat) { // promotion commoners
6171         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6172         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6173         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6174         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6175      }
6176      if( gameInfo.variant == VariantSChess ) {
6177       initialPosition[1][0] = BlackMarshall;
6178       initialPosition[2][0] = BlackAngel;
6179       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6180       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6181       initialPosition[1][1] = initialPosition[2][1] =
6182       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6183      }
6184   if (appData.debugMode) {
6185     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6186   }
6187     if(shuffleOpenings) {
6188         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6189         startedFromSetupPosition = TRUE;
6190     }
6191     if(startedFromPositionFile) {
6192       /* [HGM] loadPos: use PositionFile for every new game */
6193       CopyBoard(initialPosition, filePosition);
6194       for(i=0; i<nrCastlingRights; i++)
6195           initialRights[i] = filePosition[CASTLING][i];
6196       startedFromSetupPosition = TRUE;
6197     }
6198
6199     CopyBoard(boards[0], initialPosition);
6200
6201     if(oldx != gameInfo.boardWidth ||
6202        oldy != gameInfo.boardHeight ||
6203        oldv != gameInfo.variant ||
6204        oldh != gameInfo.holdingsWidth
6205                                          )
6206             InitDrawingSizes(-2 ,0);
6207
6208     oldv = gameInfo.variant;
6209     if (redraw)
6210       DrawPosition(TRUE, boards[currentMove]);
6211 }
6212
6213 void
6214 SendBoard (ChessProgramState *cps, int moveNum)
6215 {
6216     char message[MSG_SIZ];
6217
6218     if (cps->useSetboard) {
6219       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6220       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6221       SendToProgram(message, cps);
6222       free(fen);
6223
6224     } else {
6225       ChessSquare *bp;
6226       int i, j, left=0, right=BOARD_WIDTH;
6227       /* Kludge to set black to move, avoiding the troublesome and now
6228        * deprecated "black" command.
6229        */
6230       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6231         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6232
6233       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6234
6235       SendToProgram("edit\n", cps);
6236       SendToProgram("#\n", cps);
6237       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6238         bp = &boards[moveNum][i][left];
6239         for (j = left; j < right; j++, bp++) {
6240           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6241           if ((int) *bp < (int) BlackPawn) {
6242             if(j == BOARD_RGHT+1)
6243                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6244             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6245             if(message[0] == '+' || message[0] == '~') {
6246               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6247                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6248                         AAA + j, ONE + i);
6249             }
6250             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6251                 message[1] = BOARD_RGHT   - 1 - j + '1';
6252                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6253             }
6254             SendToProgram(message, cps);
6255           }
6256         }
6257       }
6258
6259       SendToProgram("c\n", cps);
6260       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6261         bp = &boards[moveNum][i][left];
6262         for (j = left; j < right; j++, bp++) {
6263           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6264           if (((int) *bp != (int) EmptySquare)
6265               && ((int) *bp >= (int) BlackPawn)) {
6266             if(j == BOARD_LEFT-2)
6267                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6268             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6269                     AAA + j, ONE + i);
6270             if(message[0] == '+' || message[0] == '~') {
6271               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6272                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6273                         AAA + j, ONE + i);
6274             }
6275             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6276                 message[1] = BOARD_RGHT   - 1 - j + '1';
6277                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6278             }
6279             SendToProgram(message, cps);
6280           }
6281         }
6282       }
6283
6284       SendToProgram(".\n", cps);
6285     }
6286     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6287 }
6288
6289 char exclusionHeader[MSG_SIZ];
6290 int exCnt, excludePtr;
6291 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6292 static Exclusion excluTab[200];
6293 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6294
6295 static void
6296 WriteMap (int s)
6297 {
6298     int j;
6299     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6300     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6301 }
6302
6303 static void
6304 ClearMap ()
6305 {
6306     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6307     excludePtr = 24; exCnt = 0;
6308     WriteMap(0);
6309 }
6310
6311 static void
6312 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6313 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6314     char buf[2*MOVE_LEN], *p;
6315     Exclusion *e = excluTab;
6316     int i;
6317     for(i=0; i<exCnt; i++)
6318         if(e[i].ff == fromX && e[i].fr == fromY &&
6319            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6320     if(i == exCnt) { // was not in exclude list; add it
6321         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6322         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6323             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6324             return; // abort
6325         }
6326         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6327         excludePtr++; e[i].mark = excludePtr++;
6328         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6329         exCnt++;
6330     }
6331     exclusionHeader[e[i].mark] = state;
6332 }
6333
6334 static int
6335 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6336 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6337     char buf[MSG_SIZ];
6338     int j, k;
6339     ChessMove moveType;
6340     if((signed char)promoChar == -1) { // kludge to indicate best move
6341         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6342             return 1; // if unparsable, abort
6343     }
6344     // update exclusion map (resolving toggle by consulting existing state)
6345     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6346     j = k%8; k >>= 3;
6347     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6348     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6349          excludeMap[k] |=   1<<j;
6350     else excludeMap[k] &= ~(1<<j);
6351     // update header
6352     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6353     // inform engine
6354     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6355     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6356     SendToBoth(buf);
6357     return (state == '+');
6358 }
6359
6360 static void
6361 ExcludeClick (int index)
6362 {
6363     int i, j;
6364     Exclusion *e = excluTab;
6365     if(index < 25) { // none, best or tail clicked
6366         if(index < 13) { // none: include all
6367             WriteMap(0); // clear map
6368             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6369             SendToBoth("include all\n"); // and inform engine
6370         } else if(index > 18) { // tail
6371             if(exclusionHeader[19] == '-') { // tail was excluded
6372                 SendToBoth("include all\n");
6373                 WriteMap(0); // clear map completely
6374                 // now re-exclude selected moves
6375                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6376                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6377             } else { // tail was included or in mixed state
6378                 SendToBoth("exclude all\n");
6379                 WriteMap(0xFF); // fill map completely
6380                 // now re-include selected moves
6381                 j = 0; // count them
6382                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6383                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6384                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6385             }
6386         } else { // best
6387             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6388         }
6389     } else {
6390         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6391             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6392             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6393             break;
6394         }
6395     }
6396 }
6397
6398 ChessSquare
6399 DefaultPromoChoice (int white)
6400 {
6401     ChessSquare result;
6402     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6403        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6404         result = WhiteFerz; // no choice
6405     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6406         result= WhiteKing; // in Suicide Q is the last thing we want
6407     else if(gameInfo.variant == VariantSpartan)
6408         result = white ? WhiteQueen : WhiteAngel;
6409     else result = WhiteQueen;
6410     if(!white) result = WHITE_TO_BLACK result;
6411     return result;
6412 }
6413
6414 static int autoQueen; // [HGM] oneclick
6415
6416 int
6417 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6418 {
6419     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6420     /* [HGM] add Shogi promotions */
6421     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6422     ChessSquare piece;
6423     ChessMove moveType;
6424     Boolean premove;
6425
6426     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6427     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6428
6429     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6430       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6431         return FALSE;
6432
6433     piece = boards[currentMove][fromY][fromX];
6434     if(gameInfo.variant == VariantChu) {
6435         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6436         promotionZoneSize = BOARD_HEIGHT/3;
6437         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteKing;
6438     } else if(gameInfo.variant == VariantShogi) {
6439         promotionZoneSize = BOARD_HEIGHT/3;
6440         highestPromotingPiece = (int)WhiteAlfil;
6441     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6442         promotionZoneSize = 3;
6443     }
6444
6445     // Treat Lance as Pawn when it is not representing Amazon
6446     if(gameInfo.variant != VariantSuper) {
6447         if(piece == WhiteLance) piece = WhitePawn; else
6448         if(piece == BlackLance) piece = BlackPawn;
6449     }
6450
6451     // next weed out all moves that do not touch the promotion zone at all
6452     if((int)piece >= BlackPawn) {
6453         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6454              return FALSE;
6455         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6456     } else {
6457         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6458            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6459     }
6460
6461     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6462
6463     // weed out mandatory Shogi promotions
6464     if(gameInfo.variant == VariantShogi) {
6465         if(piece >= BlackPawn) {
6466             if(toY == 0 && piece == BlackPawn ||
6467                toY == 0 && piece == BlackQueen ||
6468                toY <= 1 && piece == BlackKnight) {
6469                 *promoChoice = '+';
6470                 return FALSE;
6471             }
6472         } else {
6473             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6474                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6475                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6476                 *promoChoice = '+';
6477                 return FALSE;
6478             }
6479         }
6480     }
6481
6482     // weed out obviously illegal Pawn moves
6483     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6484         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6485         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6486         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6487         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6488         // note we are not allowed to test for valid (non-)capture, due to premove
6489     }
6490
6491     // we either have a choice what to promote to, or (in Shogi) whether to promote
6492     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6493        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6494         *promoChoice = PieceToChar(BlackFerz);  // no choice
6495         return FALSE;
6496     }
6497     // no sense asking what we must promote to if it is going to explode...
6498     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6499         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6500         return FALSE;
6501     }
6502     // give caller the default choice even if we will not make it
6503     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6504     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6505     if(        sweepSelect && gameInfo.variant != VariantGreat
6506                            && gameInfo.variant != VariantGrand
6507                            && gameInfo.variant != VariantSuper) return FALSE;
6508     if(autoQueen) return FALSE; // predetermined
6509
6510     // suppress promotion popup on illegal moves that are not premoves
6511     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6512               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6513     if(appData.testLegality && !premove) {
6514         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6515                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6516         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6517             return FALSE;
6518     }
6519
6520     return TRUE;
6521 }
6522
6523 int
6524 InPalace (int row, int column)
6525 {   /* [HGM] for Xiangqi */
6526     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6527          column < (BOARD_WIDTH + 4)/2 &&
6528          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6529     return FALSE;
6530 }
6531
6532 int
6533 PieceForSquare (int x, int y)
6534 {
6535   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6536      return -1;
6537   else
6538      return boards[currentMove][y][x];
6539 }
6540
6541 int
6542 OKToStartUserMove (int x, int y)
6543 {
6544     ChessSquare from_piece;
6545     int white_piece;
6546
6547     if (matchMode) return FALSE;
6548     if (gameMode == EditPosition) return TRUE;
6549
6550     if (x >= 0 && y >= 0)
6551       from_piece = boards[currentMove][y][x];
6552     else
6553       from_piece = EmptySquare;
6554
6555     if (from_piece == EmptySquare) return FALSE;
6556
6557     white_piece = (int)from_piece >= (int)WhitePawn &&
6558       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6559
6560     switch (gameMode) {
6561       case AnalyzeFile:
6562       case TwoMachinesPlay:
6563       case EndOfGame:
6564         return FALSE;
6565
6566       case IcsObserving:
6567       case IcsIdle:
6568         return FALSE;
6569
6570       case MachinePlaysWhite:
6571       case IcsPlayingBlack:
6572         if (appData.zippyPlay) return FALSE;
6573         if (white_piece) {
6574             DisplayMoveError(_("You are playing Black"));
6575             return FALSE;
6576         }
6577         break;
6578
6579       case MachinePlaysBlack:
6580       case IcsPlayingWhite:
6581         if (appData.zippyPlay) return FALSE;
6582         if (!white_piece) {
6583             DisplayMoveError(_("You are playing White"));
6584             return FALSE;
6585         }
6586         break;
6587
6588       case PlayFromGameFile:
6589             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6590       case EditGame:
6591         if (!white_piece && WhiteOnMove(currentMove)) {
6592             DisplayMoveError(_("It is White's turn"));
6593             return FALSE;
6594         }
6595         if (white_piece && !WhiteOnMove(currentMove)) {
6596             DisplayMoveError(_("It is Black's turn"));
6597             return FALSE;
6598         }
6599         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6600             /* Editing correspondence game history */
6601             /* Could disallow this or prompt for confirmation */
6602             cmailOldMove = -1;
6603         }
6604         break;
6605
6606       case BeginningOfGame:
6607         if (appData.icsActive) return FALSE;
6608         if (!appData.noChessProgram) {
6609             if (!white_piece) {
6610                 DisplayMoveError(_("You are playing White"));
6611                 return FALSE;
6612             }
6613         }
6614         break;
6615
6616       case Training:
6617         if (!white_piece && WhiteOnMove(currentMove)) {
6618             DisplayMoveError(_("It is White's turn"));
6619             return FALSE;
6620         }
6621         if (white_piece && !WhiteOnMove(currentMove)) {
6622             DisplayMoveError(_("It is Black's turn"));
6623             return FALSE;
6624         }
6625         break;
6626
6627       default:
6628       case IcsExamining:
6629         break;
6630     }
6631     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6632         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6633         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6634         && gameMode != AnalyzeFile && gameMode != Training) {
6635         DisplayMoveError(_("Displayed position is not current"));
6636         return FALSE;
6637     }
6638     return TRUE;
6639 }
6640
6641 Boolean
6642 OnlyMove (int *x, int *y, Boolean captures)
6643 {
6644     DisambiguateClosure cl;
6645     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6646     switch(gameMode) {
6647       case MachinePlaysBlack:
6648       case IcsPlayingWhite:
6649       case BeginningOfGame:
6650         if(!WhiteOnMove(currentMove)) return FALSE;
6651         break;
6652       case MachinePlaysWhite:
6653       case IcsPlayingBlack:
6654         if(WhiteOnMove(currentMove)) return FALSE;
6655         break;
6656       case EditGame:
6657         break;
6658       default:
6659         return FALSE;
6660     }
6661     cl.pieceIn = EmptySquare;
6662     cl.rfIn = *y;
6663     cl.ffIn = *x;
6664     cl.rtIn = -1;
6665     cl.ftIn = -1;
6666     cl.promoCharIn = NULLCHAR;
6667     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6668     if( cl.kind == NormalMove ||
6669         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6670         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6671         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6672       fromX = cl.ff;
6673       fromY = cl.rf;
6674       *x = cl.ft;
6675       *y = cl.rt;
6676       return TRUE;
6677     }
6678     if(cl.kind != ImpossibleMove) return FALSE;
6679     cl.pieceIn = EmptySquare;
6680     cl.rfIn = -1;
6681     cl.ffIn = -1;
6682     cl.rtIn = *y;
6683     cl.ftIn = *x;
6684     cl.promoCharIn = NULLCHAR;
6685     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6686     if( cl.kind == NormalMove ||
6687         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6688         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6689         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6690       fromX = cl.ff;
6691       fromY = cl.rf;
6692       *x = cl.ft;
6693       *y = cl.rt;
6694       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6695       return TRUE;
6696     }
6697     return FALSE;
6698 }
6699
6700 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6701 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6702 int lastLoadGameUseList = FALSE;
6703 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6704 ChessMove lastLoadGameStart = EndOfFile;
6705 int doubleClick;
6706
6707 void
6708 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6709 {
6710     ChessMove moveType;
6711     ChessSquare pup;
6712     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6713
6714     /* Check if the user is playing in turn.  This is complicated because we
6715        let the user "pick up" a piece before it is his turn.  So the piece he
6716        tried to pick up may have been captured by the time he puts it down!
6717        Therefore we use the color the user is supposed to be playing in this
6718        test, not the color of the piece that is currently on the starting
6719        square---except in EditGame mode, where the user is playing both
6720        sides; fortunately there the capture race can't happen.  (It can
6721        now happen in IcsExamining mode, but that's just too bad.  The user
6722        will get a somewhat confusing message in that case.)
6723        */
6724
6725     switch (gameMode) {
6726       case AnalyzeFile:
6727       case TwoMachinesPlay:
6728       case EndOfGame:
6729       case IcsObserving:
6730       case IcsIdle:
6731         /* We switched into a game mode where moves are not accepted,
6732            perhaps while the mouse button was down. */
6733         return;
6734
6735       case MachinePlaysWhite:
6736         /* User is moving for Black */
6737         if (WhiteOnMove(currentMove)) {
6738             DisplayMoveError(_("It is White's turn"));
6739             return;
6740         }
6741         break;
6742
6743       case MachinePlaysBlack:
6744         /* User is moving for White */
6745         if (!WhiteOnMove(currentMove)) {
6746             DisplayMoveError(_("It is Black's turn"));
6747             return;
6748         }
6749         break;
6750
6751       case PlayFromGameFile:
6752             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6753       case EditGame:
6754       case IcsExamining:
6755       case BeginningOfGame:
6756       case AnalyzeMode:
6757       case Training:
6758         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6759         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6760             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6761             /* User is moving for Black */
6762             if (WhiteOnMove(currentMove)) {
6763                 DisplayMoveError(_("It is White's turn"));
6764                 return;
6765             }
6766         } else {
6767             /* User is moving for White */
6768             if (!WhiteOnMove(currentMove)) {
6769                 DisplayMoveError(_("It is Black's turn"));
6770                 return;
6771             }
6772         }
6773         break;
6774
6775       case IcsPlayingBlack:
6776         /* User is moving for Black */
6777         if (WhiteOnMove(currentMove)) {
6778             if (!appData.premove) {
6779                 DisplayMoveError(_("It is White's turn"));
6780             } else if (toX >= 0 && toY >= 0) {
6781                 premoveToX = toX;
6782                 premoveToY = toY;
6783                 premoveFromX = fromX;
6784                 premoveFromY = fromY;
6785                 premovePromoChar = promoChar;
6786                 gotPremove = 1;
6787                 if (appData.debugMode)
6788                     fprintf(debugFP, "Got premove: fromX %d,"
6789                             "fromY %d, toX %d, toY %d\n",
6790                             fromX, fromY, toX, toY);
6791             }
6792             return;
6793         }
6794         break;
6795
6796       case IcsPlayingWhite:
6797         /* User is moving for White */
6798         if (!WhiteOnMove(currentMove)) {
6799             if (!appData.premove) {
6800                 DisplayMoveError(_("It is Black's turn"));
6801             } else if (toX >= 0 && toY >= 0) {
6802                 premoveToX = toX;
6803                 premoveToY = toY;
6804                 premoveFromX = fromX;
6805                 premoveFromY = fromY;
6806                 premovePromoChar = promoChar;
6807                 gotPremove = 1;
6808                 if (appData.debugMode)
6809                     fprintf(debugFP, "Got premove: fromX %d,"
6810                             "fromY %d, toX %d, toY %d\n",
6811                             fromX, fromY, toX, toY);
6812             }
6813             return;
6814         }
6815         break;
6816
6817       default:
6818         break;
6819
6820       case EditPosition:
6821         /* EditPosition, empty square, or different color piece;
6822            click-click move is possible */
6823         if (toX == -2 || toY == -2) {
6824             boards[0][fromY][fromX] = EmptySquare;
6825             DrawPosition(FALSE, boards[currentMove]);
6826             return;
6827         } else if (toX >= 0 && toY >= 0) {
6828             boards[0][toY][toX] = boards[0][fromY][fromX];
6829             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6830                 if(boards[0][fromY][0] != EmptySquare) {
6831                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6832                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6833                 }
6834             } else
6835             if(fromX == BOARD_RGHT+1) {
6836                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6837                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6838                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6839                 }
6840             } else
6841             boards[0][fromY][fromX] = gatingPiece;
6842             DrawPosition(FALSE, boards[currentMove]);
6843             return;
6844         }
6845         return;
6846     }
6847
6848     if(toX < 0 || toY < 0) return;
6849     pup = boards[currentMove][toY][toX];
6850
6851     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6852     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6853          if( pup != EmptySquare ) return;
6854          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6855            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6856                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6857            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6858            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6859            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6860            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6861          fromY = DROP_RANK;
6862     }
6863
6864     /* [HGM] always test for legality, to get promotion info */
6865     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6866                                          fromY, fromX, toY, toX, promoChar);
6867
6868     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6869
6870     /* [HGM] but possibly ignore an IllegalMove result */
6871     if (appData.testLegality) {
6872         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6873             DisplayMoveError(_("Illegal move"));
6874             return;
6875         }
6876     }
6877
6878     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6879         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6880              ClearPremoveHighlights(); // was included
6881         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6882         return;
6883     }
6884
6885     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6886 }
6887
6888 /* Common tail of UserMoveEvent and DropMenuEvent */
6889 int
6890 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6891 {
6892     char *bookHit = 0;
6893
6894     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6895         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6896         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6897         if(WhiteOnMove(currentMove)) {
6898             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6899         } else {
6900             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6901         }
6902     }
6903
6904     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6905        move type in caller when we know the move is a legal promotion */
6906     if(moveType == NormalMove && promoChar)
6907         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6908
6909     /* [HGM] <popupFix> The following if has been moved here from
6910        UserMoveEvent(). Because it seemed to belong here (why not allow
6911        piece drops in training games?), and because it can only be
6912        performed after it is known to what we promote. */
6913     if (gameMode == Training) {
6914       /* compare the move played on the board to the next move in the
6915        * game. If they match, display the move and the opponent's response.
6916        * If they don't match, display an error message.
6917        */
6918       int saveAnimate;
6919       Board testBoard;
6920       CopyBoard(testBoard, boards[currentMove]);
6921       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6922
6923       if (CompareBoards(testBoard, boards[currentMove+1])) {
6924         ForwardInner(currentMove+1);
6925
6926         /* Autoplay the opponent's response.
6927          * if appData.animate was TRUE when Training mode was entered,
6928          * the response will be animated.
6929          */
6930         saveAnimate = appData.animate;
6931         appData.animate = animateTraining;
6932         ForwardInner(currentMove+1);
6933         appData.animate = saveAnimate;
6934
6935         /* check for the end of the game */
6936         if (currentMove >= forwardMostMove) {
6937           gameMode = PlayFromGameFile;
6938           ModeHighlight();
6939           SetTrainingModeOff();
6940           DisplayInformation(_("End of game"));
6941         }
6942       } else {
6943         DisplayError(_("Incorrect move"), 0);
6944       }
6945       return 1;
6946     }
6947
6948   /* Ok, now we know that the move is good, so we can kill
6949      the previous line in Analysis Mode */
6950   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6951                                 && currentMove < forwardMostMove) {
6952     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6953     else forwardMostMove = currentMove;
6954   }
6955
6956   ClearMap();
6957
6958   /* If we need the chess program but it's dead, restart it */
6959   ResurrectChessProgram();
6960
6961   /* A user move restarts a paused game*/
6962   if (pausing)
6963     PauseEvent();
6964
6965   thinkOutput[0] = NULLCHAR;
6966
6967   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6968
6969   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6970     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6971     return 1;
6972   }
6973
6974   if (gameMode == BeginningOfGame) {
6975     if (appData.noChessProgram) {
6976       gameMode = EditGame;
6977       SetGameInfo();
6978     } else {
6979       char buf[MSG_SIZ];
6980       gameMode = MachinePlaysBlack;
6981       StartClocks();
6982       SetGameInfo();
6983       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6984       DisplayTitle(buf);
6985       if (first.sendName) {
6986         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6987         SendToProgram(buf, &first);
6988       }
6989       StartClocks();
6990     }
6991     ModeHighlight();
6992   }
6993
6994   /* Relay move to ICS or chess engine */
6995   if (appData.icsActive) {
6996     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6997         gameMode == IcsExamining) {
6998       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6999         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7000         SendToICS("draw ");
7001         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7002       }
7003       // also send plain move, in case ICS does not understand atomic claims
7004       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7005       ics_user_moved = 1;
7006     }
7007   } else {
7008     if (first.sendTime && (gameMode == BeginningOfGame ||
7009                            gameMode == MachinePlaysWhite ||
7010                            gameMode == MachinePlaysBlack)) {
7011       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7012     }
7013     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7014          // [HGM] book: if program might be playing, let it use book
7015         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7016         first.maybeThinking = TRUE;
7017     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7018         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7019         SendBoard(&first, currentMove+1);
7020         if(second.analyzing) {
7021             if(!second.useSetboard) SendToProgram("undo\n", &second);
7022             SendBoard(&second, currentMove+1);
7023         }
7024     } else {
7025         SendMoveToProgram(forwardMostMove-1, &first);
7026         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7027     }
7028     if (currentMove == cmailOldMove + 1) {
7029       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7030     }
7031   }
7032
7033   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7034
7035   switch (gameMode) {
7036   case EditGame:
7037     if(appData.testLegality)
7038     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7039     case MT_NONE:
7040     case MT_CHECK:
7041       break;
7042     case MT_CHECKMATE:
7043     case MT_STAINMATE:
7044       if (WhiteOnMove(currentMove)) {
7045         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7046       } else {
7047         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7048       }
7049       break;
7050     case MT_STALEMATE:
7051       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7052       break;
7053     }
7054     break;
7055
7056   case MachinePlaysBlack:
7057   case MachinePlaysWhite:
7058     /* disable certain menu options while machine is thinking */
7059     SetMachineThinkingEnables();
7060     break;
7061
7062   default:
7063     break;
7064   }
7065
7066   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7067   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7068
7069   if(bookHit) { // [HGM] book: simulate book reply
7070         static char bookMove[MSG_SIZ]; // a bit generous?
7071
7072         programStats.nodes = programStats.depth = programStats.time =
7073         programStats.score = programStats.got_only_move = 0;
7074         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7075
7076         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7077         strcat(bookMove, bookHit);
7078         HandleMachineMove(bookMove, &first);
7079   }
7080   return 1;
7081 }
7082
7083 void
7084 MarkByFEN(char *fen)
7085 {
7086         int r, f;
7087         if(!appData.markers || !appData.highlightDragging) return;
7088         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7089         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7090         while(*fen) {
7091             int s = 0;
7092             marker[r][f] = 0;
7093             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7094             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7095             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7096             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7097             if(*fen == 'T') marker[r][f++] = 0; else
7098             if(*fen == 'Y') marker[r][f++] = 1; else
7099             if(*fen == 'G') marker[r][f++] = 3; else
7100             if(*fen == 'B') marker[r][f++] = 4; else
7101             if(*fen == 'C') marker[r][f++] = 5; else
7102             if(*fen == 'M') marker[r][f++] = 6; else
7103             if(*fen == 'W') marker[r][f++] = 7; else
7104             if(*fen == 'D') marker[r][f++] = 8; else
7105             if(*fen == 'R') marker[r][f++] = 2; else {
7106                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7107               f += s; fen -= s>0;
7108             }
7109             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7110             if(r < 0) break;
7111             fen++;
7112         }
7113         DrawPosition(TRUE, NULL);
7114 }
7115
7116 void
7117 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7118 {
7119     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7120     Markers *m = (Markers *) closure;
7121     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7122         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7123                          || kind == WhiteCapturesEnPassant
7124                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7125     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7126 }
7127
7128 void
7129 MarkTargetSquares (int clear)
7130 {
7131   int x, y, sum=0;
7132   if(clear) { // no reason to ever suppress clearing
7133     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7134     if(!sum) return; // nothing was cleared,no redraw needed
7135   } else {
7136     int capt = 0;
7137     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7138        !appData.testLegality || gameMode == EditPosition) return;
7139     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7140     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7141       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7142       if(capt)
7143       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7144     }
7145   }
7146   DrawPosition(FALSE, NULL);
7147 }
7148
7149 int
7150 Explode (Board board, int fromX, int fromY, int toX, int toY)
7151 {
7152     if(gameInfo.variant == VariantAtomic &&
7153        (board[toY][toX] != EmptySquare ||                     // capture?
7154         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7155                          board[fromY][fromX] == BlackPawn   )
7156       )) {
7157         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7158         return TRUE;
7159     }
7160     return FALSE;
7161 }
7162
7163 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7164
7165 int
7166 CanPromote (ChessSquare piece, int y)
7167 {
7168         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7169         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7170         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7171            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7172            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7173          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7174         return (piece == BlackPawn && y == 1 ||
7175                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7176                 piece == BlackLance && y == 1 ||
7177                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7178 }
7179
7180 void
7181 HoverEvent (int xPix, int yPix, int x, int y)
7182 {
7183         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7184         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7185         int r, f;
7186         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7187         if(!first.highlight) return;
7188         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7189         if(x == oldX && y == oldY) return; // only do something if we enter new square
7190         oldFromX = fromX; oldFromY = fromY;
7191         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7192           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7193             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7194         else if(oldX != x || oldY != y) {
7195           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7196           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7197             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7198           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7199             char buf[MSG_SIZ];
7200             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7201             SendToProgram(buf, &first);
7202           }
7203           oldX = x; oldY = y;
7204 //        SetHighlights(fromX, fromY, x, y);
7205         }
7206 }
7207
7208 void ReportClick(char *action, int x, int y)
7209 {
7210         char buf[MSG_SIZ]; // Inform engine of what user does
7211         int r, f;
7212         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7213           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7214         if(!first.highlight || gameMode == EditPosition) return;
7215         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7216         SendToProgram(buf, &first);
7217 }
7218
7219 void
7220 LeftClick (ClickType clickType, int xPix, int yPix)
7221 {
7222     int x, y;
7223     Boolean saveAnimate;
7224     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7225     char promoChoice = NULLCHAR;
7226     ChessSquare piece;
7227     static TimeMark lastClickTime, prevClickTime;
7228
7229     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7230
7231     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7232
7233     if (clickType == Press) ErrorPopDown();
7234     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7235
7236     x = EventToSquare(xPix, BOARD_WIDTH);
7237     y = EventToSquare(yPix, BOARD_HEIGHT);
7238     if (!flipView && y >= 0) {
7239         y = BOARD_HEIGHT - 1 - y;
7240     }
7241     if (flipView && x >= 0) {
7242         x = BOARD_WIDTH - 1 - x;
7243     }
7244
7245     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7246         defaultPromoChoice = promoSweep;
7247         promoSweep = EmptySquare;   // terminate sweep
7248         promoDefaultAltered = TRUE;
7249         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7250     }
7251
7252     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7253         if(clickType == Release) return; // ignore upclick of click-click destination
7254         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7255         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7256         if(gameInfo.holdingsWidth &&
7257                 (WhiteOnMove(currentMove)
7258                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7259                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7260             // click in right holdings, for determining promotion piece
7261             ChessSquare p = boards[currentMove][y][x];
7262             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7263             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7264             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7265                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7266                 fromX = fromY = -1;
7267                 return;
7268             }
7269         }
7270         DrawPosition(FALSE, boards[currentMove]);
7271         return;
7272     }
7273
7274     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7275     if(clickType == Press
7276             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7277               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7278               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7279         return;
7280
7281     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7282         // could be static click on premove from-square: abort premove
7283         gotPremove = 0;
7284         ClearPremoveHighlights();
7285     }
7286
7287     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7288         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7289
7290     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7291         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7292                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7293         defaultPromoChoice = DefaultPromoChoice(side);
7294     }
7295
7296     autoQueen = appData.alwaysPromoteToQueen;
7297
7298     if (fromX == -1) {
7299       int originalY = y;
7300       gatingPiece = EmptySquare;
7301       if (clickType != Press) {
7302         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7303             DragPieceEnd(xPix, yPix); dragging = 0;
7304             DrawPosition(FALSE, NULL);
7305         }
7306         return;
7307       }
7308       doubleClick = FALSE;
7309       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7310         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7311       }
7312       fromX = x; fromY = y; toX = toY = -1;
7313       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7314          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7315          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7316             /* First square */
7317             if (OKToStartUserMove(fromX, fromY)) {
7318                 second = 0;
7319                 ReportClick("lift", x, y);
7320                 MarkTargetSquares(0);
7321                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7322                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7323                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7324                     promoSweep = defaultPromoChoice;
7325                     selectFlag = 0; lastX = xPix; lastY = yPix;
7326                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7327                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7328                 }
7329                 if (appData.highlightDragging) {
7330                     SetHighlights(fromX, fromY, -1, -1);
7331                 } else {
7332                     ClearHighlights();
7333                 }
7334             } else fromX = fromY = -1;
7335             return;
7336         }
7337     }
7338
7339     /* fromX != -1 */
7340     if (clickType == Press && gameMode != EditPosition && killX < 0) {
7341         ChessSquare fromP;
7342         ChessSquare toP;
7343         int frc;
7344
7345         // ignore off-board to clicks
7346         if(y < 0 || x < 0) return;
7347
7348         /* Check if clicking again on the same color piece */
7349         fromP = boards[currentMove][fromY][fromX];
7350         toP = boards[currentMove][y][x];
7351         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7352         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7353              WhitePawn <= toP && toP <= WhiteKing &&
7354              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7355              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7356             (BlackPawn <= fromP && fromP <= BlackKing &&
7357              BlackPawn <= toP && toP <= BlackKing &&
7358              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7359              !(fromP == BlackKing && toP == BlackRook && frc))) {
7360             /* Clicked again on same color piece -- changed his mind */
7361             second = (x == fromX && y == fromY);
7362             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7363                 second = FALSE; // first double-click rather than scond click
7364                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7365             }
7366             promoDefaultAltered = FALSE;
7367             MarkTargetSquares(1);
7368            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7369             if (appData.highlightDragging) {
7370                 SetHighlights(x, y, -1, -1);
7371             } else {
7372                 ClearHighlights();
7373             }
7374             if (OKToStartUserMove(x, y)) {
7375                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7376                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7377                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7378                  gatingPiece = boards[currentMove][fromY][fromX];
7379                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7380                 fromX = x;
7381                 fromY = y; dragging = 1;
7382                 ReportClick("lift", x, y);
7383                 MarkTargetSquares(0);
7384                 DragPieceBegin(xPix, yPix, FALSE);
7385                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7386                     promoSweep = defaultPromoChoice;
7387                     selectFlag = 0; lastX = xPix; lastY = yPix;
7388                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7389                 }
7390             }
7391            }
7392            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7393            second = FALSE;
7394         }
7395         // ignore clicks on holdings
7396         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7397     }
7398
7399     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7400         DragPieceEnd(xPix, yPix); dragging = 0;
7401         if(clearFlag) {
7402             // a deferred attempt to click-click move an empty square on top of a piece
7403             boards[currentMove][y][x] = EmptySquare;
7404             ClearHighlights();
7405             DrawPosition(FALSE, boards[currentMove]);
7406             fromX = fromY = -1; clearFlag = 0;
7407             return;
7408         }
7409         if (appData.animateDragging) {
7410             /* Undo animation damage if any */
7411             DrawPosition(FALSE, NULL);
7412         }
7413         if (second || sweepSelecting) {
7414             /* Second up/down in same square; just abort move */
7415             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7416             second = sweepSelecting = 0;
7417             fromX = fromY = -1;
7418             gatingPiece = EmptySquare;
7419             MarkTargetSquares(1);
7420             ClearHighlights();
7421             gotPremove = 0;
7422             ClearPremoveHighlights();
7423         } else {
7424             /* First upclick in same square; start click-click mode */
7425             SetHighlights(x, y, -1, -1);
7426         }
7427         return;
7428     }
7429
7430     clearFlag = 0;
7431
7432     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7433         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7434         DisplayMessage(_("only marked squares are legal"),"");
7435         DrawPosition(TRUE, NULL);
7436         return; // ignore to-click
7437     }
7438
7439     /* we now have a different from- and (possibly off-board) to-square */
7440     /* Completed move */
7441     if(!sweepSelecting) {
7442         toX = x;
7443         toY = y;
7444     }
7445
7446     saveAnimate = appData.animate;
7447     if (clickType == Press) {
7448         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7449             // must be Edit Position mode with empty-square selected
7450             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7451             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7452             return;
7453         }
7454         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7455             dragging = 1;
7456             return;
7457         }
7458         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7459             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7460         } else
7461         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7462         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7463           if(appData.sweepSelect) {
7464             ChessSquare piece = boards[currentMove][fromY][fromX];
7465             promoSweep = defaultPromoChoice;
7466             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7467             selectFlag = 0; lastX = xPix; lastY = yPix;
7468             Sweep(0); // Pawn that is going to promote: preview promotion piece
7469             sweepSelecting = 1;
7470             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7471             MarkTargetSquares(1);
7472           }
7473           return; // promo popup appears on up-click
7474         }
7475         /* Finish clickclick move */
7476         if (appData.animate || appData.highlightLastMove) {
7477             SetHighlights(fromX, fromY, toX, toY);
7478         } else {
7479             ClearHighlights();
7480         }
7481     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7482         sweepSelecting = 0;
7483         if (appData.animate || appData.highlightLastMove) {
7484             SetHighlights(fromX, fromY, toX, toY);
7485         } else {
7486             ClearHighlights();
7487         }
7488     } else {
7489 #if 0
7490 // [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
7491         /* Finish drag move */
7492         if (appData.highlightLastMove) {
7493             SetHighlights(fromX, fromY, toX, toY);
7494         } else {
7495             ClearHighlights();
7496         }
7497 #endif
7498         if(!dragging || marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7499           dragging *= 2;            // flag button-less dragging if we are dragging
7500           MarkTargetSquares(1);
7501           if(x == killX && y == killY) killX = killY = -1; else {
7502             killX = x; killY = y;     //remeber this square as intermediate
7503             MarkTargetSquares(0);
7504             ReportClick("put", x, y); // and inform engine
7505             ReportClick("lift", x, y);
7506             return;
7507           }
7508         }
7509         DragPieceEnd(xPix, yPix); dragging = 0;
7510         /* Don't animate move and drag both */
7511         appData.animate = FALSE;
7512     }
7513
7514     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7515     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7516         ChessSquare piece = boards[currentMove][fromY][fromX];
7517         if(gameMode == EditPosition && piece != EmptySquare &&
7518            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7519             int n;
7520
7521             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7522                 n = PieceToNumber(piece - (int)BlackPawn);
7523                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7524                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7525                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7526             } else
7527             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7528                 n = PieceToNumber(piece);
7529                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7530                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7531                 boards[currentMove][n][BOARD_WIDTH-2]++;
7532             }
7533             boards[currentMove][fromY][fromX] = EmptySquare;
7534         }
7535         ClearHighlights();
7536         fromX = fromY = -1;
7537         MarkTargetSquares(1);
7538         DrawPosition(TRUE, boards[currentMove]);
7539         return;
7540     }
7541
7542     // off-board moves should not be highlighted
7543     if(x < 0 || y < 0) ClearHighlights();
7544     else ReportClick("put", x, y);
7545
7546     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7547
7548     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7549         SetHighlights(fromX, fromY, toX, toY);
7550         MarkTargetSquares(1);
7551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7552             // [HGM] super: promotion to captured piece selected from holdings
7553             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7554             promotionChoice = TRUE;
7555             // kludge follows to temporarily execute move on display, without promoting yet
7556             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7557             boards[currentMove][toY][toX] = p;
7558             DrawPosition(FALSE, boards[currentMove]);
7559             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7560             boards[currentMove][toY][toX] = q;
7561             DisplayMessage("Click in holdings to choose piece", "");
7562             return;
7563         }
7564         PromotionPopUp();
7565     } else {
7566         int oldMove = currentMove;
7567         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7568         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7569         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7570         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7571            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7572             DrawPosition(TRUE, boards[currentMove]);
7573         MarkTargetSquares(1);
7574         fromX = fromY = -1;
7575     }
7576     appData.animate = saveAnimate;
7577     if (appData.animate || appData.animateDragging) {
7578         /* Undo animation damage if needed */
7579         DrawPosition(FALSE, NULL);
7580     }
7581 }
7582
7583 int
7584 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7585 {   // front-end-free part taken out of PieceMenuPopup
7586     int whichMenu; int xSqr, ySqr;
7587
7588     if(seekGraphUp) { // [HGM] seekgraph
7589         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7590         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7591         return -2;
7592     }
7593
7594     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7595          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7596         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7597         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7598         if(action == Press)   {
7599             originalFlip = flipView;
7600             flipView = !flipView; // temporarily flip board to see game from partners perspective
7601             DrawPosition(TRUE, partnerBoard);
7602             DisplayMessage(partnerStatus, "");
7603             partnerUp = TRUE;
7604         } else if(action == Release) {
7605             flipView = originalFlip;
7606             DrawPosition(TRUE, boards[currentMove]);
7607             partnerUp = FALSE;
7608         }
7609         return -2;
7610     }
7611
7612     xSqr = EventToSquare(x, BOARD_WIDTH);
7613     ySqr = EventToSquare(y, BOARD_HEIGHT);
7614     if (action == Release) {
7615         if(pieceSweep != EmptySquare) {
7616             EditPositionMenuEvent(pieceSweep, toX, toY);
7617             pieceSweep = EmptySquare;
7618         } else UnLoadPV(); // [HGM] pv
7619     }
7620     if (action != Press) return -2; // return code to be ignored
7621     switch (gameMode) {
7622       case IcsExamining:
7623         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7624       case EditPosition:
7625         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7626         if (xSqr < 0 || ySqr < 0) return -1;
7627         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7628         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7629         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7630         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7631         NextPiece(0);
7632         return 2; // grab
7633       case IcsObserving:
7634         if(!appData.icsEngineAnalyze) return -1;
7635       case IcsPlayingWhite:
7636       case IcsPlayingBlack:
7637         if(!appData.zippyPlay) goto noZip;
7638       case AnalyzeMode:
7639       case AnalyzeFile:
7640       case MachinePlaysWhite:
7641       case MachinePlaysBlack:
7642       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7643         if (!appData.dropMenu) {
7644           LoadPV(x, y);
7645           return 2; // flag front-end to grab mouse events
7646         }
7647         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7648            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7649       case EditGame:
7650       noZip:
7651         if (xSqr < 0 || ySqr < 0) return -1;
7652         if (!appData.dropMenu || appData.testLegality &&
7653             gameInfo.variant != VariantBughouse &&
7654             gameInfo.variant != VariantCrazyhouse) return -1;
7655         whichMenu = 1; // drop menu
7656         break;
7657       default:
7658         return -1;
7659     }
7660
7661     if (((*fromX = xSqr) < 0) ||
7662         ((*fromY = ySqr) < 0)) {
7663         *fromX = *fromY = -1;
7664         return -1;
7665     }
7666     if (flipView)
7667       *fromX = BOARD_WIDTH - 1 - *fromX;
7668     else
7669       *fromY = BOARD_HEIGHT - 1 - *fromY;
7670
7671     return whichMenu;
7672 }
7673
7674 void
7675 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7676 {
7677 //    char * hint = lastHint;
7678     FrontEndProgramStats stats;
7679
7680     stats.which = cps == &first ? 0 : 1;
7681     stats.depth = cpstats->depth;
7682     stats.nodes = cpstats->nodes;
7683     stats.score = cpstats->score;
7684     stats.time = cpstats->time;
7685     stats.pv = cpstats->movelist;
7686     stats.hint = lastHint;
7687     stats.an_move_index = 0;
7688     stats.an_move_count = 0;
7689
7690     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7691         stats.hint = cpstats->move_name;
7692         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7693         stats.an_move_count = cpstats->nr_moves;
7694     }
7695
7696     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
7697
7698     SetProgramStats( &stats );
7699 }
7700
7701 void
7702 ClearEngineOutputPane (int which)
7703 {
7704     static FrontEndProgramStats dummyStats;
7705     dummyStats.which = which;
7706     dummyStats.pv = "#";
7707     SetProgramStats( &dummyStats );
7708 }
7709
7710 #define MAXPLAYERS 500
7711
7712 char *
7713 TourneyStandings (int display)
7714 {
7715     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7716     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7717     char result, *p, *names[MAXPLAYERS];
7718
7719     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7720         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7721     names[0] = p = strdup(appData.participants);
7722     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7723
7724     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7725
7726     while(result = appData.results[nr]) {
7727         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7728         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7729         wScore = bScore = 0;
7730         switch(result) {
7731           case '+': wScore = 2; break;
7732           case '-': bScore = 2; break;
7733           case '=': wScore = bScore = 1; break;
7734           case ' ':
7735           case '*': return strdup("busy"); // tourney not finished
7736         }
7737         score[w] += wScore;
7738         score[b] += bScore;
7739         games[w]++;
7740         games[b]++;
7741         nr++;
7742     }
7743     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7744     for(w=0; w<nPlayers; w++) {
7745         bScore = -1;
7746         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7747         ranking[w] = b; points[w] = bScore; score[b] = -2;
7748     }
7749     p = malloc(nPlayers*34+1);
7750     for(w=0; w<nPlayers && w<display; w++)
7751         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7752     free(names[0]);
7753     return p;
7754 }
7755
7756 void
7757 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7758 {       // count all piece types
7759         int p, f, r;
7760         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7761         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7762         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7763                 p = board[r][f];
7764                 pCnt[p]++;
7765                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7766                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7767                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7768                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7769                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7770                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7771         }
7772 }
7773
7774 int
7775 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7776 {
7777         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7778         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7779
7780         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7781         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7782         if(myPawns == 2 && nMine == 3) // KPP
7783             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7784         if(myPawns == 1 && nMine == 2) // KP
7785             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7786         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7787             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7788         if(myPawns) return FALSE;
7789         if(pCnt[WhiteRook+side])
7790             return pCnt[BlackRook-side] ||
7791                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7792                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7793                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7794         if(pCnt[WhiteCannon+side]) {
7795             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7796             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7797         }
7798         if(pCnt[WhiteKnight+side])
7799             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7800         return FALSE;
7801 }
7802
7803 int
7804 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7805 {
7806         VariantClass v = gameInfo.variant;
7807
7808         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7809         if(v == VariantShatranj) return TRUE; // always winnable through baring
7810         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7811         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7812
7813         if(v == VariantXiangqi) {
7814                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7815
7816                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7817                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7818                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7819                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7820                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7821                 if(stale) // we have at least one last-rank P plus perhaps C
7822                     return majors // KPKX
7823                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7824                 else // KCA*E*
7825                     return pCnt[WhiteFerz+side] // KCAK
7826                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7827                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7828                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7829
7830         } else if(v == VariantKnightmate) {
7831                 if(nMine == 1) return FALSE;
7832                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7833         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7834                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7835
7836                 if(nMine == 1) return FALSE; // bare King
7837                 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
7838                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7839                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7840                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7841                 if(pCnt[WhiteKnight+side])
7842                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7843                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7844                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7845                 if(nBishops)
7846                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7847                 if(pCnt[WhiteAlfil+side])
7848                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7849                 if(pCnt[WhiteWazir+side])
7850                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7851         }
7852
7853         return TRUE;
7854 }
7855
7856 int
7857 CompareWithRights (Board b1, Board b2)
7858 {
7859     int rights = 0;
7860     if(!CompareBoards(b1, b2)) return FALSE;
7861     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7862     /* compare castling rights */
7863     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7864            rights++; /* King lost rights, while rook still had them */
7865     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7866         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7867            rights++; /* but at least one rook lost them */
7868     }
7869     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7870            rights++;
7871     if( b1[CASTLING][5] != NoRights ) {
7872         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7873            rights++;
7874     }
7875     return rights == 0;
7876 }
7877
7878 int
7879 Adjudicate (ChessProgramState *cps)
7880 {       // [HGM] some adjudications useful with buggy engines
7881         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7882         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7883         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7884         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7885         int k, drop, count = 0; static int bare = 1;
7886         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7887         Boolean canAdjudicate = !appData.icsActive;
7888
7889         // most tests only when we understand the game, i.e. legality-checking on
7890             if( appData.testLegality )
7891             {   /* [HGM] Some more adjudications for obstinate engines */
7892                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7893                 static int moveCount = 6;
7894                 ChessMove result;
7895                 char *reason = NULL;
7896
7897                 /* Count what is on board. */
7898                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7899
7900                 /* Some material-based adjudications that have to be made before stalemate test */
7901                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7902                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7903                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7904                      if(canAdjudicate && appData.checkMates) {
7905                          if(engineOpponent)
7906                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7907                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7908                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7909                          return 1;
7910                      }
7911                 }
7912
7913                 /* Bare King in Shatranj (loses) or Losers (wins) */
7914                 if( nrW == 1 || nrB == 1) {
7915                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7916                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7917                      if(canAdjudicate && appData.checkMates) {
7918                          if(engineOpponent)
7919                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7920                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7921                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7922                          return 1;
7923                      }
7924                   } else
7925                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7926                   {    /* bare King */
7927                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7928                         if(canAdjudicate && appData.checkMates) {
7929                             /* but only adjudicate if adjudication enabled */
7930                             if(engineOpponent)
7931                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7932                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7933                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7934                             return 1;
7935                         }
7936                   }
7937                 } else bare = 1;
7938
7939
7940             // don't wait for engine to announce game end if we can judge ourselves
7941             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7942               case MT_CHECK:
7943                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7944                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7945                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7946                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7947                             checkCnt++;
7948                         if(checkCnt >= 2) {
7949                             reason = "Xboard adjudication: 3rd check";
7950                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7951                             break;
7952                         }
7953                     }
7954                 }
7955               case MT_NONE:
7956               default:
7957                 break;
7958               case MT_STALEMATE:
7959               case MT_STAINMATE:
7960                 reason = "Xboard adjudication: Stalemate";
7961                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7962                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7963                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7964                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7965                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7966                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7967                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7968                                                                         EP_CHECKMATE : EP_WINS);
7969                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7970                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7971                 }
7972                 break;
7973               case MT_CHECKMATE:
7974                 reason = "Xboard adjudication: Checkmate";
7975                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7976                 if(gameInfo.variant == VariantShogi) {
7977                     if(forwardMostMove > backwardMostMove
7978                        && moveList[forwardMostMove-1][1] == '@'
7979                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7980                         reason = "XBoard adjudication: pawn-drop mate";
7981                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7982                     }
7983                 }
7984                 break;
7985             }
7986
7987                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7988                     case EP_STALEMATE:
7989                         result = GameIsDrawn; break;
7990                     case EP_CHECKMATE:
7991                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7992                     case EP_WINS:
7993                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7994                     default:
7995                         result = EndOfFile;
7996                 }
7997                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7998                     if(engineOpponent)
7999                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8000                     GameEnds( result, reason, GE_XBOARD );
8001                     return 1;
8002                 }
8003
8004                 /* Next absolutely insufficient mating material. */
8005                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8006                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8007                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8008
8009                      /* always flag draws, for judging claims */
8010                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8011
8012                      if(canAdjudicate && appData.materialDraws) {
8013                          /* but only adjudicate them if adjudication enabled */
8014                          if(engineOpponent) {
8015                            SendToProgram("force\n", engineOpponent); // suppress reply
8016                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8017                          }
8018                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8019                          return 1;
8020                      }
8021                 }
8022
8023                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8024                 if(gameInfo.variant == VariantXiangqi ?
8025                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8026                  : nrW + nrB == 4 &&
8027                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8028                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8029                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8030                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8031                    ) ) {
8032                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8033                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8034                           if(engineOpponent) {
8035                             SendToProgram("force\n", engineOpponent); // suppress reply
8036                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8037                           }
8038                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8039                           return 1;
8040                      }
8041                 } else moveCount = 6;
8042             }
8043
8044         // Repetition draws and 50-move rule can be applied independently of legality testing
8045
8046                 /* Check for rep-draws */
8047                 count = 0;
8048                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8049                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8050                 for(k = forwardMostMove-2;
8051                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8052                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8053                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8054                     k-=2)
8055                 {   int rights=0;
8056                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8057                         /* compare castling rights */
8058                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8059                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8060                                 rights++; /* King lost rights, while rook still had them */
8061                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8062                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8063                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8064                                    rights++; /* but at least one rook lost them */
8065                         }
8066                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8067                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8068                                 rights++;
8069                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8070                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8071                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8072                                    rights++;
8073                         }
8074                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8075                             && appData.drawRepeats > 1) {
8076                              /* adjudicate after user-specified nr of repeats */
8077                              int result = GameIsDrawn;
8078                              char *details = "XBoard adjudication: repetition draw";
8079                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8080                                 // [HGM] xiangqi: check for forbidden perpetuals
8081                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8082                                 for(m=forwardMostMove; m>k; m-=2) {
8083                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8084                                         ourPerpetual = 0; // the current mover did not always check
8085                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8086                                         hisPerpetual = 0; // the opponent did not always check
8087                                 }
8088                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8089                                                                         ourPerpetual, hisPerpetual);
8090                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8091                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8092                                     details = "Xboard adjudication: perpetual checking";
8093                                 } else
8094                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8095                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8096                                 } else
8097                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8098                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8099                                         result = BlackWins;
8100                                         details = "Xboard adjudication: repetition";
8101                                     }
8102                                 } else // it must be XQ
8103                                 // Now check for perpetual chases
8104                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8105                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8106                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8107                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8108                                         static char resdet[MSG_SIZ];
8109                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8110                                         details = resdet;
8111                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8112                                     } else
8113                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8114                                         break; // Abort repetition-checking loop.
8115                                 }
8116                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8117                              }
8118                              if(engineOpponent) {
8119                                SendToProgram("force\n", engineOpponent); // suppress reply
8120                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8121                              }
8122                              GameEnds( result, details, GE_XBOARD );
8123                              return 1;
8124                         }
8125                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8126                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8127                     }
8128                 }
8129
8130                 /* Now we test for 50-move draws. Determine ply count */
8131                 count = forwardMostMove;
8132                 /* look for last irreversble move */
8133                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8134                     count--;
8135                 /* if we hit starting position, add initial plies */
8136                 if( count == backwardMostMove )
8137                     count -= initialRulePlies;
8138                 count = forwardMostMove - count;
8139                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8140                         // adjust reversible move counter for checks in Xiangqi
8141                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8142                         if(i < backwardMostMove) i = backwardMostMove;
8143                         while(i <= forwardMostMove) {
8144                                 lastCheck = inCheck; // check evasion does not count
8145                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8146                                 if(inCheck || lastCheck) count--; // check does not count
8147                                 i++;
8148                         }
8149                 }
8150                 if( count >= 100)
8151                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8152                          /* this is used to judge if draw claims are legal */
8153                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8154                          if(engineOpponent) {
8155                            SendToProgram("force\n", engineOpponent); // suppress reply
8156                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8157                          }
8158                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8159                          return 1;
8160                 }
8161
8162                 /* if draw offer is pending, treat it as a draw claim
8163                  * when draw condition present, to allow engines a way to
8164                  * claim draws before making their move to avoid a race
8165                  * condition occurring after their move
8166                  */
8167                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8168                          char *p = NULL;
8169                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8170                              p = "Draw claim: 50-move rule";
8171                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8172                              p = "Draw claim: 3-fold repetition";
8173                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8174                              p = "Draw claim: insufficient mating material";
8175                          if( p != NULL && canAdjudicate) {
8176                              if(engineOpponent) {
8177                                SendToProgram("force\n", engineOpponent); // suppress reply
8178                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8179                              }
8180                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8181                              return 1;
8182                          }
8183                 }
8184
8185                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8186                     if(engineOpponent) {
8187                       SendToProgram("force\n", engineOpponent); // suppress reply
8188                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8189                     }
8190                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8191                     return 1;
8192                 }
8193         return 0;
8194 }
8195
8196 char *
8197 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8198 {   // [HGM] book: this routine intercepts moves to simulate book replies
8199     char *bookHit = NULL;
8200
8201     //first determine if the incoming move brings opponent into his book
8202     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8203         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8204     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8205     if(bookHit != NULL && !cps->bookSuspend) {
8206         // make sure opponent is not going to reply after receiving move to book position
8207         SendToProgram("force\n", cps);
8208         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8209     }
8210     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8211     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8212     // now arrange restart after book miss
8213     if(bookHit) {
8214         // after a book hit we never send 'go', and the code after the call to this routine
8215         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8216         char buf[MSG_SIZ], *move = bookHit;
8217         if(cps->useSAN) {
8218             int fromX, fromY, toX, toY;
8219             char promoChar;
8220             ChessMove moveType;
8221             move = buf + 30;
8222             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8223                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8224                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8225                                     PosFlags(forwardMostMove),
8226                                     fromY, fromX, toY, toX, promoChar, move);
8227             } else {
8228                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8229                 bookHit = NULL;
8230             }
8231         }
8232         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8233         SendToProgram(buf, cps);
8234         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8235     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8236         SendToProgram("go\n", cps);
8237         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8238     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8239         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8240             SendToProgram("go\n", cps);
8241         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8242     }
8243     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8244 }
8245
8246 int
8247 LoadError (char *errmess, ChessProgramState *cps)
8248 {   // unloads engine and switches back to -ncp mode if it was first
8249     if(cps->initDone) return FALSE;
8250     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8251     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8252     cps->pr = NoProc;
8253     if(cps == &first) {
8254         appData.noChessProgram = TRUE;
8255         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8256         gameMode = BeginningOfGame; ModeHighlight();
8257         SetNCPMode();
8258     }
8259     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8260     DisplayMessage("", ""); // erase waiting message
8261     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8262     return TRUE;
8263 }
8264
8265 char *savedMessage;
8266 ChessProgramState *savedState;
8267 void
8268 DeferredBookMove (void)
8269 {
8270         if(savedState->lastPing != savedState->lastPong)
8271                     ScheduleDelayedEvent(DeferredBookMove, 10);
8272         else
8273         HandleMachineMove(savedMessage, savedState);
8274 }
8275
8276 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8277 static ChessProgramState *stalledEngine;
8278 static char stashedInputMove[MSG_SIZ];
8279
8280 void
8281 HandleMachineMove (char *message, ChessProgramState *cps)
8282 {
8283     static char firstLeg[20];
8284     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8285     char realname[MSG_SIZ];
8286     int fromX, fromY, toX, toY;
8287     ChessMove moveType;
8288     char promoChar;
8289     char *p, *pv=buf1;
8290     int machineWhite, oldError;
8291     char *bookHit;
8292
8293     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8294         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8295         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8296             DisplayError(_("Invalid pairing from pairing engine"), 0);
8297             return;
8298         }
8299         pairingReceived = 1;
8300         NextMatchGame();
8301         return; // Skim the pairing messages here.
8302     }
8303
8304     oldError = cps->userError; cps->userError = 0;
8305
8306 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8307     /*
8308      * Kludge to ignore BEL characters
8309      */
8310     while (*message == '\007') message++;
8311
8312     /*
8313      * [HGM] engine debug message: ignore lines starting with '#' character
8314      */
8315     if(cps->debug && *message == '#') return;
8316
8317     /*
8318      * Look for book output
8319      */
8320     if (cps == &first && bookRequested) {
8321         if (message[0] == '\t' || message[0] == ' ') {
8322             /* Part of the book output is here; append it */
8323             strcat(bookOutput, message);
8324             strcat(bookOutput, "  \n");
8325             return;
8326         } else if (bookOutput[0] != NULLCHAR) {
8327             /* All of book output has arrived; display it */
8328             char *p = bookOutput;
8329             while (*p != NULLCHAR) {
8330                 if (*p == '\t') *p = ' ';
8331                 p++;
8332             }
8333             DisplayInformation(bookOutput);
8334             bookRequested = FALSE;
8335             /* Fall through to parse the current output */
8336         }
8337     }
8338
8339     /*
8340      * Look for machine move.
8341      */
8342     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8343         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8344     {
8345         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8346             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8347             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8348             stalledEngine = cps;
8349             if(appData.ponderNextMove) { // bring opponent out of ponder
8350                 if(gameMode == TwoMachinesPlay) {
8351                     if(cps->other->pause)
8352                         PauseEngine(cps->other);
8353                     else
8354                         SendToProgram("easy\n", cps->other);
8355                 }
8356             }
8357             StopClocks();
8358             return;
8359         }
8360
8361         /* This method is only useful on engines that support ping */
8362         if (cps->lastPing != cps->lastPong) {
8363           if (gameMode == BeginningOfGame) {
8364             /* Extra move from before last new; ignore */
8365             if (appData.debugMode) {
8366                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8367             }
8368           } else {
8369             if (appData.debugMode) {
8370                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8371                         cps->which, gameMode);
8372             }
8373
8374             SendToProgram("undo\n", cps);
8375           }
8376           return;
8377         }
8378
8379         switch (gameMode) {
8380           case BeginningOfGame:
8381             /* Extra move from before last reset; ignore */
8382             if (appData.debugMode) {
8383                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8384             }
8385             return;
8386
8387           case EndOfGame:
8388           case IcsIdle:
8389           default:
8390             /* Extra move after we tried to stop.  The mode test is
8391                not a reliable way of detecting this problem, but it's
8392                the best we can do on engines that don't support ping.
8393             */
8394             if (appData.debugMode) {
8395                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8396                         cps->which, gameMode);
8397             }
8398             SendToProgram("undo\n", cps);
8399             return;
8400
8401           case MachinePlaysWhite:
8402           case IcsPlayingWhite:
8403             machineWhite = TRUE;
8404             break;
8405
8406           case MachinePlaysBlack:
8407           case IcsPlayingBlack:
8408             machineWhite = FALSE;
8409             break;
8410
8411           case TwoMachinesPlay:
8412             machineWhite = (cps->twoMachinesColor[0] == 'w');
8413             break;
8414         }
8415         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8416             if (appData.debugMode) {
8417                 fprintf(debugFP,
8418                         "Ignoring move out of turn by %s, gameMode %d"
8419                         ", forwardMost %d\n",
8420                         cps->which, gameMode, forwardMostMove);
8421             }
8422             return;
8423         }
8424
8425         if(cps->alphaRank) AlphaRank(machineMove, 4);
8426
8427         // [HGM] lion: (some very limited) support for Alien protocol
8428         killX = killY = -1;
8429         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8430             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8431             return;
8432         } else if(firstLeg[0]) { // there was a previous leg;
8433             // only support case where same piece makes two step (and don't even test that!)
8434             char buf[20], *p = machineMove+1, *q = buf+1, f;
8435             safeStrCpy(buf, machineMove, 20);
8436             while(isdigit(*q)) q++; // find start of to-square
8437             safeStrCpy(machineMove, firstLeg, 20);
8438             while(isdigit(*p)) p++;
8439             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8440             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8441             firstLeg[0] = NULLCHAR;
8442         }
8443
8444         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8445                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8446             /* Machine move could not be parsed; ignore it. */
8447           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8448                     machineMove, _(cps->which));
8449             DisplayMoveError(buf1);
8450             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8451                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8452             if (gameMode == TwoMachinesPlay) {
8453               GameEnds(machineWhite ? BlackWins : WhiteWins,
8454                        buf1, GE_XBOARD);
8455             }
8456             return;
8457         }
8458
8459         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8460         /* So we have to redo legality test with true e.p. status here,  */
8461         /* to make sure an illegal e.p. capture does not slip through,   */
8462         /* to cause a forfeit on a justified illegal-move complaint      */
8463         /* of the opponent.                                              */
8464         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8465            ChessMove moveType;
8466            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8467                              fromY, fromX, toY, toX, promoChar);
8468             if(moveType == IllegalMove) {
8469               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8470                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8471                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8472                            buf1, GE_XBOARD);
8473                 return;
8474            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8475            /* [HGM] Kludge to handle engines that send FRC-style castling
8476               when they shouldn't (like TSCP-Gothic) */
8477            switch(moveType) {
8478              case WhiteASideCastleFR:
8479              case BlackASideCastleFR:
8480                toX+=2;
8481                currentMoveString[2]++;
8482                break;
8483              case WhiteHSideCastleFR:
8484              case BlackHSideCastleFR:
8485                toX--;
8486                currentMoveString[2]--;
8487                break;
8488              default: ; // nothing to do, but suppresses warning of pedantic compilers
8489            }
8490         }
8491         hintRequested = FALSE;
8492         lastHint[0] = NULLCHAR;
8493         bookRequested = FALSE;
8494         /* Program may be pondering now */
8495         cps->maybeThinking = TRUE;
8496         if (cps->sendTime == 2) cps->sendTime = 1;
8497         if (cps->offeredDraw) cps->offeredDraw--;
8498
8499         /* [AS] Save move info*/
8500         pvInfoList[ forwardMostMove ].score = programStats.score;
8501         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8502         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8503
8504         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8505
8506         /* Test suites abort the 'game' after one move */
8507         if(*appData.finger) {
8508            static FILE *f;
8509            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8510            if(!f) f = fopen(appData.finger, "w");
8511            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8512            else { DisplayFatalError("Bad output file", errno, 0); return; }
8513            free(fen);
8514            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8515         }
8516
8517         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8518         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8519             int count = 0;
8520
8521             while( count < adjudicateLossPlies ) {
8522                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8523
8524                 if( count & 1 ) {
8525                     score = -score; /* Flip score for winning side */
8526                 }
8527
8528                 if( score > adjudicateLossThreshold ) {
8529                     break;
8530                 }
8531
8532                 count++;
8533             }
8534
8535             if( count >= adjudicateLossPlies ) {
8536                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8537
8538                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8539                     "Xboard adjudication",
8540                     GE_XBOARD );
8541
8542                 return;
8543             }
8544         }
8545
8546         if(Adjudicate(cps)) {
8547             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8548             return; // [HGM] adjudicate: for all automatic game ends
8549         }
8550
8551 #if ZIPPY
8552         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8553             first.initDone) {
8554           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8555                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8556                 SendToICS("draw ");
8557                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8558           }
8559           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8560           ics_user_moved = 1;
8561           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8562                 char buf[3*MSG_SIZ];
8563
8564                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8565                         programStats.score / 100.,
8566                         programStats.depth,
8567                         programStats.time / 100.,
8568                         (unsigned int)programStats.nodes,
8569                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8570                         programStats.movelist);
8571                 SendToICS(buf);
8572 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8573           }
8574         }
8575 #endif
8576
8577         /* [AS] Clear stats for next move */
8578         ClearProgramStats();
8579         thinkOutput[0] = NULLCHAR;
8580         hiddenThinkOutputState = 0;
8581
8582         bookHit = NULL;
8583         if (gameMode == TwoMachinesPlay) {
8584             /* [HGM] relaying draw offers moved to after reception of move */
8585             /* and interpreting offer as claim if it brings draw condition */
8586             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8587                 SendToProgram("draw\n", cps->other);
8588             }
8589             if (cps->other->sendTime) {
8590                 SendTimeRemaining(cps->other,
8591                                   cps->other->twoMachinesColor[0] == 'w');
8592             }
8593             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8594             if (firstMove && !bookHit) {
8595                 firstMove = FALSE;
8596                 if (cps->other->useColors) {
8597                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8598                 }
8599                 SendToProgram("go\n", cps->other);
8600             }
8601             cps->other->maybeThinking = TRUE;
8602         }
8603
8604         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8605
8606         if (!pausing && appData.ringBellAfterMoves) {
8607             RingBell();
8608         }
8609
8610         /*
8611          * Reenable menu items that were disabled while
8612          * machine was thinking
8613          */
8614         if (gameMode != TwoMachinesPlay)
8615             SetUserThinkingEnables();
8616
8617         // [HGM] book: after book hit opponent has received move and is now in force mode
8618         // force the book reply into it, and then fake that it outputted this move by jumping
8619         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8620         if(bookHit) {
8621                 static char bookMove[MSG_SIZ]; // a bit generous?
8622
8623                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8624                 strcat(bookMove, bookHit);
8625                 message = bookMove;
8626                 cps = cps->other;
8627                 programStats.nodes = programStats.depth = programStats.time =
8628                 programStats.score = programStats.got_only_move = 0;
8629                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8630
8631                 if(cps->lastPing != cps->lastPong) {
8632                     savedMessage = message; // args for deferred call
8633                     savedState = cps;
8634                     ScheduleDelayedEvent(DeferredBookMove, 10);
8635                     return;
8636                 }
8637                 goto FakeBookMove;
8638         }
8639
8640         return;
8641     }
8642
8643     /* Set special modes for chess engines.  Later something general
8644      *  could be added here; for now there is just one kludge feature,
8645      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8646      *  when "xboard" is given as an interactive command.
8647      */
8648     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8649         cps->useSigint = FALSE;
8650         cps->useSigterm = FALSE;
8651     }
8652     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8653       ParseFeatures(message+8, cps);
8654       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8655     }
8656
8657     if (!strncmp(message, "setup ", 6) && 
8658         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8659                                         ) { // [HGM] allow first engine to define opening position
8660       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8661       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8662       *buf = NULLCHAR;
8663       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8664       if(startedFromSetupPosition) return;
8665       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8666       if(dummy >= 3) {
8667         while(message[s] && message[s++] != ' ');
8668         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8669            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8670             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8671             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8672           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8673           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8674         }
8675       }
8676       ParseFEN(boards[0], &dummy, message+s, FALSE);
8677       DrawPosition(TRUE, boards[0]);
8678       startedFromSetupPosition = TRUE;
8679       return;
8680     }
8681     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8682      * want this, I was asked to put it in, and obliged.
8683      */
8684     if (!strncmp(message, "setboard ", 9)) {
8685         Board initial_position;
8686
8687         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8688
8689         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8690             DisplayError(_("Bad FEN received from engine"), 0);
8691             return ;
8692         } else {
8693            Reset(TRUE, FALSE);
8694            CopyBoard(boards[0], initial_position);
8695            initialRulePlies = FENrulePlies;
8696            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8697            else gameMode = MachinePlaysBlack;
8698            DrawPosition(FALSE, boards[currentMove]);
8699         }
8700         return;
8701     }
8702
8703     /*
8704      * Look for communication commands
8705      */
8706     if (!strncmp(message, "telluser ", 9)) {
8707         if(message[9] == '\\' && message[10] == '\\')
8708             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8709         PlayTellSound();
8710         DisplayNote(message + 9);
8711         return;
8712     }
8713     if (!strncmp(message, "tellusererror ", 14)) {
8714         cps->userError = 1;
8715         if(message[14] == '\\' && message[15] == '\\')
8716             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8717         PlayTellSound();
8718         DisplayError(message + 14, 0);
8719         return;
8720     }
8721     if (!strncmp(message, "tellopponent ", 13)) {
8722       if (appData.icsActive) {
8723         if (loggedOn) {
8724           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8725           SendToICS(buf1);
8726         }
8727       } else {
8728         DisplayNote(message + 13);
8729       }
8730       return;
8731     }
8732     if (!strncmp(message, "tellothers ", 11)) {
8733       if (appData.icsActive) {
8734         if (loggedOn) {
8735           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8736           SendToICS(buf1);
8737         }
8738       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8739       return;
8740     }
8741     if (!strncmp(message, "tellall ", 8)) {
8742       if (appData.icsActive) {
8743         if (loggedOn) {
8744           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8745           SendToICS(buf1);
8746         }
8747       } else {
8748         DisplayNote(message + 8);
8749       }
8750       return;
8751     }
8752     if (strncmp(message, "warning", 7) == 0) {
8753         /* Undocumented feature, use tellusererror in new code */
8754         DisplayError(message, 0);
8755         return;
8756     }
8757     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8758         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8759         strcat(realname, " query");
8760         AskQuestion(realname, buf2, buf1, cps->pr);
8761         return;
8762     }
8763     /* Commands from the engine directly to ICS.  We don't allow these to be
8764      *  sent until we are logged on. Crafty kibitzes have been known to
8765      *  interfere with the login process.
8766      */
8767     if (loggedOn) {
8768         if (!strncmp(message, "tellics ", 8)) {
8769             SendToICS(message + 8);
8770             SendToICS("\n");
8771             return;
8772         }
8773         if (!strncmp(message, "tellicsnoalias ", 15)) {
8774             SendToICS(ics_prefix);
8775             SendToICS(message + 15);
8776             SendToICS("\n");
8777             return;
8778         }
8779         /* The following are for backward compatibility only */
8780         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8781             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8782             SendToICS(ics_prefix);
8783             SendToICS(message);
8784             SendToICS("\n");
8785             return;
8786         }
8787     }
8788     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8789         return;
8790     }
8791     if(!strncmp(message, "highlight ", 10)) {
8792         if(appData.testLegality && appData.markers) return;
8793         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8794         return;
8795     }
8796     if(!strncmp(message, "click ", 6)) {
8797         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8798         if(appData.testLegality || !appData.oneClick) return;
8799         sscanf(message+6, "%c%d%c", &f, &y, &c);
8800         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8801         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8802         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8803         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8804         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8805         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8806             LeftClick(Release, lastLeftX, lastLeftY);
8807         controlKey  = (c == ',');
8808         LeftClick(Press, x, y);
8809         LeftClick(Release, x, y);
8810         first.highlight = f;
8811         return;
8812     }
8813     /*
8814      * If the move is illegal, cancel it and redraw the board.
8815      * Also deal with other error cases.  Matching is rather loose
8816      * here to accommodate engines written before the spec.
8817      */
8818     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8819         strncmp(message, "Error", 5) == 0) {
8820         if (StrStr(message, "name") ||
8821             StrStr(message, "rating") || StrStr(message, "?") ||
8822             StrStr(message, "result") || StrStr(message, "board") ||
8823             StrStr(message, "bk") || StrStr(message, "computer") ||
8824             StrStr(message, "variant") || StrStr(message, "hint") ||
8825             StrStr(message, "random") || StrStr(message, "depth") ||
8826             StrStr(message, "accepted")) {
8827             return;
8828         }
8829         if (StrStr(message, "protover")) {
8830           /* Program is responding to input, so it's apparently done
8831              initializing, and this error message indicates it is
8832              protocol version 1.  So we don't need to wait any longer
8833              for it to initialize and send feature commands. */
8834           FeatureDone(cps, 1);
8835           cps->protocolVersion = 1;
8836           return;
8837         }
8838         cps->maybeThinking = FALSE;
8839
8840         if (StrStr(message, "draw")) {
8841             /* Program doesn't have "draw" command */
8842             cps->sendDrawOffers = 0;
8843             return;
8844         }
8845         if (cps->sendTime != 1 &&
8846             (StrStr(message, "time") || StrStr(message, "otim"))) {
8847           /* Program apparently doesn't have "time" or "otim" command */
8848           cps->sendTime = 0;
8849           return;
8850         }
8851         if (StrStr(message, "analyze")) {
8852             cps->analysisSupport = FALSE;
8853             cps->analyzing = FALSE;
8854 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8855             EditGameEvent(); // [HGM] try to preserve loaded game
8856             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8857             DisplayError(buf2, 0);
8858             return;
8859         }
8860         if (StrStr(message, "(no matching move)st")) {
8861           /* Special kludge for GNU Chess 4 only */
8862           cps->stKludge = TRUE;
8863           SendTimeControl(cps, movesPerSession, timeControl,
8864                           timeIncrement, appData.searchDepth,
8865                           searchTime);
8866           return;
8867         }
8868         if (StrStr(message, "(no matching move)sd")) {
8869           /* Special kludge for GNU Chess 4 only */
8870           cps->sdKludge = TRUE;
8871           SendTimeControl(cps, movesPerSession, timeControl,
8872                           timeIncrement, appData.searchDepth,
8873                           searchTime);
8874           return;
8875         }
8876         if (!StrStr(message, "llegal")) {
8877             return;
8878         }
8879         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8880             gameMode == IcsIdle) return;
8881         if (forwardMostMove <= backwardMostMove) return;
8882         if (pausing) PauseEvent();
8883       if(appData.forceIllegal) {
8884             // [HGM] illegal: machine refused move; force position after move into it
8885           SendToProgram("force\n", cps);
8886           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8887                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8888                 // when black is to move, while there might be nothing on a2 or black
8889                 // might already have the move. So send the board as if white has the move.
8890                 // But first we must change the stm of the engine, as it refused the last move
8891                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8892                 if(WhiteOnMove(forwardMostMove)) {
8893                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8894                     SendBoard(cps, forwardMostMove); // kludgeless board
8895                 } else {
8896                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8897                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8898                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8899                 }
8900           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8901             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8902                  gameMode == TwoMachinesPlay)
8903               SendToProgram("go\n", cps);
8904             return;
8905       } else
8906         if (gameMode == PlayFromGameFile) {
8907             /* Stop reading this game file */
8908             gameMode = EditGame;
8909             ModeHighlight();
8910         }
8911         /* [HGM] illegal-move claim should forfeit game when Xboard */
8912         /* only passes fully legal moves                            */
8913         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8914             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8915                                 "False illegal-move claim", GE_XBOARD );
8916             return; // do not take back move we tested as valid
8917         }
8918         currentMove = forwardMostMove-1;
8919         DisplayMove(currentMove-1); /* before DisplayMoveError */
8920         SwitchClocks(forwardMostMove-1); // [HGM] race
8921         DisplayBothClocks();
8922         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8923                 parseList[currentMove], _(cps->which));
8924         DisplayMoveError(buf1);
8925         DrawPosition(FALSE, boards[currentMove]);
8926
8927         SetUserThinkingEnables();
8928         return;
8929     }
8930     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8931         /* Program has a broken "time" command that
8932            outputs a string not ending in newline.
8933            Don't use it. */
8934         cps->sendTime = 0;
8935     }
8936
8937     /*
8938      * If chess program startup fails, exit with an error message.
8939      * Attempts to recover here are futile. [HGM] Well, we try anyway
8940      */
8941     if ((StrStr(message, "unknown host") != NULL)
8942         || (StrStr(message, "No remote directory") != NULL)
8943         || (StrStr(message, "not found") != NULL)
8944         || (StrStr(message, "No such file") != NULL)
8945         || (StrStr(message, "can't alloc") != NULL)
8946         || (StrStr(message, "Permission denied") != NULL)) {
8947
8948         cps->maybeThinking = FALSE;
8949         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8950                 _(cps->which), cps->program, cps->host, message);
8951         RemoveInputSource(cps->isr);
8952         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8953             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8954             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8955         }
8956         return;
8957     }
8958
8959     /*
8960      * Look for hint output
8961      */
8962     if (sscanf(message, "Hint: %s", buf1) == 1) {
8963         if (cps == &first && hintRequested) {
8964             hintRequested = FALSE;
8965             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8966                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8967                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8968                                     PosFlags(forwardMostMove),
8969                                     fromY, fromX, toY, toX, promoChar, buf1);
8970                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8971                 DisplayInformation(buf2);
8972             } else {
8973                 /* Hint move could not be parsed!? */
8974               snprintf(buf2, sizeof(buf2),
8975                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8976                         buf1, _(cps->which));
8977                 DisplayError(buf2, 0);
8978             }
8979         } else {
8980           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8981         }
8982         return;
8983     }
8984
8985     /*
8986      * Ignore other messages if game is not in progress
8987      */
8988     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8989         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8990
8991     /*
8992      * look for win, lose, draw, or draw offer
8993      */
8994     if (strncmp(message, "1-0", 3) == 0) {
8995         char *p, *q, *r = "";
8996         p = strchr(message, '{');
8997         if (p) {
8998             q = strchr(p, '}');
8999             if (q) {
9000                 *q = NULLCHAR;
9001                 r = p + 1;
9002             }
9003         }
9004         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9005         return;
9006     } else if (strncmp(message, "0-1", 3) == 0) {
9007         char *p, *q, *r = "";
9008         p = strchr(message, '{');
9009         if (p) {
9010             q = strchr(p, '}');
9011             if (q) {
9012                 *q = NULLCHAR;
9013                 r = p + 1;
9014             }
9015         }
9016         /* Kludge for Arasan 4.1 bug */
9017         if (strcmp(r, "Black resigns") == 0) {
9018             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9019             return;
9020         }
9021         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9022         return;
9023     } else if (strncmp(message, "1/2", 3) == 0) {
9024         char *p, *q, *r = "";
9025         p = strchr(message, '{');
9026         if (p) {
9027             q = strchr(p, '}');
9028             if (q) {
9029                 *q = NULLCHAR;
9030                 r = p + 1;
9031             }
9032         }
9033
9034         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9035         return;
9036
9037     } else if (strncmp(message, "White resign", 12) == 0) {
9038         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9039         return;
9040     } else if (strncmp(message, "Black resign", 12) == 0) {
9041         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9042         return;
9043     } else if (strncmp(message, "White matches", 13) == 0 ||
9044                strncmp(message, "Black matches", 13) == 0   ) {
9045         /* [HGM] ignore GNUShogi noises */
9046         return;
9047     } else if (strncmp(message, "White", 5) == 0 &&
9048                message[5] != '(' &&
9049                StrStr(message, "Black") == NULL) {
9050         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9051         return;
9052     } else if (strncmp(message, "Black", 5) == 0 &&
9053                message[5] != '(') {
9054         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9055         return;
9056     } else if (strcmp(message, "resign") == 0 ||
9057                strcmp(message, "computer resigns") == 0) {
9058         switch (gameMode) {
9059           case MachinePlaysBlack:
9060           case IcsPlayingBlack:
9061             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9062             break;
9063           case MachinePlaysWhite:
9064           case IcsPlayingWhite:
9065             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9066             break;
9067           case TwoMachinesPlay:
9068             if (cps->twoMachinesColor[0] == 'w')
9069               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9070             else
9071               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9072             break;
9073           default:
9074             /* can't happen */
9075             break;
9076         }
9077         return;
9078     } else if (strncmp(message, "opponent mates", 14) == 0) {
9079         switch (gameMode) {
9080           case MachinePlaysBlack:
9081           case IcsPlayingBlack:
9082             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9083             break;
9084           case MachinePlaysWhite:
9085           case IcsPlayingWhite:
9086             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9087             break;
9088           case TwoMachinesPlay:
9089             if (cps->twoMachinesColor[0] == 'w')
9090               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9091             else
9092               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9093             break;
9094           default:
9095             /* can't happen */
9096             break;
9097         }
9098         return;
9099     } else if (strncmp(message, "computer mates", 14) == 0) {
9100         switch (gameMode) {
9101           case MachinePlaysBlack:
9102           case IcsPlayingBlack:
9103             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9104             break;
9105           case MachinePlaysWhite:
9106           case IcsPlayingWhite:
9107             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9108             break;
9109           case TwoMachinesPlay:
9110             if (cps->twoMachinesColor[0] == 'w')
9111               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9112             else
9113               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9114             break;
9115           default:
9116             /* can't happen */
9117             break;
9118         }
9119         return;
9120     } else if (strncmp(message, "checkmate", 9) == 0) {
9121         if (WhiteOnMove(forwardMostMove)) {
9122             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9123         } else {
9124             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9125         }
9126         return;
9127     } else if (strstr(message, "Draw") != NULL ||
9128                strstr(message, "game is a draw") != NULL) {
9129         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9130         return;
9131     } else if (strstr(message, "offer") != NULL &&
9132                strstr(message, "draw") != NULL) {
9133 #if ZIPPY
9134         if (appData.zippyPlay && first.initDone) {
9135             /* Relay offer to ICS */
9136             SendToICS(ics_prefix);
9137             SendToICS("draw\n");
9138         }
9139 #endif
9140         cps->offeredDraw = 2; /* valid until this engine moves twice */
9141         if (gameMode == TwoMachinesPlay) {
9142             if (cps->other->offeredDraw) {
9143                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9144             /* [HGM] in two-machine mode we delay relaying draw offer      */
9145             /* until after we also have move, to see if it is really claim */
9146             }
9147         } else if (gameMode == MachinePlaysWhite ||
9148                    gameMode == MachinePlaysBlack) {
9149           if (userOfferedDraw) {
9150             DisplayInformation(_("Machine accepts your draw offer"));
9151             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9152           } else {
9153             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9154           }
9155         }
9156     }
9157
9158
9159     /*
9160      * Look for thinking output
9161      */
9162     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9163           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9164                                 ) {
9165         int plylev, mvleft, mvtot, curscore, time;
9166         char mvname[MOVE_LEN];
9167         u64 nodes; // [DM]
9168         char plyext;
9169         int ignore = FALSE;
9170         int prefixHint = FALSE;
9171         mvname[0] = NULLCHAR;
9172
9173         switch (gameMode) {
9174           case MachinePlaysBlack:
9175           case IcsPlayingBlack:
9176             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9177             break;
9178           case MachinePlaysWhite:
9179           case IcsPlayingWhite:
9180             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9181             break;
9182           case AnalyzeMode:
9183           case AnalyzeFile:
9184             break;
9185           case IcsObserving: /* [DM] icsEngineAnalyze */
9186             if (!appData.icsEngineAnalyze) ignore = TRUE;
9187             break;
9188           case TwoMachinesPlay:
9189             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9190                 ignore = TRUE;
9191             }
9192             break;
9193           default:
9194             ignore = TRUE;
9195             break;
9196         }
9197
9198         if (!ignore) {
9199             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9200             buf1[0] = NULLCHAR;
9201             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9202                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9203
9204                 if (plyext != ' ' && plyext != '\t') {
9205                     time *= 100;
9206                 }
9207
9208                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9209                 if( cps->scoreIsAbsolute &&
9210                     ( gameMode == MachinePlaysBlack ||
9211                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9212                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9213                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9214                      !WhiteOnMove(currentMove)
9215                     ) )
9216                 {
9217                     curscore = -curscore;
9218                 }
9219
9220                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9221
9222                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9223                         char buf[MSG_SIZ];
9224                         FILE *f;
9225                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9226                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9227                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9228                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9229                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9230                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9231                                 fclose(f);
9232                         }
9233                         else
9234                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9235                           DisplayError(_("failed writing PV"), 0);
9236                 }
9237
9238                 tempStats.depth = plylev;
9239                 tempStats.nodes = nodes;
9240                 tempStats.time = time;
9241                 tempStats.score = curscore;
9242                 tempStats.got_only_move = 0;
9243
9244                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9245                         int ticklen;
9246
9247                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9248                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9249                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9250                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9251                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9252                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9253                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9254                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9255                 }
9256
9257                 /* Buffer overflow protection */
9258                 if (pv[0] != NULLCHAR) {
9259                     if (strlen(pv) >= sizeof(tempStats.movelist)
9260                         && appData.debugMode) {
9261                         fprintf(debugFP,
9262                                 "PV is too long; using the first %u bytes.\n",
9263                                 (unsigned) sizeof(tempStats.movelist) - 1);
9264                     }
9265
9266                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9267                 } else {
9268                     sprintf(tempStats.movelist, " no PV\n");
9269                 }
9270
9271                 if (tempStats.seen_stat) {
9272                     tempStats.ok_to_send = 1;
9273                 }
9274
9275                 if (strchr(tempStats.movelist, '(') != NULL) {
9276                     tempStats.line_is_book = 1;
9277                     tempStats.nr_moves = 0;
9278                     tempStats.moves_left = 0;
9279                 } else {
9280                     tempStats.line_is_book = 0;
9281                 }
9282
9283                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9284                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9285
9286                 SendProgramStatsToFrontend( cps, &tempStats );
9287
9288                 /*
9289                     [AS] Protect the thinkOutput buffer from overflow... this
9290                     is only useful if buf1 hasn't overflowed first!
9291                 */
9292                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9293                          plylev,
9294                          (gameMode == TwoMachinesPlay ?
9295                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9296                          ((double) curscore) / 100.0,
9297                          prefixHint ? lastHint : "",
9298                          prefixHint ? " " : "" );
9299
9300                 if( buf1[0] != NULLCHAR ) {
9301                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9302
9303                     if( strlen(pv) > max_len ) {
9304                         if( appData.debugMode) {
9305                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9306                         }
9307                         pv[max_len+1] = '\0';
9308                     }
9309
9310                     strcat( thinkOutput, pv);
9311                 }
9312
9313                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9314                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9315                     DisplayMove(currentMove - 1);
9316                 }
9317                 return;
9318
9319             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9320                 /* crafty (9.25+) says "(only move) <move>"
9321                  * if there is only 1 legal move
9322                  */
9323                 sscanf(p, "(only move) %s", buf1);
9324                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9325                 sprintf(programStats.movelist, "%s (only move)", buf1);
9326                 programStats.depth = 1;
9327                 programStats.nr_moves = 1;
9328                 programStats.moves_left = 1;
9329                 programStats.nodes = 1;
9330                 programStats.time = 1;
9331                 programStats.got_only_move = 1;
9332
9333                 /* Not really, but we also use this member to
9334                    mean "line isn't going to change" (Crafty
9335                    isn't searching, so stats won't change) */
9336                 programStats.line_is_book = 1;
9337
9338                 SendProgramStatsToFrontend( cps, &programStats );
9339
9340                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9341                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9342                     DisplayMove(currentMove - 1);
9343                 }
9344                 return;
9345             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9346                               &time, &nodes, &plylev, &mvleft,
9347                               &mvtot, mvname) >= 5) {
9348                 /* The stat01: line is from Crafty (9.29+) in response
9349                    to the "." command */
9350                 programStats.seen_stat = 1;
9351                 cps->maybeThinking = TRUE;
9352
9353                 if (programStats.got_only_move || !appData.periodicUpdates)
9354                   return;
9355
9356                 programStats.depth = plylev;
9357                 programStats.time = time;
9358                 programStats.nodes = nodes;
9359                 programStats.moves_left = mvleft;
9360                 programStats.nr_moves = mvtot;
9361                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9362                 programStats.ok_to_send = 1;
9363                 programStats.movelist[0] = '\0';
9364
9365                 SendProgramStatsToFrontend( cps, &programStats );
9366
9367                 return;
9368
9369             } else if (strncmp(message,"++",2) == 0) {
9370                 /* Crafty 9.29+ outputs this */
9371                 programStats.got_fail = 2;
9372                 return;
9373
9374             } else if (strncmp(message,"--",2) == 0) {
9375                 /* Crafty 9.29+ outputs this */
9376                 programStats.got_fail = 1;
9377                 return;
9378
9379             } else if (thinkOutput[0] != NULLCHAR &&
9380                        strncmp(message, "    ", 4) == 0) {
9381                 unsigned message_len;
9382
9383                 p = message;
9384                 while (*p && *p == ' ') p++;
9385
9386                 message_len = strlen( p );
9387
9388                 /* [AS] Avoid buffer overflow */
9389                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9390                     strcat(thinkOutput, " ");
9391                     strcat(thinkOutput, p);
9392                 }
9393
9394                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9395                     strcat(programStats.movelist, " ");
9396                     strcat(programStats.movelist, p);
9397                 }
9398
9399                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9400                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9401                     DisplayMove(currentMove - 1);
9402                 }
9403                 return;
9404             }
9405         }
9406         else {
9407             buf1[0] = NULLCHAR;
9408
9409             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9410                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9411             {
9412                 ChessProgramStats cpstats;
9413
9414                 if (plyext != ' ' && plyext != '\t') {
9415                     time *= 100;
9416                 }
9417
9418                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9419                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9420                     curscore = -curscore;
9421                 }
9422
9423                 cpstats.depth = plylev;
9424                 cpstats.nodes = nodes;
9425                 cpstats.time = time;
9426                 cpstats.score = curscore;
9427                 cpstats.got_only_move = 0;
9428                 cpstats.movelist[0] = '\0';
9429
9430                 if (buf1[0] != NULLCHAR) {
9431                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9432                 }
9433
9434                 cpstats.ok_to_send = 0;
9435                 cpstats.line_is_book = 0;
9436                 cpstats.nr_moves = 0;
9437                 cpstats.moves_left = 0;
9438
9439                 SendProgramStatsToFrontend( cps, &cpstats );
9440             }
9441         }
9442     }
9443 }
9444
9445
9446 /* Parse a game score from the character string "game", and
9447    record it as the history of the current game.  The game
9448    score is NOT assumed to start from the standard position.
9449    The display is not updated in any way.
9450    */
9451 void
9452 ParseGameHistory (char *game)
9453 {
9454     ChessMove moveType;
9455     int fromX, fromY, toX, toY, boardIndex;
9456     char promoChar;
9457     char *p, *q;
9458     char buf[MSG_SIZ];
9459
9460     if (appData.debugMode)
9461       fprintf(debugFP, "Parsing game history: %s\n", game);
9462
9463     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9464     gameInfo.site = StrSave(appData.icsHost);
9465     gameInfo.date = PGNDate();
9466     gameInfo.round = StrSave("-");
9467
9468     /* Parse out names of players */
9469     while (*game == ' ') game++;
9470     p = buf;
9471     while (*game != ' ') *p++ = *game++;
9472     *p = NULLCHAR;
9473     gameInfo.white = StrSave(buf);
9474     while (*game == ' ') game++;
9475     p = buf;
9476     while (*game != ' ' && *game != '\n') *p++ = *game++;
9477     *p = NULLCHAR;
9478     gameInfo.black = StrSave(buf);
9479
9480     /* Parse moves */
9481     boardIndex = blackPlaysFirst ? 1 : 0;
9482     yynewstr(game);
9483     for (;;) {
9484         yyboardindex = boardIndex;
9485         moveType = (ChessMove) Myylex();
9486         switch (moveType) {
9487           case IllegalMove:             /* maybe suicide chess, etc. */
9488   if (appData.debugMode) {
9489     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9490     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9491     setbuf(debugFP, NULL);
9492   }
9493           case WhitePromotion:
9494           case BlackPromotion:
9495           case WhiteNonPromotion:
9496           case BlackNonPromotion:
9497           case NormalMove:
9498           case WhiteCapturesEnPassant:
9499           case BlackCapturesEnPassant:
9500           case WhiteKingSideCastle:
9501           case WhiteQueenSideCastle:
9502           case BlackKingSideCastle:
9503           case BlackQueenSideCastle:
9504           case WhiteKingSideCastleWild:
9505           case WhiteQueenSideCastleWild:
9506           case BlackKingSideCastleWild:
9507           case BlackQueenSideCastleWild:
9508           /* PUSH Fabien */
9509           case WhiteHSideCastleFR:
9510           case WhiteASideCastleFR:
9511           case BlackHSideCastleFR:
9512           case BlackASideCastleFR:
9513           /* POP Fabien */
9514             fromX = currentMoveString[0] - AAA;
9515             fromY = currentMoveString[1] - ONE;
9516             toX = currentMoveString[2] - AAA;
9517             toY = currentMoveString[3] - ONE;
9518             promoChar = currentMoveString[4];
9519             break;
9520           case WhiteDrop:
9521           case BlackDrop:
9522             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9523             fromX = moveType == WhiteDrop ?
9524               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9525             (int) CharToPiece(ToLower(currentMoveString[0]));
9526             fromY = DROP_RANK;
9527             toX = currentMoveString[2] - AAA;
9528             toY = currentMoveString[3] - ONE;
9529             promoChar = NULLCHAR;
9530             break;
9531           case AmbiguousMove:
9532             /* bug? */
9533             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9534   if (appData.debugMode) {
9535     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9536     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9537     setbuf(debugFP, NULL);
9538   }
9539             DisplayError(buf, 0);
9540             return;
9541           case ImpossibleMove:
9542             /* bug? */
9543             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9544   if (appData.debugMode) {
9545     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9546     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9547     setbuf(debugFP, NULL);
9548   }
9549             DisplayError(buf, 0);
9550             return;
9551           case EndOfFile:
9552             if (boardIndex < backwardMostMove) {
9553                 /* Oops, gap.  How did that happen? */
9554                 DisplayError(_("Gap in move list"), 0);
9555                 return;
9556             }
9557             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9558             if (boardIndex > forwardMostMove) {
9559                 forwardMostMove = boardIndex;
9560             }
9561             return;
9562           case ElapsedTime:
9563             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9564                 strcat(parseList[boardIndex-1], " ");
9565                 strcat(parseList[boardIndex-1], yy_text);
9566             }
9567             continue;
9568           case Comment:
9569           case PGNTag:
9570           case NAG:
9571           default:
9572             /* ignore */
9573             continue;
9574           case WhiteWins:
9575           case BlackWins:
9576           case GameIsDrawn:
9577           case GameUnfinished:
9578             if (gameMode == IcsExamining) {
9579                 if (boardIndex < backwardMostMove) {
9580                     /* Oops, gap.  How did that happen? */
9581                     return;
9582                 }
9583                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9584                 return;
9585             }
9586             gameInfo.result = moveType;
9587             p = strchr(yy_text, '{');
9588             if (p == NULL) p = strchr(yy_text, '(');
9589             if (p == NULL) {
9590                 p = yy_text;
9591                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9592             } else {
9593                 q = strchr(p, *p == '{' ? '}' : ')');
9594                 if (q != NULL) *q = NULLCHAR;
9595                 p++;
9596             }
9597             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9598             gameInfo.resultDetails = StrSave(p);
9599             continue;
9600         }
9601         if (boardIndex >= forwardMostMove &&
9602             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9603             backwardMostMove = blackPlaysFirst ? 1 : 0;
9604             return;
9605         }
9606         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9607                                  fromY, fromX, toY, toX, promoChar,
9608                                  parseList[boardIndex]);
9609         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9610         /* currentMoveString is set as a side-effect of yylex */
9611         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9612         strcat(moveList[boardIndex], "\n");
9613         boardIndex++;
9614         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9615         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9616           case MT_NONE:
9617           case MT_STALEMATE:
9618           default:
9619             break;
9620           case MT_CHECK:
9621             if(gameInfo.variant != VariantShogi)
9622                 strcat(parseList[boardIndex - 1], "+");
9623             break;
9624           case MT_CHECKMATE:
9625           case MT_STAINMATE:
9626             strcat(parseList[boardIndex - 1], "#");
9627             break;
9628         }
9629     }
9630 }
9631
9632
9633 /* Apply a move to the given board  */
9634 void
9635 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9636 {
9637   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9638   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9639
9640     /* [HGM] compute & store e.p. status and castling rights for new position */
9641     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9642
9643       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9644       oldEP = (signed char)board[EP_STATUS];
9645       board[EP_STATUS] = EP_NONE;
9646
9647   if (fromY == DROP_RANK) {
9648         /* must be first */
9649         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9650             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9651             return;
9652         }
9653         piece = board[toY][toX] = (ChessSquare) fromX;
9654   } else {
9655       ChessSquare victim;
9656       int i;
9657
9658       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9659            victim = board[killY][killX],
9660            board[killY][killX] = EmptySquare,
9661            board[EP_STATUS] = EP_CAPTURE;
9662
9663       if( board[toY][toX] != EmptySquare ) {
9664            board[EP_STATUS] = EP_CAPTURE;
9665            if( (fromX != toX || fromY != toY) && // not igui!
9666                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9667                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9668                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9669            }
9670       }
9671
9672       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9673            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9674                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9675       } else
9676       if( board[fromY][fromX] == WhitePawn ) {
9677            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9678                board[EP_STATUS] = EP_PAWN_MOVE;
9679            if( toY-fromY==2) {
9680                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9681                         gameInfo.variant != VariantBerolina || toX < fromX)
9682                       board[EP_STATUS] = toX | berolina;
9683                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9684                         gameInfo.variant != VariantBerolina || toX > fromX)
9685                       board[EP_STATUS] = toX;
9686            }
9687       } else
9688       if( board[fromY][fromX] == BlackPawn ) {
9689            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9690                board[EP_STATUS] = EP_PAWN_MOVE;
9691            if( toY-fromY== -2) {
9692                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9693                         gameInfo.variant != VariantBerolina || toX < fromX)
9694                       board[EP_STATUS] = toX | berolina;
9695                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9696                         gameInfo.variant != VariantBerolina || toX > fromX)
9697                       board[EP_STATUS] = toX;
9698            }
9699        }
9700
9701        for(i=0; i<nrCastlingRights; i++) {
9702            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9703               board[CASTLING][i] == toX   && castlingRank[i] == toY
9704              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9705        }
9706
9707        if(gameInfo.variant == VariantSChess) { // update virginity
9708            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9709            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9710            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9711            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9712        }
9713
9714      if (fromX == toX && fromY == toY) return;
9715
9716      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9717      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9718      if(gameInfo.variant == VariantKnightmate)
9719          king += (int) WhiteUnicorn - (int) WhiteKing;
9720
9721     /* Code added by Tord: */
9722     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9723     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9724         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9725       board[fromY][fromX] = EmptySquare;
9726       board[toY][toX] = EmptySquare;
9727       if((toX > fromX) != (piece == WhiteRook)) {
9728         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9729       } else {
9730         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9731       }
9732     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9733                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9734       board[fromY][fromX] = EmptySquare;
9735       board[toY][toX] = EmptySquare;
9736       if((toX > fromX) != (piece == BlackRook)) {
9737         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9738       } else {
9739         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9740       }
9741     /* End of code added by Tord */
9742
9743     } else if (board[fromY][fromX] == king
9744         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9745         && toY == fromY && toX > fromX+1) {
9746         board[fromY][fromX] = EmptySquare;
9747         board[toY][toX] = king;
9748         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9749         board[fromY][BOARD_RGHT-1] = EmptySquare;
9750     } else if (board[fromY][fromX] == king
9751         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9752                && toY == fromY && toX < fromX-1) {
9753         board[fromY][fromX] = EmptySquare;
9754         board[toY][toX] = king;
9755         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9756         board[fromY][BOARD_LEFT] = EmptySquare;
9757     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9758                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9759                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9760                ) {
9761         /* white pawn promotion */
9762         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9763         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9764             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9765         board[fromY][fromX] = EmptySquare;
9766     } else if ((fromY >= BOARD_HEIGHT>>1)
9767                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9768                && (toX != fromX)
9769                && gameInfo.variant != VariantXiangqi
9770                && gameInfo.variant != VariantBerolina
9771                && (board[fromY][fromX] == WhitePawn)
9772                && (board[toY][toX] == EmptySquare)) {
9773         board[fromY][fromX] = EmptySquare;
9774         board[toY][toX] = WhitePawn;
9775         captured = board[toY - 1][toX];
9776         board[toY - 1][toX] = EmptySquare;
9777     } else if ((fromY == BOARD_HEIGHT-4)
9778                && (toX == fromX)
9779                && gameInfo.variant == VariantBerolina
9780                && (board[fromY][fromX] == WhitePawn)
9781                && (board[toY][toX] == EmptySquare)) {
9782         board[fromY][fromX] = EmptySquare;
9783         board[toY][toX] = WhitePawn;
9784         if(oldEP & EP_BEROLIN_A) {
9785                 captured = board[fromY][fromX-1];
9786                 board[fromY][fromX-1] = EmptySquare;
9787         }else{  captured = board[fromY][fromX+1];
9788                 board[fromY][fromX+1] = EmptySquare;
9789         }
9790     } else if (board[fromY][fromX] == king
9791         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9792                && toY == fromY && toX > fromX+1) {
9793         board[fromY][fromX] = EmptySquare;
9794         board[toY][toX] = king;
9795         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9796         board[fromY][BOARD_RGHT-1] = EmptySquare;
9797     } else if (board[fromY][fromX] == king
9798         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9799                && toY == fromY && toX < fromX-1) {
9800         board[fromY][fromX] = EmptySquare;
9801         board[toY][toX] = king;
9802         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9803         board[fromY][BOARD_LEFT] = EmptySquare;
9804     } else if (fromY == 7 && fromX == 3
9805                && board[fromY][fromX] == BlackKing
9806                && toY == 7 && toX == 5) {
9807         board[fromY][fromX] = EmptySquare;
9808         board[toY][toX] = BlackKing;
9809         board[fromY][7] = EmptySquare;
9810         board[toY][4] = BlackRook;
9811     } else if (fromY == 7 && fromX == 3
9812                && board[fromY][fromX] == BlackKing
9813                && toY == 7 && toX == 1) {
9814         board[fromY][fromX] = EmptySquare;
9815         board[toY][toX] = BlackKing;
9816         board[fromY][0] = EmptySquare;
9817         board[toY][2] = BlackRook;
9818     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9819                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9820                && toY < promoRank && promoChar
9821                ) {
9822         /* black pawn promotion */
9823         board[toY][toX] = CharToPiece(ToLower(promoChar));
9824         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9825             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9826         board[fromY][fromX] = EmptySquare;
9827     } else if ((fromY < BOARD_HEIGHT>>1)
9828                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9829                && (toX != fromX)
9830                && gameInfo.variant != VariantXiangqi
9831                && gameInfo.variant != VariantBerolina
9832                && (board[fromY][fromX] == BlackPawn)
9833                && (board[toY][toX] == EmptySquare)) {
9834         board[fromY][fromX] = EmptySquare;
9835         board[toY][toX] = BlackPawn;
9836         captured = board[toY + 1][toX];
9837         board[toY + 1][toX] = EmptySquare;
9838     } else if ((fromY == 3)
9839                && (toX == fromX)
9840                && gameInfo.variant == VariantBerolina
9841                && (board[fromY][fromX] == BlackPawn)
9842                && (board[toY][toX] == EmptySquare)) {
9843         board[fromY][fromX] = EmptySquare;
9844         board[toY][toX] = BlackPawn;
9845         if(oldEP & EP_BEROLIN_A) {
9846                 captured = board[fromY][fromX-1];
9847                 board[fromY][fromX-1] = EmptySquare;
9848         }else{  captured = board[fromY][fromX+1];
9849                 board[fromY][fromX+1] = EmptySquare;
9850         }
9851     } else {
9852         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9853         board[fromY][fromX] = EmptySquare;
9854         board[toY][toX] = piece;
9855     }
9856   }
9857
9858     if (gameInfo.holdingsWidth != 0) {
9859
9860       /* !!A lot more code needs to be written to support holdings  */
9861       /* [HGM] OK, so I have written it. Holdings are stored in the */
9862       /* penultimate board files, so they are automaticlly stored   */
9863       /* in the game history.                                       */
9864       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9865                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9866         /* Delete from holdings, by decreasing count */
9867         /* and erasing image if necessary            */
9868         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9869         if(p < (int) BlackPawn) { /* white drop */
9870              p -= (int)WhitePawn;
9871                  p = PieceToNumber((ChessSquare)p);
9872              if(p >= gameInfo.holdingsSize) p = 0;
9873              if(--board[p][BOARD_WIDTH-2] <= 0)
9874                   board[p][BOARD_WIDTH-1] = EmptySquare;
9875              if((int)board[p][BOARD_WIDTH-2] < 0)
9876                         board[p][BOARD_WIDTH-2] = 0;
9877         } else {                  /* black drop */
9878              p -= (int)BlackPawn;
9879                  p = PieceToNumber((ChessSquare)p);
9880              if(p >= gameInfo.holdingsSize) p = 0;
9881              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9882                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9883              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9884                         board[BOARD_HEIGHT-1-p][1] = 0;
9885         }
9886       }
9887       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9888           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9889         /* [HGM] holdings: Add to holdings, if holdings exist */
9890         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9891                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9892                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9893         }
9894         p = (int) captured;
9895         if (p >= (int) BlackPawn) {
9896           p -= (int)BlackPawn;
9897           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9898                   /* in Shogi restore piece to its original  first */
9899                   captured = (ChessSquare) (DEMOTED captured);
9900                   p = DEMOTED p;
9901           }
9902           p = PieceToNumber((ChessSquare)p);
9903           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9904           board[p][BOARD_WIDTH-2]++;
9905           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9906         } else {
9907           p -= (int)WhitePawn;
9908           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9909                   captured = (ChessSquare) (DEMOTED captured);
9910                   p = DEMOTED p;
9911           }
9912           p = PieceToNumber((ChessSquare)p);
9913           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9914           board[BOARD_HEIGHT-1-p][1]++;
9915           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9916         }
9917       }
9918     } else if (gameInfo.variant == VariantAtomic) {
9919       if (captured != EmptySquare) {
9920         int y, x;
9921         for (y = toY-1; y <= toY+1; y++) {
9922           for (x = toX-1; x <= toX+1; x++) {
9923             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9924                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9925               board[y][x] = EmptySquare;
9926             }
9927           }
9928         }
9929         board[toY][toX] = EmptySquare;
9930       }
9931     }
9932     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9933         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9934     } else
9935     if(promoChar == '+') {
9936         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9937         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9938     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9939         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9940         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9941            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9942         board[toY][toX] = newPiece;
9943     }
9944     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9945                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9946         // [HGM] superchess: take promotion piece out of holdings
9947         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9948         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9949             if(!--board[k][BOARD_WIDTH-2])
9950                 board[k][BOARD_WIDTH-1] = EmptySquare;
9951         } else {
9952             if(!--board[BOARD_HEIGHT-1-k][1])
9953                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9954         }
9955     }
9956 }
9957
9958 /* Updates forwardMostMove */
9959 void
9960 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9961 {
9962     int x = toX, y = toY;
9963     char *s = parseList[forwardMostMove];
9964     ChessSquare p = boards[forwardMostMove][toY][toX];
9965 //    forwardMostMove++; // [HGM] bare: moved downstream
9966
9967     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9968     (void) CoordsToAlgebraic(boards[forwardMostMove],
9969                              PosFlags(forwardMostMove),
9970                              fromY, fromX, y, x, promoChar,
9971                              s);
9972     if(killX >= 0 && killY >= 0)
9973         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9974
9975     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9976         int timeLeft; static int lastLoadFlag=0; int king, piece;
9977         piece = boards[forwardMostMove][fromY][fromX];
9978         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9979         if(gameInfo.variant == VariantKnightmate)
9980             king += (int) WhiteUnicorn - (int) WhiteKing;
9981         if(forwardMostMove == 0) {
9982             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9983                 fprintf(serverMoves, "%s;", UserName());
9984             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9985                 fprintf(serverMoves, "%s;", second.tidy);
9986             fprintf(serverMoves, "%s;", first.tidy);
9987             if(gameMode == MachinePlaysWhite)
9988                 fprintf(serverMoves, "%s;", UserName());
9989             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9990                 fprintf(serverMoves, "%s;", second.tidy);
9991         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9992         lastLoadFlag = loadFlag;
9993         // print base move
9994         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9995         // print castling suffix
9996         if( toY == fromY && piece == king ) {
9997             if(toX-fromX > 1)
9998                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9999             if(fromX-toX >1)
10000                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10001         }
10002         // e.p. suffix
10003         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10004              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10005              boards[forwardMostMove][toY][toX] == EmptySquare
10006              && fromX != toX && fromY != toY)
10007                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10008         // promotion suffix
10009         if(promoChar != NULLCHAR) {
10010             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10011                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10012                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10013             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10014         }
10015         if(!loadFlag) {
10016                 char buf[MOVE_LEN*2], *p; int len;
10017             fprintf(serverMoves, "/%d/%d",
10018                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10019             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10020             else                      timeLeft = blackTimeRemaining/1000;
10021             fprintf(serverMoves, "/%d", timeLeft);
10022                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10023                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10024                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10025                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10026             fprintf(serverMoves, "/%s", buf);
10027         }
10028         fflush(serverMoves);
10029     }
10030
10031     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10032         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10033       return;
10034     }
10035     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10036     if (commentList[forwardMostMove+1] != NULL) {
10037         free(commentList[forwardMostMove+1]);
10038         commentList[forwardMostMove+1] = NULL;
10039     }
10040     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10041     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10042     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10043     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10044     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10045     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10046     adjustedClock = FALSE;
10047     gameInfo.result = GameUnfinished;
10048     if (gameInfo.resultDetails != NULL) {
10049         free(gameInfo.resultDetails);
10050         gameInfo.resultDetails = NULL;
10051     }
10052     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10053                               moveList[forwardMostMove - 1]);
10054     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10055       case MT_NONE:
10056       case MT_STALEMATE:
10057       default:
10058         break;
10059       case MT_CHECK:
10060         if(gameInfo.variant != VariantShogi)
10061             strcat(parseList[forwardMostMove - 1], "+");
10062         break;
10063       case MT_CHECKMATE:
10064       case MT_STAINMATE:
10065         strcat(parseList[forwardMostMove - 1], "#");
10066         break;
10067     }
10068
10069     killX = killY = -1; // [HGM] lion: used up
10070 }
10071
10072 /* Updates currentMove if not pausing */
10073 void
10074 ShowMove (int fromX, int fromY, int toX, int toY)
10075 {
10076     int instant = (gameMode == PlayFromGameFile) ?
10077         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10078     if(appData.noGUI) return;
10079     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10080         if (!instant) {
10081             if (forwardMostMove == currentMove + 1) {
10082                 AnimateMove(boards[forwardMostMove - 1],
10083                             fromX, fromY, toX, toY);
10084             }
10085         }
10086         currentMove = forwardMostMove;
10087     }
10088
10089     if (instant) return;
10090
10091     DisplayMove(currentMove - 1);
10092     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10093             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10094                 SetHighlights(fromX, fromY, toX, toY);
10095             }
10096     }
10097     DrawPosition(FALSE, boards[currentMove]);
10098     DisplayBothClocks();
10099     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10100 }
10101
10102 void
10103 SendEgtPath (ChessProgramState *cps)
10104 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10105         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10106
10107         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10108
10109         while(*p) {
10110             char c, *q = name+1, *r, *s;
10111
10112             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10113             while(*p && *p != ',') *q++ = *p++;
10114             *q++ = ':'; *q = 0;
10115             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10116                 strcmp(name, ",nalimov:") == 0 ) {
10117                 // take nalimov path from the menu-changeable option first, if it is defined
10118               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10119                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10120             } else
10121             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10122                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10123                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10124                 s = r = StrStr(s, ":") + 1; // beginning of path info
10125                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10126                 c = *r; *r = 0;             // temporarily null-terminate path info
10127                     *--q = 0;               // strip of trailig ':' from name
10128                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10129                 *r = c;
10130                 SendToProgram(buf,cps);     // send egtbpath command for this format
10131             }
10132             if(*p == ',') p++; // read away comma to position for next format name
10133         }
10134 }
10135
10136 static int
10137 NonStandardBoardSize ()
10138 {
10139       /* [HGM] Awkward testing. Should really be a table */
10140       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10141       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10142       if( gameInfo.variant == VariantXiangqi )
10143            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10144       if( gameInfo.variant == VariantShogi )
10145            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10146       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10147            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10148       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10149           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10150            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10151       if( gameInfo.variant == VariantCourier )
10152            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10153       if( gameInfo.variant == VariantSuper )
10154            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10155       if( gameInfo.variant == VariantGreat )
10156            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10157       if( gameInfo.variant == VariantSChess )
10158            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10159       if( gameInfo.variant == VariantGrand )
10160            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10161       if( gameInfo.variant == VariantChu )
10162            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10163       return overruled;
10164 }
10165
10166 void
10167 InitChessProgram (ChessProgramState *cps, int setup)
10168 /* setup needed to setup FRC opening position */
10169 {
10170     char buf[MSG_SIZ], b[MSG_SIZ];
10171     if (appData.noChessProgram) return;
10172     hintRequested = FALSE;
10173     bookRequested = FALSE;
10174
10175     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10176     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10177     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10178     if(cps->memSize) { /* [HGM] memory */
10179       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10180         SendToProgram(buf, cps);
10181     }
10182     SendEgtPath(cps); /* [HGM] EGT */
10183     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10184       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10185         SendToProgram(buf, cps);
10186     }
10187
10188     SendToProgram(cps->initString, cps);
10189     if (gameInfo.variant != VariantNormal &&
10190         gameInfo.variant != VariantLoadable
10191         /* [HGM] also send variant if board size non-standard */
10192         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10193                                             ) {
10194       char *v = VariantName(gameInfo.variant);
10195       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10196         /* [HGM] in protocol 1 we have to assume all variants valid */
10197         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10198         DisplayFatalError(buf, 0, 1);
10199         return;
10200       }
10201
10202       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10203         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10204                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10205            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10206            if(StrStr(cps->variants, b) == NULL) {
10207                // specific sized variant not known, check if general sizing allowed
10208                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10209                    if(StrStr(cps->variants, "boardsize") == NULL) {
10210                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10211                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10212                        DisplayFatalError(buf, 0, 1);
10213                        return;
10214                    }
10215                    /* [HGM] here we really should compare with the maximum supported board size */
10216                }
10217            }
10218       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10219       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10220       SendToProgram(buf, cps);
10221     }
10222     currentlyInitializedVariant = gameInfo.variant;
10223
10224     /* [HGM] send opening position in FRC to first engine */
10225     if(setup) {
10226           SendToProgram("force\n", cps);
10227           SendBoard(cps, 0);
10228           /* engine is now in force mode! Set flag to wake it up after first move. */
10229           setboardSpoiledMachineBlack = 1;
10230     }
10231
10232     if (cps->sendICS) {
10233       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10234       SendToProgram(buf, cps);
10235     }
10236     cps->maybeThinking = FALSE;
10237     cps->offeredDraw = 0;
10238     if (!appData.icsActive) {
10239         SendTimeControl(cps, movesPerSession, timeControl,
10240                         timeIncrement, appData.searchDepth,
10241                         searchTime);
10242     }
10243     if (appData.showThinking
10244         // [HGM] thinking: four options require thinking output to be sent
10245         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10246                                 ) {
10247         SendToProgram("post\n", cps);
10248     }
10249     SendToProgram("hard\n", cps);
10250     if (!appData.ponderNextMove) {
10251         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10252            it without being sure what state we are in first.  "hard"
10253            is not a toggle, so that one is OK.
10254          */
10255         SendToProgram("easy\n", cps);
10256     }
10257     if (cps->usePing) {
10258       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10259       SendToProgram(buf, cps);
10260     }
10261     cps->initDone = TRUE;
10262     ClearEngineOutputPane(cps == &second);
10263 }
10264
10265
10266 void
10267 ResendOptions (ChessProgramState *cps)
10268 { // send the stored value of the options
10269   int i;
10270   char buf[MSG_SIZ];
10271   Option *opt = cps->option;
10272   for(i=0; i<cps->nrOptions; i++, opt++) {
10273       switch(opt->type) {
10274         case Spin:
10275         case Slider:
10276         case CheckBox:
10277             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10278           break;
10279         case ComboBox:
10280           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10281           break;
10282         default:
10283             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10284           break;
10285         case Button:
10286         case SaveButton:
10287           continue;
10288       }
10289       SendToProgram(buf, cps);
10290   }
10291 }
10292
10293 void
10294 StartChessProgram (ChessProgramState *cps)
10295 {
10296     char buf[MSG_SIZ];
10297     int err;
10298
10299     if (appData.noChessProgram) return;
10300     cps->initDone = FALSE;
10301
10302     if (strcmp(cps->host, "localhost") == 0) {
10303         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10304     } else if (*appData.remoteShell == NULLCHAR) {
10305         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10306     } else {
10307         if (*appData.remoteUser == NULLCHAR) {
10308           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10309                     cps->program);
10310         } else {
10311           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10312                     cps->host, appData.remoteUser, cps->program);
10313         }
10314         err = StartChildProcess(buf, "", &cps->pr);
10315     }
10316
10317     if (err != 0) {
10318       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10319         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10320         if(cps != &first) return;
10321         appData.noChessProgram = TRUE;
10322         ThawUI();
10323         SetNCPMode();
10324 //      DisplayFatalError(buf, err, 1);
10325 //      cps->pr = NoProc;
10326 //      cps->isr = NULL;
10327         return;
10328     }
10329
10330     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10331     if (cps->protocolVersion > 1) {
10332       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10333       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10334         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10335         cps->comboCnt = 0;  //                and values of combo boxes
10336       }
10337       SendToProgram(buf, cps);
10338       if(cps->reload) ResendOptions(cps);
10339     } else {
10340       SendToProgram("xboard\n", cps);
10341     }
10342 }
10343
10344 void
10345 TwoMachinesEventIfReady P((void))
10346 {
10347   static int curMess = 0;
10348   if (first.lastPing != first.lastPong) {
10349     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10350     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10351     return;
10352   }
10353   if (second.lastPing != second.lastPong) {
10354     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10355     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10356     return;
10357   }
10358   DisplayMessage("", ""); curMess = 0;
10359   TwoMachinesEvent();
10360 }
10361
10362 char *
10363 MakeName (char *template)
10364 {
10365     time_t clock;
10366     struct tm *tm;
10367     static char buf[MSG_SIZ];
10368     char *p = buf;
10369     int i;
10370
10371     clock = time((time_t *)NULL);
10372     tm = localtime(&clock);
10373
10374     while(*p++ = *template++) if(p[-1] == '%') {
10375         switch(*template++) {
10376           case 0:   *p = 0; return buf;
10377           case 'Y': i = tm->tm_year+1900; break;
10378           case 'y': i = tm->tm_year-100; break;
10379           case 'M': i = tm->tm_mon+1; break;
10380           case 'd': i = tm->tm_mday; break;
10381           case 'h': i = tm->tm_hour; break;
10382           case 'm': i = tm->tm_min; break;
10383           case 's': i = tm->tm_sec; break;
10384           default:  i = 0;
10385         }
10386         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10387     }
10388     return buf;
10389 }
10390
10391 int
10392 CountPlayers (char *p)
10393 {
10394     int n = 0;
10395     while(p = strchr(p, '\n')) p++, n++; // count participants
10396     return n;
10397 }
10398
10399 FILE *
10400 WriteTourneyFile (char *results, FILE *f)
10401 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10402     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10403     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10404         // create a file with tournament description
10405         fprintf(f, "-participants {%s}\n", appData.participants);
10406         fprintf(f, "-seedBase %d\n", appData.seedBase);
10407         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10408         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10409         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10410         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10411         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10412         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10413         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10414         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10415         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10416         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10417         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10418         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10419         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10420         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10421         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10422         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10423         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10424         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10425         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10426         fprintf(f, "-smpCores %d\n", appData.smpCores);
10427         if(searchTime > 0)
10428                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10429         else {
10430                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10431                 fprintf(f, "-tc %s\n", appData.timeControl);
10432                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10433         }
10434         fprintf(f, "-results \"%s\"\n", results);
10435     }
10436     return f;
10437 }
10438
10439 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10440
10441 void
10442 Substitute (char *participants, int expunge)
10443 {
10444     int i, changed, changes=0, nPlayers=0;
10445     char *p, *q, *r, buf[MSG_SIZ];
10446     if(participants == NULL) return;
10447     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10448     r = p = participants; q = appData.participants;
10449     while(*p && *p == *q) {
10450         if(*p == '\n') r = p+1, nPlayers++;
10451         p++; q++;
10452     }
10453     if(*p) { // difference
10454         while(*p && *p++ != '\n');
10455         while(*q && *q++ != '\n');
10456       changed = nPlayers;
10457         changes = 1 + (strcmp(p, q) != 0);
10458     }
10459     if(changes == 1) { // a single engine mnemonic was changed
10460         q = r; while(*q) nPlayers += (*q++ == '\n');
10461         p = buf; while(*r && (*p = *r++) != '\n') p++;
10462         *p = NULLCHAR;
10463         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10464         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10465         if(mnemonic[i]) { // The substitute is valid
10466             FILE *f;
10467             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10468                 flock(fileno(f), LOCK_EX);
10469                 ParseArgsFromFile(f);
10470                 fseek(f, 0, SEEK_SET);
10471                 FREE(appData.participants); appData.participants = participants;
10472                 if(expunge) { // erase results of replaced engine
10473                     int len = strlen(appData.results), w, b, dummy;
10474                     for(i=0; i<len; i++) {
10475                         Pairing(i, nPlayers, &w, &b, &dummy);
10476                         if((w == changed || b == changed) && appData.results[i] == '*') {
10477                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10478                             fclose(f);
10479                             return;
10480                         }
10481                     }
10482                     for(i=0; i<len; i++) {
10483                         Pairing(i, nPlayers, &w, &b, &dummy);
10484                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10485                     }
10486                 }
10487                 WriteTourneyFile(appData.results, f);
10488                 fclose(f); // release lock
10489                 return;
10490             }
10491         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10492     }
10493     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10494     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10495     free(participants);
10496     return;
10497 }
10498
10499 int
10500 CheckPlayers (char *participants)
10501 {
10502         int i;
10503         char buf[MSG_SIZ], *p;
10504         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10505         while(p = strchr(participants, '\n')) {
10506             *p = NULLCHAR;
10507             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10508             if(!mnemonic[i]) {
10509                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10510                 *p = '\n';
10511                 DisplayError(buf, 0);
10512                 return 1;
10513             }
10514             *p = '\n';
10515             participants = p + 1;
10516         }
10517         return 0;
10518 }
10519
10520 int
10521 CreateTourney (char *name)
10522 {
10523         FILE *f;
10524         if(matchMode && strcmp(name, appData.tourneyFile)) {
10525              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10526         }
10527         if(name[0] == NULLCHAR) {
10528             if(appData.participants[0])
10529                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10530             return 0;
10531         }
10532         f = fopen(name, "r");
10533         if(f) { // file exists
10534             ASSIGN(appData.tourneyFile, name);
10535             ParseArgsFromFile(f); // parse it
10536         } else {
10537             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10538             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10539                 DisplayError(_("Not enough participants"), 0);
10540                 return 0;
10541             }
10542             if(CheckPlayers(appData.participants)) return 0;
10543             ASSIGN(appData.tourneyFile, name);
10544             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10545             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10546         }
10547         fclose(f);
10548         appData.noChessProgram = FALSE;
10549         appData.clockMode = TRUE;
10550         SetGNUMode();
10551         return 1;
10552 }
10553
10554 int
10555 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10556 {
10557     char buf[MSG_SIZ], *p, *q;
10558     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10559     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10560     skip = !all && group[0]; // if group requested, we start in skip mode
10561     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10562         p = names; q = buf; header = 0;
10563         while(*p && *p != '\n') *q++ = *p++;
10564         *q = 0;
10565         if(*p == '\n') p++;
10566         if(buf[0] == '#') {
10567             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10568             depth++; // we must be entering a new group
10569             if(all) continue; // suppress printing group headers when complete list requested
10570             header = 1;
10571             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10572         }
10573         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10574         if(engineList[i]) free(engineList[i]);
10575         engineList[i] = strdup(buf);
10576         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10577         if(engineMnemonic[i]) free(engineMnemonic[i]);
10578         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10579             strcat(buf, " (");
10580             sscanf(q + 8, "%s", buf + strlen(buf));
10581             strcat(buf, ")");
10582         }
10583         engineMnemonic[i] = strdup(buf);
10584         i++;
10585     }
10586     engineList[i] = engineMnemonic[i] = NULL;
10587     return i;
10588 }
10589
10590 // following implemented as macro to avoid type limitations
10591 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10592
10593 void
10594 SwapEngines (int n)
10595 {   // swap settings for first engine and other engine (so far only some selected options)
10596     int h;
10597     char *p;
10598     if(n == 0) return;
10599     SWAP(directory, p)
10600     SWAP(chessProgram, p)
10601     SWAP(isUCI, h)
10602     SWAP(hasOwnBookUCI, h)
10603     SWAP(protocolVersion, h)
10604     SWAP(reuse, h)
10605     SWAP(scoreIsAbsolute, h)
10606     SWAP(timeOdds, h)
10607     SWAP(logo, p)
10608     SWAP(pgnName, p)
10609     SWAP(pvSAN, h)
10610     SWAP(engOptions, p)
10611     SWAP(engInitString, p)
10612     SWAP(computerString, p)
10613     SWAP(features, p)
10614     SWAP(fenOverride, p)
10615     SWAP(NPS, h)
10616     SWAP(accumulateTC, h)
10617     SWAP(host, p)
10618 }
10619
10620 int
10621 GetEngineLine (char *s, int n)
10622 {
10623     int i;
10624     char buf[MSG_SIZ];
10625     extern char *icsNames;
10626     if(!s || !*s) return 0;
10627     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10628     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10629     if(!mnemonic[i]) return 0;
10630     if(n == 11) return 1; // just testing if there was a match
10631     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10632     if(n == 1) SwapEngines(n);
10633     ParseArgsFromString(buf);
10634     if(n == 1) SwapEngines(n);
10635     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10636         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10637         ParseArgsFromString(buf);
10638     }
10639     return 1;
10640 }
10641
10642 int
10643 SetPlayer (int player, char *p)
10644 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10645     int i;
10646     char buf[MSG_SIZ], *engineName;
10647     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10648     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10649     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10650     if(mnemonic[i]) {
10651         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10652         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10653         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10654         ParseArgsFromString(buf);
10655     } else { // no engine with this nickname is installed!
10656         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10657         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10658         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10659         ModeHighlight();
10660         DisplayError(buf, 0);
10661         return 0;
10662     }
10663     free(engineName);
10664     return i;
10665 }
10666
10667 char *recentEngines;
10668
10669 void
10670 RecentEngineEvent (int nr)
10671 {
10672     int n;
10673 //    SwapEngines(1); // bump first to second
10674 //    ReplaceEngine(&second, 1); // and load it there
10675     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10676     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10677     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10678         ReplaceEngine(&first, 0);
10679         FloatToFront(&appData.recentEngineList, command[n]);
10680     }
10681 }
10682
10683 int
10684 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10685 {   // determine players from game number
10686     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10687
10688     if(appData.tourneyType == 0) {
10689         roundsPerCycle = (nPlayers - 1) | 1;
10690         pairingsPerRound = nPlayers / 2;
10691     } else if(appData.tourneyType > 0) {
10692         roundsPerCycle = nPlayers - appData.tourneyType;
10693         pairingsPerRound = appData.tourneyType;
10694     }
10695     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10696     gamesPerCycle = gamesPerRound * roundsPerCycle;
10697     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10698     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10699     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10700     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10701     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10702     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10703
10704     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10705     if(appData.roundSync) *syncInterval = gamesPerRound;
10706
10707     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10708
10709     if(appData.tourneyType == 0) {
10710         if(curPairing == (nPlayers-1)/2 ) {
10711             *whitePlayer = curRound;
10712             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10713         } else {
10714             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10715             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10716             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10717             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10718         }
10719     } else if(appData.tourneyType > 1) {
10720         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10721         *whitePlayer = curRound + appData.tourneyType;
10722     } else if(appData.tourneyType > 0) {
10723         *whitePlayer = curPairing;
10724         *blackPlayer = curRound + appData.tourneyType;
10725     }
10726
10727     // take care of white/black alternation per round.
10728     // For cycles and games this is already taken care of by default, derived from matchGame!
10729     return curRound & 1;
10730 }
10731
10732 int
10733 NextTourneyGame (int nr, int *swapColors)
10734 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10735     char *p, *q;
10736     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10737     FILE *tf;
10738     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10739     tf = fopen(appData.tourneyFile, "r");
10740     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10741     ParseArgsFromFile(tf); fclose(tf);
10742     InitTimeControls(); // TC might be altered from tourney file
10743
10744     nPlayers = CountPlayers(appData.participants); // count participants
10745     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10746     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10747
10748     if(syncInterval) {
10749         p = q = appData.results;
10750         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10751         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10752             DisplayMessage(_("Waiting for other game(s)"),"");
10753             waitingForGame = TRUE;
10754             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10755             return 0;
10756         }
10757         waitingForGame = FALSE;
10758     }
10759
10760     if(appData.tourneyType < 0) {
10761         if(nr>=0 && !pairingReceived) {
10762             char buf[1<<16];
10763             if(pairing.pr == NoProc) {
10764                 if(!appData.pairingEngine[0]) {
10765                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10766                     return 0;
10767                 }
10768                 StartChessProgram(&pairing); // starts the pairing engine
10769             }
10770             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10771             SendToProgram(buf, &pairing);
10772             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10773             SendToProgram(buf, &pairing);
10774             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10775         }
10776         pairingReceived = 0;                              // ... so we continue here
10777         *swapColors = 0;
10778         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10779         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10780         matchGame = 1; roundNr = nr / syncInterval + 1;
10781     }
10782
10783     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10784
10785     // redefine engines, engine dir, etc.
10786     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10787     if(first.pr == NoProc) {
10788       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10789       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10790     }
10791     if(second.pr == NoProc) {
10792       SwapEngines(1);
10793       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10794       SwapEngines(1);         // and make that valid for second engine by swapping
10795       InitEngine(&second, 1);
10796     }
10797     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10798     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10799     return OK;
10800 }
10801
10802 void
10803 NextMatchGame ()
10804 {   // performs game initialization that does not invoke engines, and then tries to start the game
10805     int res, firstWhite, swapColors = 0;
10806     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10807     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
10808         char buf[MSG_SIZ];
10809         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10810         if(strcmp(buf, currentDebugFile)) { // name has changed
10811             FILE *f = fopen(buf, "w");
10812             if(f) { // if opening the new file failed, just keep using the old one
10813                 ASSIGN(currentDebugFile, buf);
10814                 fclose(debugFP);
10815                 debugFP = f;
10816             }
10817             if(appData.serverFileName) {
10818                 if(serverFP) fclose(serverFP);
10819                 serverFP = fopen(appData.serverFileName, "w");
10820                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10821                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10822             }
10823         }
10824     }
10825     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10826     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10827     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10828     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10829     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10830     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10831     Reset(FALSE, first.pr != NoProc);
10832     res = LoadGameOrPosition(matchGame); // setup game
10833     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10834     if(!res) return; // abort when bad game/pos file
10835     TwoMachinesEvent();
10836 }
10837
10838 void
10839 UserAdjudicationEvent (int result)
10840 {
10841     ChessMove gameResult = GameIsDrawn;
10842
10843     if( result > 0 ) {
10844         gameResult = WhiteWins;
10845     }
10846     else if( result < 0 ) {
10847         gameResult = BlackWins;
10848     }
10849
10850     if( gameMode == TwoMachinesPlay ) {
10851         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10852     }
10853 }
10854
10855
10856 // [HGM] save: calculate checksum of game to make games easily identifiable
10857 int
10858 StringCheckSum (char *s)
10859 {
10860         int i = 0;
10861         if(s==NULL) return 0;
10862         while(*s) i = i*259 + *s++;
10863         return i;
10864 }
10865
10866 int
10867 GameCheckSum ()
10868 {
10869         int i, sum=0;
10870         for(i=backwardMostMove; i<forwardMostMove; i++) {
10871                 sum += pvInfoList[i].depth;
10872                 sum += StringCheckSum(parseList[i]);
10873                 sum += StringCheckSum(commentList[i]);
10874                 sum *= 261;
10875         }
10876         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10877         return sum + StringCheckSum(commentList[i]);
10878 } // end of save patch
10879
10880 void
10881 GameEnds (ChessMove result, char *resultDetails, int whosays)
10882 {
10883     GameMode nextGameMode;
10884     int isIcsGame;
10885     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10886
10887     if(endingGame) return; /* [HGM] crash: forbid recursion */
10888     endingGame = 1;
10889     if(twoBoards) { // [HGM] dual: switch back to one board
10890         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10891         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10892     }
10893     if (appData.debugMode) {
10894       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10895               result, resultDetails ? resultDetails : "(null)", whosays);
10896     }
10897
10898     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10899
10900     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10901
10902     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10903         /* If we are playing on ICS, the server decides when the
10904            game is over, but the engine can offer to draw, claim
10905            a draw, or resign.
10906          */
10907 #if ZIPPY
10908         if (appData.zippyPlay && first.initDone) {
10909             if (result == GameIsDrawn) {
10910                 /* In case draw still needs to be claimed */
10911                 SendToICS(ics_prefix);
10912                 SendToICS("draw\n");
10913             } else if (StrCaseStr(resultDetails, "resign")) {
10914                 SendToICS(ics_prefix);
10915                 SendToICS("resign\n");
10916             }
10917         }
10918 #endif
10919         endingGame = 0; /* [HGM] crash */
10920         return;
10921     }
10922
10923     /* If we're loading the game from a file, stop */
10924     if (whosays == GE_FILE) {
10925       (void) StopLoadGameTimer();
10926       gameFileFP = NULL;
10927     }
10928
10929     /* Cancel draw offers */
10930     first.offeredDraw = second.offeredDraw = 0;
10931
10932     /* If this is an ICS game, only ICS can really say it's done;
10933        if not, anyone can. */
10934     isIcsGame = (gameMode == IcsPlayingWhite ||
10935                  gameMode == IcsPlayingBlack ||
10936                  gameMode == IcsObserving    ||
10937                  gameMode == IcsExamining);
10938
10939     if (!isIcsGame || whosays == GE_ICS) {
10940         /* OK -- not an ICS game, or ICS said it was done */
10941         StopClocks();
10942         if (!isIcsGame && !appData.noChessProgram)
10943           SetUserThinkingEnables();
10944
10945         /* [HGM] if a machine claims the game end we verify this claim */
10946         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10947             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10948                 char claimer;
10949                 ChessMove trueResult = (ChessMove) -1;
10950
10951                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10952                                             first.twoMachinesColor[0] :
10953                                             second.twoMachinesColor[0] ;
10954
10955                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10956                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10957                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10958                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10959                 } else
10960                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10961                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10962                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10963                 } else
10964                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10965                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10966                 }
10967
10968                 // now verify win claims, but not in drop games, as we don't understand those yet
10969                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10970                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10971                     (result == WhiteWins && claimer == 'w' ||
10972                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10973                       if (appData.debugMode) {
10974                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10975                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10976                       }
10977                       if(result != trueResult) {
10978                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10979                               result = claimer == 'w' ? BlackWins : WhiteWins;
10980                               resultDetails = buf;
10981                       }
10982                 } else
10983                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10984                     && (forwardMostMove <= backwardMostMove ||
10985                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10986                         (claimer=='b')==(forwardMostMove&1))
10987                                                                                   ) {
10988                       /* [HGM] verify: draws that were not flagged are false claims */
10989                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10990                       result = claimer == 'w' ? BlackWins : WhiteWins;
10991                       resultDetails = buf;
10992                 }
10993                 /* (Claiming a loss is accepted no questions asked!) */
10994             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10995                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10996                 result = GameUnfinished;
10997                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10998             }
10999             /* [HGM] bare: don't allow bare King to win */
11000             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11001                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11002                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11003                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11004                && result != GameIsDrawn)
11005             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11006                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11007                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11008                         if(p >= 0 && p <= (int)WhiteKing) k++;
11009                 }
11010                 if (appData.debugMode) {
11011                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11012                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11013                 }
11014                 if(k <= 1) {
11015                         result = GameIsDrawn;
11016                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11017                         resultDetails = buf;
11018                 }
11019             }
11020         }
11021
11022
11023         if(serverMoves != NULL && !loadFlag) { char c = '=';
11024             if(result==WhiteWins) c = '+';
11025             if(result==BlackWins) c = '-';
11026             if(resultDetails != NULL)
11027                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11028         }
11029         if (resultDetails != NULL) {
11030             gameInfo.result = result;
11031             gameInfo.resultDetails = StrSave(resultDetails);
11032
11033             /* display last move only if game was not loaded from file */
11034             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11035                 DisplayMove(currentMove - 1);
11036
11037             if (forwardMostMove != 0) {
11038                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11039                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11040                                                                 ) {
11041                     if (*appData.saveGameFile != NULLCHAR) {
11042                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11043                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11044                         else
11045                         SaveGameToFile(appData.saveGameFile, TRUE);
11046                     } else if (appData.autoSaveGames) {
11047                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11048                     }
11049                     if (*appData.savePositionFile != NULLCHAR) {
11050                         SavePositionToFile(appData.savePositionFile);
11051                     }
11052                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11053                 }
11054             }
11055
11056             /* Tell program how game ended in case it is learning */
11057             /* [HGM] Moved this to after saving the PGN, just in case */
11058             /* engine died and we got here through time loss. In that */
11059             /* case we will get a fatal error writing the pipe, which */
11060             /* would otherwise lose us the PGN.                       */
11061             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11062             /* output during GameEnds should never be fatal anymore   */
11063             if (gameMode == MachinePlaysWhite ||
11064                 gameMode == MachinePlaysBlack ||
11065                 gameMode == TwoMachinesPlay ||
11066                 gameMode == IcsPlayingWhite ||
11067                 gameMode == IcsPlayingBlack ||
11068                 gameMode == BeginningOfGame) {
11069                 char buf[MSG_SIZ];
11070                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11071                         resultDetails);
11072                 if (first.pr != NoProc) {
11073                     SendToProgram(buf, &first);
11074                 }
11075                 if (second.pr != NoProc &&
11076                     gameMode == TwoMachinesPlay) {
11077                     SendToProgram(buf, &second);
11078                 }
11079             }
11080         }
11081
11082         if (appData.icsActive) {
11083             if (appData.quietPlay &&
11084                 (gameMode == IcsPlayingWhite ||
11085                  gameMode == IcsPlayingBlack)) {
11086                 SendToICS(ics_prefix);
11087                 SendToICS("set shout 1\n");
11088             }
11089             nextGameMode = IcsIdle;
11090             ics_user_moved = FALSE;
11091             /* clean up premove.  It's ugly when the game has ended and the
11092              * premove highlights are still on the board.
11093              */
11094             if (gotPremove) {
11095               gotPremove = FALSE;
11096               ClearPremoveHighlights();
11097               DrawPosition(FALSE, boards[currentMove]);
11098             }
11099             if (whosays == GE_ICS) {
11100                 switch (result) {
11101                 case WhiteWins:
11102                     if (gameMode == IcsPlayingWhite)
11103                         PlayIcsWinSound();
11104                     else if(gameMode == IcsPlayingBlack)
11105                         PlayIcsLossSound();
11106                     break;
11107                 case BlackWins:
11108                     if (gameMode == IcsPlayingBlack)
11109                         PlayIcsWinSound();
11110                     else if(gameMode == IcsPlayingWhite)
11111                         PlayIcsLossSound();
11112                     break;
11113                 case GameIsDrawn:
11114                     PlayIcsDrawSound();
11115                     break;
11116                 default:
11117                     PlayIcsUnfinishedSound();
11118                 }
11119             }
11120             if(appData.quitNext) { ExitEvent(0); return; }
11121         } else if (gameMode == EditGame ||
11122                    gameMode == PlayFromGameFile ||
11123                    gameMode == AnalyzeMode ||
11124                    gameMode == AnalyzeFile) {
11125             nextGameMode = gameMode;
11126         } else {
11127             nextGameMode = EndOfGame;
11128         }
11129         pausing = FALSE;
11130         ModeHighlight();
11131     } else {
11132         nextGameMode = gameMode;
11133     }
11134
11135     if (appData.noChessProgram) {
11136         gameMode = nextGameMode;
11137         ModeHighlight();
11138         endingGame = 0; /* [HGM] crash */
11139         return;
11140     }
11141
11142     if (first.reuse) {
11143         /* Put first chess program into idle state */
11144         if (first.pr != NoProc &&
11145             (gameMode == MachinePlaysWhite ||
11146              gameMode == MachinePlaysBlack ||
11147              gameMode == TwoMachinesPlay ||
11148              gameMode == IcsPlayingWhite ||
11149              gameMode == IcsPlayingBlack ||
11150              gameMode == BeginningOfGame)) {
11151             SendToProgram("force\n", &first);
11152             if (first.usePing) {
11153               char buf[MSG_SIZ];
11154               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11155               SendToProgram(buf, &first);
11156             }
11157         }
11158     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11159         /* Kill off first chess program */
11160         if (first.isr != NULL)
11161           RemoveInputSource(first.isr);
11162         first.isr = NULL;
11163
11164         if (first.pr != NoProc) {
11165             ExitAnalyzeMode();
11166             DoSleep( appData.delayBeforeQuit );
11167             SendToProgram("quit\n", &first);
11168             DoSleep( appData.delayAfterQuit );
11169             DestroyChildProcess(first.pr, first.useSigterm);
11170             first.reload = TRUE;
11171         }
11172         first.pr = NoProc;
11173     }
11174     if (second.reuse) {
11175         /* Put second chess program into idle state */
11176         if (second.pr != NoProc &&
11177             gameMode == TwoMachinesPlay) {
11178             SendToProgram("force\n", &second);
11179             if (second.usePing) {
11180               char buf[MSG_SIZ];
11181               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11182               SendToProgram(buf, &second);
11183             }
11184         }
11185     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11186         /* Kill off second chess program */
11187         if (second.isr != NULL)
11188           RemoveInputSource(second.isr);
11189         second.isr = NULL;
11190
11191         if (second.pr != NoProc) {
11192             DoSleep( appData.delayBeforeQuit );
11193             SendToProgram("quit\n", &second);
11194             DoSleep( appData.delayAfterQuit );
11195             DestroyChildProcess(second.pr, second.useSigterm);
11196             second.reload = TRUE;
11197         }
11198         second.pr = NoProc;
11199     }
11200
11201     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11202         char resChar = '=';
11203         switch (result) {
11204         case WhiteWins:
11205           resChar = '+';
11206           if (first.twoMachinesColor[0] == 'w') {
11207             first.matchWins++;
11208           } else {
11209             second.matchWins++;
11210           }
11211           break;
11212         case BlackWins:
11213           resChar = '-';
11214           if (first.twoMachinesColor[0] == 'b') {
11215             first.matchWins++;
11216           } else {
11217             second.matchWins++;
11218           }
11219           break;
11220         case GameUnfinished:
11221           resChar = ' ';
11222         default:
11223           break;
11224         }
11225
11226         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11227         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11228             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11229             ReserveGame(nextGame, resChar); // sets nextGame
11230             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11231             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11232         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11233
11234         if (nextGame <= appData.matchGames && !abortMatch) {
11235             gameMode = nextGameMode;
11236             matchGame = nextGame; // this will be overruled in tourney mode!
11237             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11238             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11239             endingGame = 0; /* [HGM] crash */
11240             return;
11241         } else {
11242             gameMode = nextGameMode;
11243             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11244                      first.tidy, second.tidy,
11245                      first.matchWins, second.matchWins,
11246                      appData.matchGames - (first.matchWins + second.matchWins));
11247             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11248             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11249             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11250             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11251                 first.twoMachinesColor = "black\n";
11252                 second.twoMachinesColor = "white\n";
11253             } else {
11254                 first.twoMachinesColor = "white\n";
11255                 second.twoMachinesColor = "black\n";
11256             }
11257         }
11258     }
11259     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11260         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11261       ExitAnalyzeMode();
11262     gameMode = nextGameMode;
11263     ModeHighlight();
11264     endingGame = 0;  /* [HGM] crash */
11265     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11266         if(matchMode == TRUE) { // match through command line: exit with or without popup
11267             if(ranking) {
11268                 ToNrEvent(forwardMostMove);
11269                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11270                 else ExitEvent(0);
11271             } else DisplayFatalError(buf, 0, 0);
11272         } else { // match through menu; just stop, with or without popup
11273             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11274             ModeHighlight();
11275             if(ranking){
11276                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11277             } else DisplayNote(buf);
11278       }
11279       if(ranking) free(ranking);
11280     }
11281 }
11282
11283 /* Assumes program was just initialized (initString sent).
11284    Leaves program in force mode. */
11285 void
11286 FeedMovesToProgram (ChessProgramState *cps, int upto)
11287 {
11288     int i;
11289
11290     if (appData.debugMode)
11291       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11292               startedFromSetupPosition ? "position and " : "",
11293               backwardMostMove, upto, cps->which);
11294     if(currentlyInitializedVariant != gameInfo.variant) {
11295       char buf[MSG_SIZ];
11296         // [HGM] variantswitch: make engine aware of new variant
11297         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11298                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11299         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11300         SendToProgram(buf, cps);
11301         currentlyInitializedVariant = gameInfo.variant;
11302     }
11303     SendToProgram("force\n", cps);
11304     if (startedFromSetupPosition) {
11305         SendBoard(cps, backwardMostMove);
11306     if (appData.debugMode) {
11307         fprintf(debugFP, "feedMoves\n");
11308     }
11309     }
11310     for (i = backwardMostMove; i < upto; i++) {
11311         SendMoveToProgram(i, cps);
11312     }
11313 }
11314
11315
11316 int
11317 ResurrectChessProgram ()
11318 {
11319      /* The chess program may have exited.
11320         If so, restart it and feed it all the moves made so far. */
11321     static int doInit = 0;
11322
11323     if (appData.noChessProgram) return 1;
11324
11325     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11326         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11327         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11328         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11329     } else {
11330         if (first.pr != NoProc) return 1;
11331         StartChessProgram(&first);
11332     }
11333     InitChessProgram(&first, FALSE);
11334     FeedMovesToProgram(&first, currentMove);
11335
11336     if (!first.sendTime) {
11337         /* can't tell gnuchess what its clock should read,
11338            so we bow to its notion. */
11339         ResetClocks();
11340         timeRemaining[0][currentMove] = whiteTimeRemaining;
11341         timeRemaining[1][currentMove] = blackTimeRemaining;
11342     }
11343
11344     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11345                 appData.icsEngineAnalyze) && first.analysisSupport) {
11346       SendToProgram("analyze\n", &first);
11347       first.analyzing = TRUE;
11348     }
11349     return 1;
11350 }
11351
11352 /*
11353  * Button procedures
11354  */
11355 void
11356 Reset (int redraw, int init)
11357 {
11358     int i;
11359
11360     if (appData.debugMode) {
11361         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11362                 redraw, init, gameMode);
11363     }
11364     CleanupTail(); // [HGM] vari: delete any stored variations
11365     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11366     pausing = pauseExamInvalid = FALSE;
11367     startedFromSetupPosition = blackPlaysFirst = FALSE;
11368     firstMove = TRUE;
11369     whiteFlag = blackFlag = FALSE;
11370     userOfferedDraw = FALSE;
11371     hintRequested = bookRequested = FALSE;
11372     first.maybeThinking = FALSE;
11373     second.maybeThinking = FALSE;
11374     first.bookSuspend = FALSE; // [HGM] book
11375     second.bookSuspend = FALSE;
11376     thinkOutput[0] = NULLCHAR;
11377     lastHint[0] = NULLCHAR;
11378     ClearGameInfo(&gameInfo);
11379     gameInfo.variant = StringToVariant(appData.variant);
11380     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11381     ics_user_moved = ics_clock_paused = FALSE;
11382     ics_getting_history = H_FALSE;
11383     ics_gamenum = -1;
11384     white_holding[0] = black_holding[0] = NULLCHAR;
11385     ClearProgramStats();
11386     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11387
11388     ResetFrontEnd();
11389     ClearHighlights();
11390     flipView = appData.flipView;
11391     ClearPremoveHighlights();
11392     gotPremove = FALSE;
11393     alarmSounded = FALSE;
11394
11395     GameEnds(EndOfFile, NULL, GE_PLAYER);
11396     if(appData.serverMovesName != NULL) {
11397         /* [HGM] prepare to make moves file for broadcasting */
11398         clock_t t = clock();
11399         if(serverMoves != NULL) fclose(serverMoves);
11400         serverMoves = fopen(appData.serverMovesName, "r");
11401         if(serverMoves != NULL) {
11402             fclose(serverMoves);
11403             /* delay 15 sec before overwriting, so all clients can see end */
11404             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11405         }
11406         serverMoves = fopen(appData.serverMovesName, "w");
11407     }
11408
11409     ExitAnalyzeMode();
11410     gameMode = BeginningOfGame;
11411     ModeHighlight();
11412     if(appData.icsActive) gameInfo.variant = VariantNormal;
11413     currentMove = forwardMostMove = backwardMostMove = 0;
11414     MarkTargetSquares(1);
11415     InitPosition(redraw);
11416     for (i = 0; i < MAX_MOVES; i++) {
11417         if (commentList[i] != NULL) {
11418             free(commentList[i]);
11419             commentList[i] = NULL;
11420         }
11421     }
11422     ResetClocks();
11423     timeRemaining[0][0] = whiteTimeRemaining;
11424     timeRemaining[1][0] = blackTimeRemaining;
11425
11426     if (first.pr == NoProc) {
11427         StartChessProgram(&first);
11428     }
11429     if (init) {
11430             InitChessProgram(&first, startedFromSetupPosition);
11431     }
11432     DisplayTitle("");
11433     DisplayMessage("", "");
11434     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11435     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11436     ClearMap();        // [HGM] exclude: invalidate map
11437 }
11438
11439 void
11440 AutoPlayGameLoop ()
11441 {
11442     for (;;) {
11443         if (!AutoPlayOneMove())
11444           return;
11445         if (matchMode || appData.timeDelay == 0)
11446           continue;
11447         if (appData.timeDelay < 0)
11448           return;
11449         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11450         break;
11451     }
11452 }
11453
11454 void
11455 AnalyzeNextGame()
11456 {
11457     ReloadGame(1); // next game
11458 }
11459
11460 int
11461 AutoPlayOneMove ()
11462 {
11463     int fromX, fromY, toX, toY;
11464
11465     if (appData.debugMode) {
11466       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11467     }
11468
11469     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11470       return FALSE;
11471
11472     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11473       pvInfoList[currentMove].depth = programStats.depth;
11474       pvInfoList[currentMove].score = programStats.score;
11475       pvInfoList[currentMove].time  = 0;
11476       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11477       else { // append analysis of final position as comment
11478         char buf[MSG_SIZ];
11479         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11480         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11481       }
11482       programStats.depth = 0;
11483     }
11484
11485     if (currentMove >= forwardMostMove) {
11486       if(gameMode == AnalyzeFile) {
11487           if(appData.loadGameIndex == -1) {
11488             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11489           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11490           } else {
11491           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11492         }
11493       }
11494 //      gameMode = EndOfGame;
11495 //      ModeHighlight();
11496
11497       /* [AS] Clear current move marker at the end of a game */
11498       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11499
11500       return FALSE;
11501     }
11502
11503     toX = moveList[currentMove][2] - AAA;
11504     toY = moveList[currentMove][3] - ONE;
11505
11506     if (moveList[currentMove][1] == '@') {
11507         if (appData.highlightLastMove) {
11508             SetHighlights(-1, -1, toX, toY);
11509         }
11510     } else {
11511         fromX = moveList[currentMove][0] - AAA;
11512         fromY = moveList[currentMove][1] - ONE;
11513
11514         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11515
11516         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11517
11518         if (appData.highlightLastMove) {
11519             SetHighlights(fromX, fromY, toX, toY);
11520         }
11521     }
11522     DisplayMove(currentMove);
11523     SendMoveToProgram(currentMove++, &first);
11524     DisplayBothClocks();
11525     DrawPosition(FALSE, boards[currentMove]);
11526     // [HGM] PV info: always display, routine tests if empty
11527     DisplayComment(currentMove - 1, commentList[currentMove]);
11528     return TRUE;
11529 }
11530
11531
11532 int
11533 LoadGameOneMove (ChessMove readAhead)
11534 {
11535     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11536     char promoChar = NULLCHAR;
11537     ChessMove moveType;
11538     char move[MSG_SIZ];
11539     char *p, *q;
11540
11541     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11542         gameMode != AnalyzeMode && gameMode != Training) {
11543         gameFileFP = NULL;
11544         return FALSE;
11545     }
11546
11547     yyboardindex = forwardMostMove;
11548     if (readAhead != EndOfFile) {
11549       moveType = readAhead;
11550     } else {
11551       if (gameFileFP == NULL)
11552           return FALSE;
11553       moveType = (ChessMove) Myylex();
11554     }
11555
11556     done = FALSE;
11557     switch (moveType) {
11558       case Comment:
11559         if (appData.debugMode)
11560           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11561         p = yy_text;
11562
11563         /* append the comment but don't display it */
11564         AppendComment(currentMove, p, FALSE);
11565         return TRUE;
11566
11567       case WhiteCapturesEnPassant:
11568       case BlackCapturesEnPassant:
11569       case WhitePromotion:
11570       case BlackPromotion:
11571       case WhiteNonPromotion:
11572       case BlackNonPromotion:
11573       case NormalMove:
11574       case WhiteKingSideCastle:
11575       case WhiteQueenSideCastle:
11576       case BlackKingSideCastle:
11577       case BlackQueenSideCastle:
11578       case WhiteKingSideCastleWild:
11579       case WhiteQueenSideCastleWild:
11580       case BlackKingSideCastleWild:
11581       case BlackQueenSideCastleWild:
11582       /* PUSH Fabien */
11583       case WhiteHSideCastleFR:
11584       case WhiteASideCastleFR:
11585       case BlackHSideCastleFR:
11586       case BlackASideCastleFR:
11587       /* POP Fabien */
11588         if (appData.debugMode)
11589           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11590         fromX = currentMoveString[0] - AAA;
11591         fromY = currentMoveString[1] - ONE;
11592         toX = currentMoveString[2] - AAA;
11593         toY = currentMoveString[3] - ONE;
11594         promoChar = currentMoveString[4];
11595         break;
11596
11597       case WhiteDrop:
11598       case BlackDrop:
11599         if (appData.debugMode)
11600           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11601         fromX = moveType == WhiteDrop ?
11602           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11603         (int) CharToPiece(ToLower(currentMoveString[0]));
11604         fromY = DROP_RANK;
11605         toX = currentMoveString[2] - AAA;
11606         toY = currentMoveString[3] - ONE;
11607         break;
11608
11609       case WhiteWins:
11610       case BlackWins:
11611       case GameIsDrawn:
11612       case GameUnfinished:
11613         if (appData.debugMode)
11614           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11615         p = strchr(yy_text, '{');
11616         if (p == NULL) p = strchr(yy_text, '(');
11617         if (p == NULL) {
11618             p = yy_text;
11619             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11620         } else {
11621             q = strchr(p, *p == '{' ? '}' : ')');
11622             if (q != NULL) *q = NULLCHAR;
11623             p++;
11624         }
11625         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11626         GameEnds(moveType, p, GE_FILE);
11627         done = TRUE;
11628         if (cmailMsgLoaded) {
11629             ClearHighlights();
11630             flipView = WhiteOnMove(currentMove);
11631             if (moveType == GameUnfinished) flipView = !flipView;
11632             if (appData.debugMode)
11633               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11634         }
11635         break;
11636
11637       case EndOfFile:
11638         if (appData.debugMode)
11639           fprintf(debugFP, "Parser hit end of file\n");
11640         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11641           case MT_NONE:
11642           case MT_CHECK:
11643             break;
11644           case MT_CHECKMATE:
11645           case MT_STAINMATE:
11646             if (WhiteOnMove(currentMove)) {
11647                 GameEnds(BlackWins, "Black mates", GE_FILE);
11648             } else {
11649                 GameEnds(WhiteWins, "White mates", GE_FILE);
11650             }
11651             break;
11652           case MT_STALEMATE:
11653             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11654             break;
11655         }
11656         done = TRUE;
11657         break;
11658
11659       case MoveNumberOne:
11660         if (lastLoadGameStart == GNUChessGame) {
11661             /* GNUChessGames have numbers, but they aren't move numbers */
11662             if (appData.debugMode)
11663               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11664                       yy_text, (int) moveType);
11665             return LoadGameOneMove(EndOfFile); /* tail recursion */
11666         }
11667         /* else fall thru */
11668
11669       case XBoardGame:
11670       case GNUChessGame:
11671       case PGNTag:
11672         /* Reached start of next game in file */
11673         if (appData.debugMode)
11674           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11675         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11676           case MT_NONE:
11677           case MT_CHECK:
11678             break;
11679           case MT_CHECKMATE:
11680           case MT_STAINMATE:
11681             if (WhiteOnMove(currentMove)) {
11682                 GameEnds(BlackWins, "Black mates", GE_FILE);
11683             } else {
11684                 GameEnds(WhiteWins, "White mates", GE_FILE);
11685             }
11686             break;
11687           case MT_STALEMATE:
11688             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11689             break;
11690         }
11691         done = TRUE;
11692         break;
11693
11694       case PositionDiagram:     /* should not happen; ignore */
11695       case ElapsedTime:         /* ignore */
11696       case NAG:                 /* ignore */
11697         if (appData.debugMode)
11698           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11699                   yy_text, (int) moveType);
11700         return LoadGameOneMove(EndOfFile); /* tail recursion */
11701
11702       case IllegalMove:
11703         if (appData.testLegality) {
11704             if (appData.debugMode)
11705               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11706             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11707                     (forwardMostMove / 2) + 1,
11708                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11709             DisplayError(move, 0);
11710             done = TRUE;
11711         } else {
11712             if (appData.debugMode)
11713               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11714                       yy_text, currentMoveString);
11715             fromX = currentMoveString[0] - AAA;
11716             fromY = currentMoveString[1] - ONE;
11717             toX = currentMoveString[2] - AAA;
11718             toY = currentMoveString[3] - ONE;
11719             promoChar = currentMoveString[4];
11720         }
11721         break;
11722
11723       case AmbiguousMove:
11724         if (appData.debugMode)
11725           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11726         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11727                 (forwardMostMove / 2) + 1,
11728                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11729         DisplayError(move, 0);
11730         done = TRUE;
11731         break;
11732
11733       default:
11734       case ImpossibleMove:
11735         if (appData.debugMode)
11736           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11737         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11738                 (forwardMostMove / 2) + 1,
11739                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11740         DisplayError(move, 0);
11741         done = TRUE;
11742         break;
11743     }
11744
11745     if (done) {
11746         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11747             DrawPosition(FALSE, boards[currentMove]);
11748             DisplayBothClocks();
11749             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11750               DisplayComment(currentMove - 1, commentList[currentMove]);
11751         }
11752         (void) StopLoadGameTimer();
11753         gameFileFP = NULL;
11754         cmailOldMove = forwardMostMove;
11755         return FALSE;
11756     } else {
11757         /* currentMoveString is set as a side-effect of yylex */
11758
11759         thinkOutput[0] = NULLCHAR;
11760         MakeMove(fromX, fromY, toX, toY, promoChar);
11761         currentMove = forwardMostMove;
11762         return TRUE;
11763     }
11764 }
11765
11766 /* Load the nth game from the given file */
11767 int
11768 LoadGameFromFile (char *filename, int n, char *title, int useList)
11769 {
11770     FILE *f;
11771     char buf[MSG_SIZ];
11772
11773     if (strcmp(filename, "-") == 0) {
11774         f = stdin;
11775         title = "stdin";
11776     } else {
11777         f = fopen(filename, "rb");
11778         if (f == NULL) {
11779           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11780             DisplayError(buf, errno);
11781             return FALSE;
11782         }
11783     }
11784     if (fseek(f, 0, 0) == -1) {
11785         /* f is not seekable; probably a pipe */
11786         useList = FALSE;
11787     }
11788     if (useList && n == 0) {
11789         int error = GameListBuild(f);
11790         if (error) {
11791             DisplayError(_("Cannot build game list"), error);
11792         } else if (!ListEmpty(&gameList) &&
11793                    ((ListGame *) gameList.tailPred)->number > 1) {
11794             GameListPopUp(f, title);
11795             return TRUE;
11796         }
11797         GameListDestroy();
11798         n = 1;
11799     }
11800     if (n == 0) n = 1;
11801     return LoadGame(f, n, title, FALSE);
11802 }
11803
11804
11805 void
11806 MakeRegisteredMove ()
11807 {
11808     int fromX, fromY, toX, toY;
11809     char promoChar;
11810     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11811         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11812           case CMAIL_MOVE:
11813           case CMAIL_DRAW:
11814             if (appData.debugMode)
11815               fprintf(debugFP, "Restoring %s for game %d\n",
11816                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11817
11818             thinkOutput[0] = NULLCHAR;
11819             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11820             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11821             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11822             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11823             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11824             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11825             MakeMove(fromX, fromY, toX, toY, promoChar);
11826             ShowMove(fromX, fromY, toX, toY);
11827
11828             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11829               case MT_NONE:
11830               case MT_CHECK:
11831                 break;
11832
11833               case MT_CHECKMATE:
11834               case MT_STAINMATE:
11835                 if (WhiteOnMove(currentMove)) {
11836                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11837                 } else {
11838                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11839                 }
11840                 break;
11841
11842               case MT_STALEMATE:
11843                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11844                 break;
11845             }
11846
11847             break;
11848
11849           case CMAIL_RESIGN:
11850             if (WhiteOnMove(currentMove)) {
11851                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11852             } else {
11853                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11854             }
11855             break;
11856
11857           case CMAIL_ACCEPT:
11858             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11859             break;
11860
11861           default:
11862             break;
11863         }
11864     }
11865
11866     return;
11867 }
11868
11869 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11870 int
11871 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11872 {
11873     int retVal;
11874
11875     if (gameNumber > nCmailGames) {
11876         DisplayError(_("No more games in this message"), 0);
11877         return FALSE;
11878     }
11879     if (f == lastLoadGameFP) {
11880         int offset = gameNumber - lastLoadGameNumber;
11881         if (offset == 0) {
11882             cmailMsg[0] = NULLCHAR;
11883             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11884                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11885                 nCmailMovesRegistered--;
11886             }
11887             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11888             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11889                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11890             }
11891         } else {
11892             if (! RegisterMove()) return FALSE;
11893         }
11894     }
11895
11896     retVal = LoadGame(f, gameNumber, title, useList);
11897
11898     /* Make move registered during previous look at this game, if any */
11899     MakeRegisteredMove();
11900
11901     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11902         commentList[currentMove]
11903           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11904         DisplayComment(currentMove - 1, commentList[currentMove]);
11905     }
11906
11907     return retVal;
11908 }
11909
11910 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11911 int
11912 ReloadGame (int offset)
11913 {
11914     int gameNumber = lastLoadGameNumber + offset;
11915     if (lastLoadGameFP == NULL) {
11916         DisplayError(_("No game has been loaded yet"), 0);
11917         return FALSE;
11918     }
11919     if (gameNumber <= 0) {
11920         DisplayError(_("Can't back up any further"), 0);
11921         return FALSE;
11922     }
11923     if (cmailMsgLoaded) {
11924         return CmailLoadGame(lastLoadGameFP, gameNumber,
11925                              lastLoadGameTitle, lastLoadGameUseList);
11926     } else {
11927         return LoadGame(lastLoadGameFP, gameNumber,
11928                         lastLoadGameTitle, lastLoadGameUseList);
11929     }
11930 }
11931
11932 int keys[EmptySquare+1];
11933
11934 int
11935 PositionMatches (Board b1, Board b2)
11936 {
11937     int r, f, sum=0;
11938     switch(appData.searchMode) {
11939         case 1: return CompareWithRights(b1, b2);
11940         case 2:
11941             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11942                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11943             }
11944             return TRUE;
11945         case 3:
11946             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11947               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11948                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11949             }
11950             return sum==0;
11951         case 4:
11952             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11953                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11954             }
11955             return sum==0;
11956     }
11957     return TRUE;
11958 }
11959
11960 #define Q_PROMO  4
11961 #define Q_EP     3
11962 #define Q_BCASTL 2
11963 #define Q_WCASTL 1
11964
11965 int pieceList[256], quickBoard[256];
11966 ChessSquare pieceType[256] = { EmptySquare };
11967 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11968 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11969 int soughtTotal, turn;
11970 Boolean epOK, flipSearch;
11971
11972 typedef struct {
11973     unsigned char piece, to;
11974 } Move;
11975
11976 #define DSIZE (250000)
11977
11978 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11979 Move *moveDatabase = initialSpace;
11980 unsigned int movePtr, dataSize = DSIZE;
11981
11982 int
11983 MakePieceList (Board board, int *counts)
11984 {
11985     int r, f, n=Q_PROMO, total=0;
11986     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11987     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11988         int sq = f + (r<<4);
11989         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11990             quickBoard[sq] = ++n;
11991             pieceList[n] = sq;
11992             pieceType[n] = board[r][f];
11993             counts[board[r][f]]++;
11994             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11995             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11996             total++;
11997         }
11998     }
11999     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12000     return total;
12001 }
12002
12003 void
12004 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12005 {
12006     int sq = fromX + (fromY<<4);
12007     int piece = quickBoard[sq];
12008     quickBoard[sq] = 0;
12009     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12010     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12011         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12012         moveDatabase[movePtr++].piece = Q_WCASTL;
12013         quickBoard[sq] = piece;
12014         piece = quickBoard[from]; quickBoard[from] = 0;
12015         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12016     } else
12017     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12018         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12019         moveDatabase[movePtr++].piece = Q_BCASTL;
12020         quickBoard[sq] = piece;
12021         piece = quickBoard[from]; quickBoard[from] = 0;
12022         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12023     } else
12024     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12025         quickBoard[(fromY<<4)+toX] = 0;
12026         moveDatabase[movePtr].piece = Q_EP;
12027         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12028         moveDatabase[movePtr].to = sq;
12029     } else
12030     if(promoPiece != pieceType[piece]) {
12031         moveDatabase[movePtr++].piece = Q_PROMO;
12032         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12033     }
12034     moveDatabase[movePtr].piece = piece;
12035     quickBoard[sq] = piece;
12036     movePtr++;
12037 }
12038
12039 int
12040 PackGame (Board board)
12041 {
12042     Move *newSpace = NULL;
12043     moveDatabase[movePtr].piece = 0; // terminate previous game
12044     if(movePtr > dataSize) {
12045         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12046         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12047         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12048         if(newSpace) {
12049             int i;
12050             Move *p = moveDatabase, *q = newSpace;
12051             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12052             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12053             moveDatabase = newSpace;
12054         } else { // calloc failed, we must be out of memory. Too bad...
12055             dataSize = 0; // prevent calloc events for all subsequent games
12056             return 0;     // and signal this one isn't cached
12057         }
12058     }
12059     movePtr++;
12060     MakePieceList(board, counts);
12061     return movePtr;
12062 }
12063
12064 int
12065 QuickCompare (Board board, int *minCounts, int *maxCounts)
12066 {   // compare according to search mode
12067     int r, f;
12068     switch(appData.searchMode)
12069     {
12070       case 1: // exact position match
12071         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12072         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12073             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12074         }
12075         break;
12076       case 2: // can have extra material on empty squares
12077         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12078             if(board[r][f] == EmptySquare) continue;
12079             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12080         }
12081         break;
12082       case 3: // material with exact Pawn structure
12083         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12084             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12085             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12086         } // fall through to material comparison
12087       case 4: // exact material
12088         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12089         break;
12090       case 6: // material range with given imbalance
12091         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12092         // fall through to range comparison
12093       case 5: // material range
12094         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12095     }
12096     return TRUE;
12097 }
12098
12099 int
12100 QuickScan (Board board, Move *move)
12101 {   // reconstruct game,and compare all positions in it
12102     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12103     do {
12104         int piece = move->piece;
12105         int to = move->to, from = pieceList[piece];
12106         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12107           if(!piece) return -1;
12108           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12109             piece = (++move)->piece;
12110             from = pieceList[piece];
12111             counts[pieceType[piece]]--;
12112             pieceType[piece] = (ChessSquare) move->to;
12113             counts[move->to]++;
12114           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12115             counts[pieceType[quickBoard[to]]]--;
12116             quickBoard[to] = 0; total--;
12117             move++;
12118             continue;
12119           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12120             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12121             from  = pieceList[piece]; // so this must be King
12122             quickBoard[from] = 0;
12123             pieceList[piece] = to;
12124             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12125             quickBoard[from] = 0; // rook
12126             quickBoard[to] = piece;
12127             to = move->to; piece = move->piece;
12128             goto aftercastle;
12129           }
12130         }
12131         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12132         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12133         quickBoard[from] = 0;
12134       aftercastle:
12135         quickBoard[to] = piece;
12136         pieceList[piece] = to;
12137         cnt++; turn ^= 3;
12138         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12139            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12140            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12141                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12142           ) {
12143             static int lastCounts[EmptySquare+1];
12144             int i;
12145             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12146             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12147         } else stretch = 0;
12148         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12149         move++;
12150     } while(1);
12151 }
12152
12153 void
12154 InitSearch ()
12155 {
12156     int r, f;
12157     flipSearch = FALSE;
12158     CopyBoard(soughtBoard, boards[currentMove]);
12159     soughtTotal = MakePieceList(soughtBoard, maxSought);
12160     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12161     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12162     CopyBoard(reverseBoard, boards[currentMove]);
12163     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12164         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12165         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12166         reverseBoard[r][f] = piece;
12167     }
12168     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12169     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12170     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12171                  || (boards[currentMove][CASTLING][2] == NoRights ||
12172                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12173                  && (boards[currentMove][CASTLING][5] == NoRights ||
12174                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12175       ) {
12176         flipSearch = TRUE;
12177         CopyBoard(flipBoard, soughtBoard);
12178         CopyBoard(rotateBoard, reverseBoard);
12179         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12180             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12181             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12182         }
12183     }
12184     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12185     if(appData.searchMode >= 5) {
12186         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12187         MakePieceList(soughtBoard, minSought);
12188         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12189     }
12190     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12191         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12192 }
12193
12194 GameInfo dummyInfo;
12195 static int creatingBook;
12196
12197 int
12198 GameContainsPosition (FILE *f, ListGame *lg)
12199 {
12200     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12201     int fromX, fromY, toX, toY;
12202     char promoChar;
12203     static int initDone=FALSE;
12204
12205     // weed out games based on numerical tag comparison
12206     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12207     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12208     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12209     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12210     if(!initDone) {
12211         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12212         initDone = TRUE;
12213     }
12214     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12215     else CopyBoard(boards[scratch], initialPosition); // default start position
12216     if(lg->moves) {
12217         turn = btm + 1;
12218         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12219         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12220     }
12221     if(btm) plyNr++;
12222     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12223     fseek(f, lg->offset, 0);
12224     yynewfile(f);
12225     while(1) {
12226         yyboardindex = scratch;
12227         quickFlag = plyNr+1;
12228         next = Myylex();
12229         quickFlag = 0;
12230         switch(next) {
12231             case PGNTag:
12232                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12233             default:
12234                 continue;
12235
12236             case XBoardGame:
12237             case GNUChessGame:
12238                 if(plyNr) return -1; // after we have seen moves, this is for new game
12239               continue;
12240
12241             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12242             case ImpossibleMove:
12243             case WhiteWins: // game ends here with these four
12244             case BlackWins:
12245             case GameIsDrawn:
12246             case GameUnfinished:
12247                 return -1;
12248
12249             case IllegalMove:
12250                 if(appData.testLegality) return -1;
12251             case WhiteCapturesEnPassant:
12252             case BlackCapturesEnPassant:
12253             case WhitePromotion:
12254             case BlackPromotion:
12255             case WhiteNonPromotion:
12256             case BlackNonPromotion:
12257             case NormalMove:
12258             case WhiteKingSideCastle:
12259             case WhiteQueenSideCastle:
12260             case BlackKingSideCastle:
12261             case BlackQueenSideCastle:
12262             case WhiteKingSideCastleWild:
12263             case WhiteQueenSideCastleWild:
12264             case BlackKingSideCastleWild:
12265             case BlackQueenSideCastleWild:
12266             case WhiteHSideCastleFR:
12267             case WhiteASideCastleFR:
12268             case BlackHSideCastleFR:
12269             case BlackASideCastleFR:
12270                 fromX = currentMoveString[0] - AAA;
12271                 fromY = currentMoveString[1] - ONE;
12272                 toX = currentMoveString[2] - AAA;
12273                 toY = currentMoveString[3] - ONE;
12274                 promoChar = currentMoveString[4];
12275                 break;
12276             case WhiteDrop:
12277             case BlackDrop:
12278                 fromX = next == WhiteDrop ?
12279                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12280                   (int) CharToPiece(ToLower(currentMoveString[0]));
12281                 fromY = DROP_RANK;
12282                 toX = currentMoveString[2] - AAA;
12283                 toY = currentMoveString[3] - ONE;
12284                 promoChar = 0;
12285                 break;
12286         }
12287         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12288         plyNr++;
12289         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12290         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12291         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12292         if(appData.findMirror) {
12293             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12294             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12295         }
12296     }
12297 }
12298
12299 /* Load the nth game from open file f */
12300 int
12301 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12302 {
12303     ChessMove cm;
12304     char buf[MSG_SIZ];
12305     int gn = gameNumber;
12306     ListGame *lg = NULL;
12307     int numPGNTags = 0;
12308     int err, pos = -1;
12309     GameMode oldGameMode;
12310     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12311
12312     if (appData.debugMode)
12313         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12314
12315     if (gameMode == Training )
12316         SetTrainingModeOff();
12317
12318     oldGameMode = gameMode;
12319     if (gameMode != BeginningOfGame) {
12320       Reset(FALSE, TRUE);
12321     }
12322
12323     gameFileFP = f;
12324     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12325         fclose(lastLoadGameFP);
12326     }
12327
12328     if (useList) {
12329         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12330
12331         if (lg) {
12332             fseek(f, lg->offset, 0);
12333             GameListHighlight(gameNumber);
12334             pos = lg->position;
12335             gn = 1;
12336         }
12337         else {
12338             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12339               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12340             else
12341             DisplayError(_("Game number out of range"), 0);
12342             return FALSE;
12343         }
12344     } else {
12345         GameListDestroy();
12346         if (fseek(f, 0, 0) == -1) {
12347             if (f == lastLoadGameFP ?
12348                 gameNumber == lastLoadGameNumber + 1 :
12349                 gameNumber == 1) {
12350                 gn = 1;
12351             } else {
12352                 DisplayError(_("Can't seek on game file"), 0);
12353                 return FALSE;
12354             }
12355         }
12356     }
12357     lastLoadGameFP = f;
12358     lastLoadGameNumber = gameNumber;
12359     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12360     lastLoadGameUseList = useList;
12361
12362     yynewfile(f);
12363
12364     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12365       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12366                 lg->gameInfo.black);
12367             DisplayTitle(buf);
12368     } else if (*title != NULLCHAR) {
12369         if (gameNumber > 1) {
12370           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12371             DisplayTitle(buf);
12372         } else {
12373             DisplayTitle(title);
12374         }
12375     }
12376
12377     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12378         gameMode = PlayFromGameFile;
12379         ModeHighlight();
12380     }
12381
12382     currentMove = forwardMostMove = backwardMostMove = 0;
12383     CopyBoard(boards[0], initialPosition);
12384     StopClocks();
12385
12386     /*
12387      * Skip the first gn-1 games in the file.
12388      * Also skip over anything that precedes an identifiable
12389      * start of game marker, to avoid being confused by
12390      * garbage at the start of the file.  Currently
12391      * recognized start of game markers are the move number "1",
12392      * the pattern "gnuchess .* game", the pattern
12393      * "^[#;%] [^ ]* game file", and a PGN tag block.
12394      * A game that starts with one of the latter two patterns
12395      * will also have a move number 1, possibly
12396      * following a position diagram.
12397      * 5-4-02: Let's try being more lenient and allowing a game to
12398      * start with an unnumbered move.  Does that break anything?
12399      */
12400     cm = lastLoadGameStart = EndOfFile;
12401     while (gn > 0) {
12402         yyboardindex = forwardMostMove;
12403         cm = (ChessMove) Myylex();
12404         switch (cm) {
12405           case EndOfFile:
12406             if (cmailMsgLoaded) {
12407                 nCmailGames = CMAIL_MAX_GAMES - gn;
12408             } else {
12409                 Reset(TRUE, TRUE);
12410                 DisplayError(_("Game not found in file"), 0);
12411             }
12412             return FALSE;
12413
12414           case GNUChessGame:
12415           case XBoardGame:
12416             gn--;
12417             lastLoadGameStart = cm;
12418             break;
12419
12420           case MoveNumberOne:
12421             switch (lastLoadGameStart) {
12422               case GNUChessGame:
12423               case XBoardGame:
12424               case PGNTag:
12425                 break;
12426               case MoveNumberOne:
12427               case EndOfFile:
12428                 gn--;           /* count this game */
12429                 lastLoadGameStart = cm;
12430                 break;
12431               default:
12432                 /* impossible */
12433                 break;
12434             }
12435             break;
12436
12437           case PGNTag:
12438             switch (lastLoadGameStart) {
12439               case GNUChessGame:
12440               case PGNTag:
12441               case MoveNumberOne:
12442               case EndOfFile:
12443                 gn--;           /* count this game */
12444                 lastLoadGameStart = cm;
12445                 break;
12446               case XBoardGame:
12447                 lastLoadGameStart = cm; /* game counted already */
12448                 break;
12449               default:
12450                 /* impossible */
12451                 break;
12452             }
12453             if (gn > 0) {
12454                 do {
12455                     yyboardindex = forwardMostMove;
12456                     cm = (ChessMove) Myylex();
12457                 } while (cm == PGNTag || cm == Comment);
12458             }
12459             break;
12460
12461           case WhiteWins:
12462           case BlackWins:
12463           case GameIsDrawn:
12464             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12465                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12466                     != CMAIL_OLD_RESULT) {
12467                     nCmailResults ++ ;
12468                     cmailResult[  CMAIL_MAX_GAMES
12469                                 - gn - 1] = CMAIL_OLD_RESULT;
12470                 }
12471             }
12472             break;
12473
12474           case NormalMove:
12475             /* Only a NormalMove can be at the start of a game
12476              * without a position diagram. */
12477             if (lastLoadGameStart == EndOfFile ) {
12478               gn--;
12479               lastLoadGameStart = MoveNumberOne;
12480             }
12481             break;
12482
12483           default:
12484             break;
12485         }
12486     }
12487
12488     if (appData.debugMode)
12489       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12490
12491     if (cm == XBoardGame) {
12492         /* Skip any header junk before position diagram and/or move 1 */
12493         for (;;) {
12494             yyboardindex = forwardMostMove;
12495             cm = (ChessMove) Myylex();
12496
12497             if (cm == EndOfFile ||
12498                 cm == GNUChessGame || cm == XBoardGame) {
12499                 /* Empty game; pretend end-of-file and handle later */
12500                 cm = EndOfFile;
12501                 break;
12502             }
12503
12504             if (cm == MoveNumberOne || cm == PositionDiagram ||
12505                 cm == PGNTag || cm == Comment)
12506               break;
12507         }
12508     } else if (cm == GNUChessGame) {
12509         if (gameInfo.event != NULL) {
12510             free(gameInfo.event);
12511         }
12512         gameInfo.event = StrSave(yy_text);
12513     }
12514
12515     startedFromSetupPosition = FALSE;
12516     while (cm == PGNTag) {
12517         if (appData.debugMode)
12518           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12519         err = ParsePGNTag(yy_text, &gameInfo);
12520         if (!err) numPGNTags++;
12521
12522         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12523         if(gameInfo.variant != oldVariant) {
12524             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12525             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12526             InitPosition(TRUE);
12527             oldVariant = gameInfo.variant;
12528             if (appData.debugMode)
12529               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12530         }
12531
12532
12533         if (gameInfo.fen != NULL) {
12534           Board initial_position;
12535           startedFromSetupPosition = TRUE;
12536           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12537             Reset(TRUE, TRUE);
12538             DisplayError(_("Bad FEN position in file"), 0);
12539             return FALSE;
12540           }
12541           CopyBoard(boards[0], initial_position);
12542           if (blackPlaysFirst) {
12543             currentMove = forwardMostMove = backwardMostMove = 1;
12544             CopyBoard(boards[1], initial_position);
12545             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12546             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12547             timeRemaining[0][1] = whiteTimeRemaining;
12548             timeRemaining[1][1] = blackTimeRemaining;
12549             if (commentList[0] != NULL) {
12550               commentList[1] = commentList[0];
12551               commentList[0] = NULL;
12552             }
12553           } else {
12554             currentMove = forwardMostMove = backwardMostMove = 0;
12555           }
12556           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12557           {   int i;
12558               initialRulePlies = FENrulePlies;
12559               for( i=0; i< nrCastlingRights; i++ )
12560                   initialRights[i] = initial_position[CASTLING][i];
12561           }
12562           yyboardindex = forwardMostMove;
12563           free(gameInfo.fen);
12564           gameInfo.fen = NULL;
12565         }
12566
12567         yyboardindex = forwardMostMove;
12568         cm = (ChessMove) Myylex();
12569
12570         /* Handle comments interspersed among the tags */
12571         while (cm == Comment) {
12572             char *p;
12573             if (appData.debugMode)
12574               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12575             p = yy_text;
12576             AppendComment(currentMove, p, FALSE);
12577             yyboardindex = forwardMostMove;
12578             cm = (ChessMove) Myylex();
12579         }
12580     }
12581
12582     /* don't rely on existence of Event tag since if game was
12583      * pasted from clipboard the Event tag may not exist
12584      */
12585     if (numPGNTags > 0){
12586         char *tags;
12587         if (gameInfo.variant == VariantNormal) {
12588           VariantClass v = StringToVariant(gameInfo.event);
12589           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12590           if(v < VariantShogi) gameInfo.variant = v;
12591         }
12592         if (!matchMode) {
12593           if( appData.autoDisplayTags ) {
12594             tags = PGNTags(&gameInfo);
12595             TagsPopUp(tags, CmailMsg());
12596             free(tags);
12597           }
12598         }
12599     } else {
12600         /* Make something up, but don't display it now */
12601         SetGameInfo();
12602         TagsPopDown();
12603     }
12604
12605     if (cm == PositionDiagram) {
12606         int i, j;
12607         char *p;
12608         Board initial_position;
12609
12610         if (appData.debugMode)
12611           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12612
12613         if (!startedFromSetupPosition) {
12614             p = yy_text;
12615             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12616               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12617                 switch (*p) {
12618                   case '{':
12619                   case '[':
12620                   case '-':
12621                   case ' ':
12622                   case '\t':
12623                   case '\n':
12624                   case '\r':
12625                     break;
12626                   default:
12627                     initial_position[i][j++] = CharToPiece(*p);
12628                     break;
12629                 }
12630             while (*p == ' ' || *p == '\t' ||
12631                    *p == '\n' || *p == '\r') p++;
12632
12633             if (strncmp(p, "black", strlen("black"))==0)
12634               blackPlaysFirst = TRUE;
12635             else
12636               blackPlaysFirst = FALSE;
12637             startedFromSetupPosition = TRUE;
12638
12639             CopyBoard(boards[0], initial_position);
12640             if (blackPlaysFirst) {
12641                 currentMove = forwardMostMove = backwardMostMove = 1;
12642                 CopyBoard(boards[1], initial_position);
12643                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12644                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12645                 timeRemaining[0][1] = whiteTimeRemaining;
12646                 timeRemaining[1][1] = blackTimeRemaining;
12647                 if (commentList[0] != NULL) {
12648                     commentList[1] = commentList[0];
12649                     commentList[0] = NULL;
12650                 }
12651             } else {
12652                 currentMove = forwardMostMove = backwardMostMove = 0;
12653             }
12654         }
12655         yyboardindex = forwardMostMove;
12656         cm = (ChessMove) Myylex();
12657     }
12658
12659   if(!creatingBook) {
12660     if (first.pr == NoProc) {
12661         StartChessProgram(&first);
12662     }
12663     InitChessProgram(&first, FALSE);
12664     SendToProgram("force\n", &first);
12665     if (startedFromSetupPosition) {
12666         SendBoard(&first, forwardMostMove);
12667     if (appData.debugMode) {
12668         fprintf(debugFP, "Load Game\n");
12669     }
12670         DisplayBothClocks();
12671     }
12672   }
12673
12674     /* [HGM] server: flag to write setup moves in broadcast file as one */
12675     loadFlag = appData.suppressLoadMoves;
12676
12677     while (cm == Comment) {
12678         char *p;
12679         if (appData.debugMode)
12680           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12681         p = yy_text;
12682         AppendComment(currentMove, p, FALSE);
12683         yyboardindex = forwardMostMove;
12684         cm = (ChessMove) Myylex();
12685     }
12686
12687     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12688         cm == WhiteWins || cm == BlackWins ||
12689         cm == GameIsDrawn || cm == GameUnfinished) {
12690         DisplayMessage("", _("No moves in game"));
12691         if (cmailMsgLoaded) {
12692             if (appData.debugMode)
12693               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12694             ClearHighlights();
12695             flipView = FALSE;
12696         }
12697         DrawPosition(FALSE, boards[currentMove]);
12698         DisplayBothClocks();
12699         gameMode = EditGame;
12700         ModeHighlight();
12701         gameFileFP = NULL;
12702         cmailOldMove = 0;
12703         return TRUE;
12704     }
12705
12706     // [HGM] PV info: routine tests if comment empty
12707     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12708         DisplayComment(currentMove - 1, commentList[currentMove]);
12709     }
12710     if (!matchMode && appData.timeDelay != 0)
12711       DrawPosition(FALSE, boards[currentMove]);
12712
12713     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12714       programStats.ok_to_send = 1;
12715     }
12716
12717     /* if the first token after the PGN tags is a move
12718      * and not move number 1, retrieve it from the parser
12719      */
12720     if (cm != MoveNumberOne)
12721         LoadGameOneMove(cm);
12722
12723     /* load the remaining moves from the file */
12724     while (LoadGameOneMove(EndOfFile)) {
12725       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12726       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12727     }
12728
12729     /* rewind to the start of the game */
12730     currentMove = backwardMostMove;
12731
12732     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12733
12734     if (oldGameMode == AnalyzeFile) {
12735       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12736       AnalyzeFileEvent();
12737     } else
12738     if (oldGameMode == AnalyzeMode) {
12739       AnalyzeFileEvent();
12740     }
12741
12742     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12743         long int w, b; // [HGM] adjourn: restore saved clock times
12744         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12745         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12746             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12747             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12748         }
12749     }
12750
12751     if(creatingBook) return TRUE;
12752     if (!matchMode && pos > 0) {
12753         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12754     } else
12755     if (matchMode || appData.timeDelay == 0) {
12756       ToEndEvent();
12757     } else if (appData.timeDelay > 0) {
12758       AutoPlayGameLoop();
12759     }
12760
12761     if (appData.debugMode)
12762         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12763
12764     loadFlag = 0; /* [HGM] true game starts */
12765     return TRUE;
12766 }
12767
12768 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12769 int
12770 ReloadPosition (int offset)
12771 {
12772     int positionNumber = lastLoadPositionNumber + offset;
12773     if (lastLoadPositionFP == NULL) {
12774         DisplayError(_("No position has been loaded yet"), 0);
12775         return FALSE;
12776     }
12777     if (positionNumber <= 0) {
12778         DisplayError(_("Can't back up any further"), 0);
12779         return FALSE;
12780     }
12781     return LoadPosition(lastLoadPositionFP, positionNumber,
12782                         lastLoadPositionTitle);
12783 }
12784
12785 /* Load the nth position from the given file */
12786 int
12787 LoadPositionFromFile (char *filename, int n, char *title)
12788 {
12789     FILE *f;
12790     char buf[MSG_SIZ];
12791
12792     if (strcmp(filename, "-") == 0) {
12793         return LoadPosition(stdin, n, "stdin");
12794     } else {
12795         f = fopen(filename, "rb");
12796         if (f == NULL) {
12797             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12798             DisplayError(buf, errno);
12799             return FALSE;
12800         } else {
12801             return LoadPosition(f, n, title);
12802         }
12803     }
12804 }
12805
12806 /* Load the nth position from the given open file, and close it */
12807 int
12808 LoadPosition (FILE *f, int positionNumber, char *title)
12809 {
12810     char *p, line[MSG_SIZ];
12811     Board initial_position;
12812     int i, j, fenMode, pn;
12813
12814     if (gameMode == Training )
12815         SetTrainingModeOff();
12816
12817     if (gameMode != BeginningOfGame) {
12818         Reset(FALSE, TRUE);
12819     }
12820     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12821         fclose(lastLoadPositionFP);
12822     }
12823     if (positionNumber == 0) positionNumber = 1;
12824     lastLoadPositionFP = f;
12825     lastLoadPositionNumber = positionNumber;
12826     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12827     if (first.pr == NoProc && !appData.noChessProgram) {
12828       StartChessProgram(&first);
12829       InitChessProgram(&first, FALSE);
12830     }
12831     pn = positionNumber;
12832     if (positionNumber < 0) {
12833         /* Negative position number means to seek to that byte offset */
12834         if (fseek(f, -positionNumber, 0) == -1) {
12835             DisplayError(_("Can't seek on position file"), 0);
12836             return FALSE;
12837         };
12838         pn = 1;
12839     } else {
12840         if (fseek(f, 0, 0) == -1) {
12841             if (f == lastLoadPositionFP ?
12842                 positionNumber == lastLoadPositionNumber + 1 :
12843                 positionNumber == 1) {
12844                 pn = 1;
12845             } else {
12846                 DisplayError(_("Can't seek on position file"), 0);
12847                 return FALSE;
12848             }
12849         }
12850     }
12851     /* See if this file is FEN or old-style xboard */
12852     if (fgets(line, MSG_SIZ, f) == NULL) {
12853         DisplayError(_("Position not found in file"), 0);
12854         return FALSE;
12855     }
12856     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12857     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12858
12859     if (pn >= 2) {
12860         if (fenMode || line[0] == '#') pn--;
12861         while (pn > 0) {
12862             /* skip positions before number pn */
12863             if (fgets(line, MSG_SIZ, f) == NULL) {
12864                 Reset(TRUE, TRUE);
12865                 DisplayError(_("Position not found in file"), 0);
12866                 return FALSE;
12867             }
12868             if (fenMode || line[0] == '#') pn--;
12869         }
12870     }
12871
12872     if (fenMode) {
12873         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12874             DisplayError(_("Bad FEN position in file"), 0);
12875             return FALSE;
12876         }
12877     } else {
12878         (void) fgets(line, MSG_SIZ, f);
12879         (void) fgets(line, MSG_SIZ, f);
12880
12881         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12882             (void) fgets(line, MSG_SIZ, f);
12883             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12884                 if (*p == ' ')
12885                   continue;
12886                 initial_position[i][j++] = CharToPiece(*p);
12887             }
12888         }
12889
12890         blackPlaysFirst = FALSE;
12891         if (!feof(f)) {
12892             (void) fgets(line, MSG_SIZ, f);
12893             if (strncmp(line, "black", strlen("black"))==0)
12894               blackPlaysFirst = TRUE;
12895         }
12896     }
12897     startedFromSetupPosition = TRUE;
12898
12899     CopyBoard(boards[0], initial_position);
12900     if (blackPlaysFirst) {
12901         currentMove = forwardMostMove = backwardMostMove = 1;
12902         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12903         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12904         CopyBoard(boards[1], initial_position);
12905         DisplayMessage("", _("Black to play"));
12906     } else {
12907         currentMove = forwardMostMove = backwardMostMove = 0;
12908         DisplayMessage("", _("White to play"));
12909     }
12910     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12911     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12912         SendToProgram("force\n", &first);
12913         SendBoard(&first, forwardMostMove);
12914     }
12915     if (appData.debugMode) {
12916 int i, j;
12917   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12918   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12919         fprintf(debugFP, "Load Position\n");
12920     }
12921
12922     if (positionNumber > 1) {
12923       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12924         DisplayTitle(line);
12925     } else {
12926         DisplayTitle(title);
12927     }
12928     gameMode = EditGame;
12929     ModeHighlight();
12930     ResetClocks();
12931     timeRemaining[0][1] = whiteTimeRemaining;
12932     timeRemaining[1][1] = blackTimeRemaining;
12933     DrawPosition(FALSE, boards[currentMove]);
12934
12935     return TRUE;
12936 }
12937
12938
12939 void
12940 CopyPlayerNameIntoFileName (char **dest, char *src)
12941 {
12942     while (*src != NULLCHAR && *src != ',') {
12943         if (*src == ' ') {
12944             *(*dest)++ = '_';
12945             src++;
12946         } else {
12947             *(*dest)++ = *src++;
12948         }
12949     }
12950 }
12951
12952 char *
12953 DefaultFileName (char *ext)
12954 {
12955     static char def[MSG_SIZ];
12956     char *p;
12957
12958     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12959         p = def;
12960         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12961         *p++ = '-';
12962         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12963         *p++ = '.';
12964         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12965     } else {
12966         def[0] = NULLCHAR;
12967     }
12968     return def;
12969 }
12970
12971 /* Save the current game to the given file */
12972 int
12973 SaveGameToFile (char *filename, int append)
12974 {
12975     FILE *f;
12976     char buf[MSG_SIZ];
12977     int result, i, t,tot=0;
12978
12979     if (strcmp(filename, "-") == 0) {
12980         return SaveGame(stdout, 0, NULL);
12981     } else {
12982         for(i=0; i<10; i++) { // upto 10 tries
12983              f = fopen(filename, append ? "a" : "w");
12984              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12985              if(f || errno != 13) break;
12986              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12987              tot += t;
12988         }
12989         if (f == NULL) {
12990             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12991             DisplayError(buf, errno);
12992             return FALSE;
12993         } else {
12994             safeStrCpy(buf, lastMsg, MSG_SIZ);
12995             DisplayMessage(_("Waiting for access to save file"), "");
12996             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12997             DisplayMessage(_("Saving game"), "");
12998             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12999             result = SaveGame(f, 0, NULL);
13000             DisplayMessage(buf, "");
13001             return result;
13002         }
13003     }
13004 }
13005
13006 char *
13007 SavePart (char *str)
13008 {
13009     static char buf[MSG_SIZ];
13010     char *p;
13011
13012     p = strchr(str, ' ');
13013     if (p == NULL) return str;
13014     strncpy(buf, str, p - str);
13015     buf[p - str] = NULLCHAR;
13016     return buf;
13017 }
13018
13019 #define PGN_MAX_LINE 75
13020
13021 #define PGN_SIDE_WHITE  0
13022 #define PGN_SIDE_BLACK  1
13023
13024 static int
13025 FindFirstMoveOutOfBook (int side)
13026 {
13027     int result = -1;
13028
13029     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13030         int index = backwardMostMove;
13031         int has_book_hit = 0;
13032
13033         if( (index % 2) != side ) {
13034             index++;
13035         }
13036
13037         while( index < forwardMostMove ) {
13038             /* Check to see if engine is in book */
13039             int depth = pvInfoList[index].depth;
13040             int score = pvInfoList[index].score;
13041             int in_book = 0;
13042
13043             if( depth <= 2 ) {
13044                 in_book = 1;
13045             }
13046             else if( score == 0 && depth == 63 ) {
13047                 in_book = 1; /* Zappa */
13048             }
13049             else if( score == 2 && depth == 99 ) {
13050                 in_book = 1; /* Abrok */
13051             }
13052
13053             has_book_hit += in_book;
13054
13055             if( ! in_book ) {
13056                 result = index;
13057
13058                 break;
13059             }
13060
13061             index += 2;
13062         }
13063     }
13064
13065     return result;
13066 }
13067
13068 void
13069 GetOutOfBookInfo (char * buf)
13070 {
13071     int oob[2];
13072     int i;
13073     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13074
13075     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13076     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13077
13078     *buf = '\0';
13079
13080     if( oob[0] >= 0 || oob[1] >= 0 ) {
13081         for( i=0; i<2; i++ ) {
13082             int idx = oob[i];
13083
13084             if( idx >= 0 ) {
13085                 if( i > 0 && oob[0] >= 0 ) {
13086                     strcat( buf, "   " );
13087                 }
13088
13089                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13090                 sprintf( buf+strlen(buf), "%s%.2f",
13091                     pvInfoList[idx].score >= 0 ? "+" : "",
13092                     pvInfoList[idx].score / 100.0 );
13093             }
13094         }
13095     }
13096 }
13097
13098 /* Save game in PGN style and close the file */
13099 int
13100 SaveGamePGN (FILE *f)
13101 {
13102     int i, offset, linelen, newblock;
13103 //    char *movetext;
13104     char numtext[32];
13105     int movelen, numlen, blank;
13106     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13107
13108     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13109
13110     PrintPGNTags(f, &gameInfo);
13111
13112     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13113
13114     if (backwardMostMove > 0 || startedFromSetupPosition) {
13115         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13116         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13117         fprintf(f, "\n{--------------\n");
13118         PrintPosition(f, backwardMostMove);
13119         fprintf(f, "--------------}\n");
13120         free(fen);
13121     }
13122     else {
13123         /* [AS] Out of book annotation */
13124         if( appData.saveOutOfBookInfo ) {
13125             char buf[64];
13126
13127             GetOutOfBookInfo( buf );
13128
13129             if( buf[0] != '\0' ) {
13130                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13131             }
13132         }
13133
13134         fprintf(f, "\n");
13135     }
13136
13137     i = backwardMostMove;
13138     linelen = 0;
13139     newblock = TRUE;
13140
13141     while (i < forwardMostMove) {
13142         /* Print comments preceding this move */
13143         if (commentList[i] != NULL) {
13144             if (linelen > 0) fprintf(f, "\n");
13145             fprintf(f, "%s", commentList[i]);
13146             linelen = 0;
13147             newblock = TRUE;
13148         }
13149
13150         /* Format move number */
13151         if ((i % 2) == 0)
13152           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13153         else
13154           if (newblock)
13155             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13156           else
13157             numtext[0] = NULLCHAR;
13158
13159         numlen = strlen(numtext);
13160         newblock = FALSE;
13161
13162         /* Print move number */
13163         blank = linelen > 0 && numlen > 0;
13164         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13165             fprintf(f, "\n");
13166             linelen = 0;
13167             blank = 0;
13168         }
13169         if (blank) {
13170             fprintf(f, " ");
13171             linelen++;
13172         }
13173         fprintf(f, "%s", numtext);
13174         linelen += numlen;
13175
13176         /* Get move */
13177         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13178         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13179
13180         /* Print move */
13181         blank = linelen > 0 && movelen > 0;
13182         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13183             fprintf(f, "\n");
13184             linelen = 0;
13185             blank = 0;
13186         }
13187         if (blank) {
13188             fprintf(f, " ");
13189             linelen++;
13190         }
13191         fprintf(f, "%s", move_buffer);
13192         linelen += movelen;
13193
13194         /* [AS] Add PV info if present */
13195         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13196             /* [HGM] add time */
13197             char buf[MSG_SIZ]; int seconds;
13198
13199             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13200
13201             if( seconds <= 0)
13202               buf[0] = 0;
13203             else
13204               if( seconds < 30 )
13205                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13206               else
13207                 {
13208                   seconds = (seconds + 4)/10; // round to full seconds
13209                   if( seconds < 60 )
13210                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13211                   else
13212                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13213                 }
13214
13215             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13216                       pvInfoList[i].score >= 0 ? "+" : "",
13217                       pvInfoList[i].score / 100.0,
13218                       pvInfoList[i].depth,
13219                       buf );
13220
13221             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13222
13223             /* Print score/depth */
13224             blank = linelen > 0 && movelen > 0;
13225             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13226                 fprintf(f, "\n");
13227                 linelen = 0;
13228                 blank = 0;
13229             }
13230             if (blank) {
13231                 fprintf(f, " ");
13232                 linelen++;
13233             }
13234             fprintf(f, "%s", move_buffer);
13235             linelen += movelen;
13236         }
13237
13238         i++;
13239     }
13240
13241     /* Start a new line */
13242     if (linelen > 0) fprintf(f, "\n");
13243
13244     /* Print comments after last move */
13245     if (commentList[i] != NULL) {
13246         fprintf(f, "%s\n", commentList[i]);
13247     }
13248
13249     /* Print result */
13250     if (gameInfo.resultDetails != NULL &&
13251         gameInfo.resultDetails[0] != NULLCHAR) {
13252         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13253         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13254            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13255             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13256         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13257     } else {
13258         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13259     }
13260
13261     fclose(f);
13262     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13263     return TRUE;
13264 }
13265
13266 /* Save game in old style and close the file */
13267 int
13268 SaveGameOldStyle (FILE *f)
13269 {
13270     int i, offset;
13271     time_t tm;
13272
13273     tm = time((time_t *) NULL);
13274
13275     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13276     PrintOpponents(f);
13277
13278     if (backwardMostMove > 0 || startedFromSetupPosition) {
13279         fprintf(f, "\n[--------------\n");
13280         PrintPosition(f, backwardMostMove);
13281         fprintf(f, "--------------]\n");
13282     } else {
13283         fprintf(f, "\n");
13284     }
13285
13286     i = backwardMostMove;
13287     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13288
13289     while (i < forwardMostMove) {
13290         if (commentList[i] != NULL) {
13291             fprintf(f, "[%s]\n", commentList[i]);
13292         }
13293
13294         if ((i % 2) == 1) {
13295             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13296             i++;
13297         } else {
13298             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13299             i++;
13300             if (commentList[i] != NULL) {
13301                 fprintf(f, "\n");
13302                 continue;
13303             }
13304             if (i >= forwardMostMove) {
13305                 fprintf(f, "\n");
13306                 break;
13307             }
13308             fprintf(f, "%s\n", parseList[i]);
13309             i++;
13310         }
13311     }
13312
13313     if (commentList[i] != NULL) {
13314         fprintf(f, "[%s]\n", commentList[i]);
13315     }
13316
13317     /* This isn't really the old style, but it's close enough */
13318     if (gameInfo.resultDetails != NULL &&
13319         gameInfo.resultDetails[0] != NULLCHAR) {
13320         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13321                 gameInfo.resultDetails);
13322     } else {
13323         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13324     }
13325
13326     fclose(f);
13327     return TRUE;
13328 }
13329
13330 /* Save the current game to open file f and close the file */
13331 int
13332 SaveGame (FILE *f, int dummy, char *dummy2)
13333 {
13334     if (gameMode == EditPosition) EditPositionDone(TRUE);
13335     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13336     if (appData.oldSaveStyle)
13337       return SaveGameOldStyle(f);
13338     else
13339       return SaveGamePGN(f);
13340 }
13341
13342 /* Save the current position to the given file */
13343 int
13344 SavePositionToFile (char *filename)
13345 {
13346     FILE *f;
13347     char buf[MSG_SIZ];
13348
13349     if (strcmp(filename, "-") == 0) {
13350         return SavePosition(stdout, 0, NULL);
13351     } else {
13352         f = fopen(filename, "a");
13353         if (f == NULL) {
13354             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13355             DisplayError(buf, errno);
13356             return FALSE;
13357         } else {
13358             safeStrCpy(buf, lastMsg, MSG_SIZ);
13359             DisplayMessage(_("Waiting for access to save file"), "");
13360             flock(fileno(f), LOCK_EX); // [HGM] lock
13361             DisplayMessage(_("Saving position"), "");
13362             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13363             SavePosition(f, 0, NULL);
13364             DisplayMessage(buf, "");
13365             return TRUE;
13366         }
13367     }
13368 }
13369
13370 /* Save the current position to the given open file and close the file */
13371 int
13372 SavePosition (FILE *f, int dummy, char *dummy2)
13373 {
13374     time_t tm;
13375     char *fen;
13376
13377     if (gameMode == EditPosition) EditPositionDone(TRUE);
13378     if (appData.oldSaveStyle) {
13379         tm = time((time_t *) NULL);
13380
13381         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13382         PrintOpponents(f);
13383         fprintf(f, "[--------------\n");
13384         PrintPosition(f, currentMove);
13385         fprintf(f, "--------------]\n");
13386     } else {
13387         fen = PositionToFEN(currentMove, NULL, 1);
13388         fprintf(f, "%s\n", fen);
13389         free(fen);
13390     }
13391     fclose(f);
13392     return TRUE;
13393 }
13394
13395 void
13396 ReloadCmailMsgEvent (int unregister)
13397 {
13398 #if !WIN32
13399     static char *inFilename = NULL;
13400     static char *outFilename;
13401     int i;
13402     struct stat inbuf, outbuf;
13403     int status;
13404
13405     /* Any registered moves are unregistered if unregister is set, */
13406     /* i.e. invoked by the signal handler */
13407     if (unregister) {
13408         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13409             cmailMoveRegistered[i] = FALSE;
13410             if (cmailCommentList[i] != NULL) {
13411                 free(cmailCommentList[i]);
13412                 cmailCommentList[i] = NULL;
13413             }
13414         }
13415         nCmailMovesRegistered = 0;
13416     }
13417
13418     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13419         cmailResult[i] = CMAIL_NOT_RESULT;
13420     }
13421     nCmailResults = 0;
13422
13423     if (inFilename == NULL) {
13424         /* Because the filenames are static they only get malloced once  */
13425         /* and they never get freed                                      */
13426         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13427         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13428
13429         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13430         sprintf(outFilename, "%s.out", appData.cmailGameName);
13431     }
13432
13433     status = stat(outFilename, &outbuf);
13434     if (status < 0) {
13435         cmailMailedMove = FALSE;
13436     } else {
13437         status = stat(inFilename, &inbuf);
13438         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13439     }
13440
13441     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13442        counts the games, notes how each one terminated, etc.
13443
13444        It would be nice to remove this kludge and instead gather all
13445        the information while building the game list.  (And to keep it
13446        in the game list nodes instead of having a bunch of fixed-size
13447        parallel arrays.)  Note this will require getting each game's
13448        termination from the PGN tags, as the game list builder does
13449        not process the game moves.  --mann
13450        */
13451     cmailMsgLoaded = TRUE;
13452     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13453
13454     /* Load first game in the file or popup game menu */
13455     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13456
13457 #endif /* !WIN32 */
13458     return;
13459 }
13460
13461 int
13462 RegisterMove ()
13463 {
13464     FILE *f;
13465     char string[MSG_SIZ];
13466
13467     if (   cmailMailedMove
13468         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13469         return TRUE;            /* Allow free viewing  */
13470     }
13471
13472     /* Unregister move to ensure that we don't leave RegisterMove        */
13473     /* with the move registered when the conditions for registering no   */
13474     /* longer hold                                                       */
13475     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13476         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13477         nCmailMovesRegistered --;
13478
13479         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13480           {
13481               free(cmailCommentList[lastLoadGameNumber - 1]);
13482               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13483           }
13484     }
13485
13486     if (cmailOldMove == -1) {
13487         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13488         return FALSE;
13489     }
13490
13491     if (currentMove > cmailOldMove + 1) {
13492         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13493         return FALSE;
13494     }
13495
13496     if (currentMove < cmailOldMove) {
13497         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13498         return FALSE;
13499     }
13500
13501     if (forwardMostMove > currentMove) {
13502         /* Silently truncate extra moves */
13503         TruncateGame();
13504     }
13505
13506     if (   (currentMove == cmailOldMove + 1)
13507         || (   (currentMove == cmailOldMove)
13508             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13509                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13510         if (gameInfo.result != GameUnfinished) {
13511             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13512         }
13513
13514         if (commentList[currentMove] != NULL) {
13515             cmailCommentList[lastLoadGameNumber - 1]
13516               = StrSave(commentList[currentMove]);
13517         }
13518         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13519
13520         if (appData.debugMode)
13521           fprintf(debugFP, "Saving %s for game %d\n",
13522                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13523
13524         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13525
13526         f = fopen(string, "w");
13527         if (appData.oldSaveStyle) {
13528             SaveGameOldStyle(f); /* also closes the file */
13529
13530             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13531             f = fopen(string, "w");
13532             SavePosition(f, 0, NULL); /* also closes the file */
13533         } else {
13534             fprintf(f, "{--------------\n");
13535             PrintPosition(f, currentMove);
13536             fprintf(f, "--------------}\n\n");
13537
13538             SaveGame(f, 0, NULL); /* also closes the file*/
13539         }
13540
13541         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13542         nCmailMovesRegistered ++;
13543     } else if (nCmailGames == 1) {
13544         DisplayError(_("You have not made a move yet"), 0);
13545         return FALSE;
13546     }
13547
13548     return TRUE;
13549 }
13550
13551 void
13552 MailMoveEvent ()
13553 {
13554 #if !WIN32
13555     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13556     FILE *commandOutput;
13557     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13558     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13559     int nBuffers;
13560     int i;
13561     int archived;
13562     char *arcDir;
13563
13564     if (! cmailMsgLoaded) {
13565         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13566         return;
13567     }
13568
13569     if (nCmailGames == nCmailResults) {
13570         DisplayError(_("No unfinished games"), 0);
13571         return;
13572     }
13573
13574 #if CMAIL_PROHIBIT_REMAIL
13575     if (cmailMailedMove) {
13576       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);
13577         DisplayError(msg, 0);
13578         return;
13579     }
13580 #endif
13581
13582     if (! (cmailMailedMove || RegisterMove())) return;
13583
13584     if (   cmailMailedMove
13585         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13586       snprintf(string, MSG_SIZ, partCommandString,
13587                appData.debugMode ? " -v" : "", appData.cmailGameName);
13588         commandOutput = popen(string, "r");
13589
13590         if (commandOutput == NULL) {
13591             DisplayError(_("Failed to invoke cmail"), 0);
13592         } else {
13593             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13594                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13595             }
13596             if (nBuffers > 1) {
13597                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13598                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13599                 nBytes = MSG_SIZ - 1;
13600             } else {
13601                 (void) memcpy(msg, buffer, nBytes);
13602             }
13603             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13604
13605             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13606                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13607
13608                 archived = TRUE;
13609                 for (i = 0; i < nCmailGames; i ++) {
13610                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13611                         archived = FALSE;
13612                     }
13613                 }
13614                 if (   archived
13615                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13616                         != NULL)) {
13617                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13618                            arcDir,
13619                            appData.cmailGameName,
13620                            gameInfo.date);
13621                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13622                     cmailMsgLoaded = FALSE;
13623                 }
13624             }
13625
13626             DisplayInformation(msg);
13627             pclose(commandOutput);
13628         }
13629     } else {
13630         if ((*cmailMsg) != '\0') {
13631             DisplayInformation(cmailMsg);
13632         }
13633     }
13634
13635     return;
13636 #endif /* !WIN32 */
13637 }
13638
13639 char *
13640 CmailMsg ()
13641 {
13642 #if WIN32
13643     return NULL;
13644 #else
13645     int  prependComma = 0;
13646     char number[5];
13647     char string[MSG_SIZ];       /* Space for game-list */
13648     int  i;
13649
13650     if (!cmailMsgLoaded) return "";
13651
13652     if (cmailMailedMove) {
13653       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13654     } else {
13655         /* Create a list of games left */
13656       snprintf(string, MSG_SIZ, "[");
13657         for (i = 0; i < nCmailGames; i ++) {
13658             if (! (   cmailMoveRegistered[i]
13659                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13660                 if (prependComma) {
13661                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13662                 } else {
13663                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13664                     prependComma = 1;
13665                 }
13666
13667                 strcat(string, number);
13668             }
13669         }
13670         strcat(string, "]");
13671
13672         if (nCmailMovesRegistered + nCmailResults == 0) {
13673             switch (nCmailGames) {
13674               case 1:
13675                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13676                 break;
13677
13678               case 2:
13679                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13680                 break;
13681
13682               default:
13683                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13684                          nCmailGames);
13685                 break;
13686             }
13687         } else {
13688             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13689               case 1:
13690                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13691                          string);
13692                 break;
13693
13694               case 0:
13695                 if (nCmailResults == nCmailGames) {
13696                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13697                 } else {
13698                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13699                 }
13700                 break;
13701
13702               default:
13703                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13704                          string);
13705             }
13706         }
13707     }
13708     return cmailMsg;
13709 #endif /* WIN32 */
13710 }
13711
13712 void
13713 ResetGameEvent ()
13714 {
13715     if (gameMode == Training)
13716       SetTrainingModeOff();
13717
13718     Reset(TRUE, TRUE);
13719     cmailMsgLoaded = FALSE;
13720     if (appData.icsActive) {
13721       SendToICS(ics_prefix);
13722       SendToICS("refresh\n");
13723     }
13724 }
13725
13726 void
13727 ExitEvent (int status)
13728 {
13729     exiting++;
13730     if (exiting > 2) {
13731       /* Give up on clean exit */
13732       exit(status);
13733     }
13734     if (exiting > 1) {
13735       /* Keep trying for clean exit */
13736       return;
13737     }
13738
13739     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13740
13741     if (telnetISR != NULL) {
13742       RemoveInputSource(telnetISR);
13743     }
13744     if (icsPR != NoProc) {
13745       DestroyChildProcess(icsPR, TRUE);
13746     }
13747
13748     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13749     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13750
13751     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13752     /* make sure this other one finishes before killing it!                  */
13753     if(endingGame) { int count = 0;
13754         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13755         while(endingGame && count++ < 10) DoSleep(1);
13756         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13757     }
13758
13759     /* Kill off chess programs */
13760     if (first.pr != NoProc) {
13761         ExitAnalyzeMode();
13762
13763         DoSleep( appData.delayBeforeQuit );
13764         SendToProgram("quit\n", &first);
13765         DoSleep( appData.delayAfterQuit );
13766         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13767     }
13768     if (second.pr != NoProc) {
13769         DoSleep( appData.delayBeforeQuit );
13770         SendToProgram("quit\n", &second);
13771         DoSleep( appData.delayAfterQuit );
13772         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13773     }
13774     if (first.isr != NULL) {
13775         RemoveInputSource(first.isr);
13776     }
13777     if (second.isr != NULL) {
13778         RemoveInputSource(second.isr);
13779     }
13780
13781     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13782     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13783
13784     ShutDownFrontEnd();
13785     exit(status);
13786 }
13787
13788 void
13789 PauseEngine (ChessProgramState *cps)
13790 {
13791     SendToProgram("pause\n", cps);
13792     cps->pause = 2;
13793 }
13794
13795 void
13796 UnPauseEngine (ChessProgramState *cps)
13797 {
13798     SendToProgram("resume\n", cps);
13799     cps->pause = 1;
13800 }
13801
13802 void
13803 PauseEvent ()
13804 {
13805     if (appData.debugMode)
13806         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13807     if (pausing) {
13808         pausing = FALSE;
13809         ModeHighlight();
13810         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13811             StartClocks();
13812             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13813                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13814                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13815             }
13816             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13817             HandleMachineMove(stashedInputMove, stalledEngine);
13818             stalledEngine = NULL;
13819             return;
13820         }
13821         if (gameMode == MachinePlaysWhite ||
13822             gameMode == TwoMachinesPlay   ||
13823             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13824             if(first.pause)  UnPauseEngine(&first);
13825             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13826             if(second.pause) UnPauseEngine(&second);
13827             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13828             StartClocks();
13829         } else {
13830             DisplayBothClocks();
13831         }
13832         if (gameMode == PlayFromGameFile) {
13833             if (appData.timeDelay >= 0)
13834                 AutoPlayGameLoop();
13835         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13836             Reset(FALSE, TRUE);
13837             SendToICS(ics_prefix);
13838             SendToICS("refresh\n");
13839         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13840             ForwardInner(forwardMostMove);
13841         }
13842         pauseExamInvalid = FALSE;
13843     } else {
13844         switch (gameMode) {
13845           default:
13846             return;
13847           case IcsExamining:
13848             pauseExamForwardMostMove = forwardMostMove;
13849             pauseExamInvalid = FALSE;
13850             /* fall through */
13851           case IcsObserving:
13852           case IcsPlayingWhite:
13853           case IcsPlayingBlack:
13854             pausing = TRUE;
13855             ModeHighlight();
13856             return;
13857           case PlayFromGameFile:
13858             (void) StopLoadGameTimer();
13859             pausing = TRUE;
13860             ModeHighlight();
13861             break;
13862           case BeginningOfGame:
13863             if (appData.icsActive) return;
13864             /* else fall through */
13865           case MachinePlaysWhite:
13866           case MachinePlaysBlack:
13867           case TwoMachinesPlay:
13868             if (forwardMostMove == 0)
13869               return;           /* don't pause if no one has moved */
13870             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13871                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13872                 if(onMove->pause) {           // thinking engine can be paused
13873                     PauseEngine(onMove);      // do it
13874                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13875                         PauseEngine(onMove->other);
13876                     else
13877                         SendToProgram("easy\n", onMove->other);
13878                     StopClocks();
13879                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13880             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13881                 if(first.pause) {
13882                     PauseEngine(&first);
13883                     StopClocks();
13884                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13885             } else { // human on move, pause pondering by either method
13886                 if(first.pause)
13887                     PauseEngine(&first);
13888                 else if(appData.ponderNextMove)
13889                     SendToProgram("easy\n", &first);
13890                 StopClocks();
13891             }
13892             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13893           case AnalyzeMode:
13894             pausing = TRUE;
13895             ModeHighlight();
13896             break;
13897         }
13898     }
13899 }
13900
13901 void
13902 EditCommentEvent ()
13903 {
13904     char title[MSG_SIZ];
13905
13906     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13907       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13908     } else {
13909       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13910                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13911                parseList[currentMove - 1]);
13912     }
13913
13914     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13915 }
13916
13917
13918 void
13919 EditTagsEvent ()
13920 {
13921     char *tags = PGNTags(&gameInfo);
13922     bookUp = FALSE;
13923     EditTagsPopUp(tags, NULL);
13924     free(tags);
13925 }
13926
13927 void
13928 ToggleSecond ()
13929 {
13930   if(second.analyzing) {
13931     SendToProgram("exit\n", &second);
13932     second.analyzing = FALSE;
13933   } else {
13934     if (second.pr == NoProc) StartChessProgram(&second);
13935     InitChessProgram(&second, FALSE);
13936     FeedMovesToProgram(&second, currentMove);
13937
13938     SendToProgram("analyze\n", &second);
13939     second.analyzing = TRUE;
13940   }
13941 }
13942
13943 /* Toggle ShowThinking */
13944 void
13945 ToggleShowThinking()
13946 {
13947   appData.showThinking = !appData.showThinking;
13948   ShowThinkingEvent();
13949 }
13950
13951 int
13952 AnalyzeModeEvent ()
13953 {
13954     char buf[MSG_SIZ];
13955
13956     if (!first.analysisSupport) {
13957       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13958       DisplayError(buf, 0);
13959       return 0;
13960     }
13961     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13962     if (appData.icsActive) {
13963         if (gameMode != IcsObserving) {
13964           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13965             DisplayError(buf, 0);
13966             /* secure check */
13967             if (appData.icsEngineAnalyze) {
13968                 if (appData.debugMode)
13969                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13970                 ExitAnalyzeMode();
13971                 ModeHighlight();
13972             }
13973             return 0;
13974         }
13975         /* if enable, user wants to disable icsEngineAnalyze */
13976         if (appData.icsEngineAnalyze) {
13977                 ExitAnalyzeMode();
13978                 ModeHighlight();
13979                 return 0;
13980         }
13981         appData.icsEngineAnalyze = TRUE;
13982         if (appData.debugMode)
13983             fprintf(debugFP, "ICS engine analyze starting... \n");
13984     }
13985
13986     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13987     if (appData.noChessProgram || gameMode == AnalyzeMode)
13988       return 0;
13989
13990     if (gameMode != AnalyzeFile) {
13991         if (!appData.icsEngineAnalyze) {
13992                EditGameEvent();
13993                if (gameMode != EditGame) return 0;
13994         }
13995         if (!appData.showThinking) ToggleShowThinking();
13996         ResurrectChessProgram();
13997         SendToProgram("analyze\n", &first);
13998         first.analyzing = TRUE;
13999         /*first.maybeThinking = TRUE;*/
14000         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14001         EngineOutputPopUp();
14002     }
14003     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14004     pausing = FALSE;
14005     ModeHighlight();
14006     SetGameInfo();
14007
14008     StartAnalysisClock();
14009     GetTimeMark(&lastNodeCountTime);
14010     lastNodeCount = 0;
14011     return 1;
14012 }
14013
14014 void
14015 AnalyzeFileEvent ()
14016 {
14017     if (appData.noChessProgram || gameMode == AnalyzeFile)
14018       return;
14019
14020     if (!first.analysisSupport) {
14021       char buf[MSG_SIZ];
14022       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14023       DisplayError(buf, 0);
14024       return;
14025     }
14026
14027     if (gameMode != AnalyzeMode) {
14028         keepInfo = 1; // mere annotating should not alter PGN tags
14029         EditGameEvent();
14030         keepInfo = 0;
14031         if (gameMode != EditGame) return;
14032         if (!appData.showThinking) ToggleShowThinking();
14033         ResurrectChessProgram();
14034         SendToProgram("analyze\n", &first);
14035         first.analyzing = TRUE;
14036         /*first.maybeThinking = TRUE;*/
14037         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14038         EngineOutputPopUp();
14039     }
14040     gameMode = AnalyzeFile;
14041     pausing = FALSE;
14042     ModeHighlight();
14043
14044     StartAnalysisClock();
14045     GetTimeMark(&lastNodeCountTime);
14046     lastNodeCount = 0;
14047     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14048     AnalysisPeriodicEvent(1);
14049 }
14050
14051 void
14052 MachineWhiteEvent ()
14053 {
14054     char buf[MSG_SIZ];
14055     char *bookHit = NULL;
14056
14057     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14058       return;
14059
14060
14061     if (gameMode == PlayFromGameFile ||
14062         gameMode == TwoMachinesPlay  ||
14063         gameMode == Training         ||
14064         gameMode == AnalyzeMode      ||
14065         gameMode == EndOfGame)
14066         EditGameEvent();
14067
14068     if (gameMode == EditPosition)
14069         EditPositionDone(TRUE);
14070
14071     if (!WhiteOnMove(currentMove)) {
14072         DisplayError(_("It is not White's turn"), 0);
14073         return;
14074     }
14075
14076     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14077       ExitAnalyzeMode();
14078
14079     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14080         gameMode == AnalyzeFile)
14081         TruncateGame();
14082
14083     ResurrectChessProgram();    /* in case it isn't running */
14084     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14085         gameMode = MachinePlaysWhite;
14086         ResetClocks();
14087     } else
14088     gameMode = MachinePlaysWhite;
14089     pausing = FALSE;
14090     ModeHighlight();
14091     SetGameInfo();
14092     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14093     DisplayTitle(buf);
14094     if (first.sendName) {
14095       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14096       SendToProgram(buf, &first);
14097     }
14098     if (first.sendTime) {
14099       if (first.useColors) {
14100         SendToProgram("black\n", &first); /*gnu kludge*/
14101       }
14102       SendTimeRemaining(&first, TRUE);
14103     }
14104     if (first.useColors) {
14105       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14106     }
14107     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14108     SetMachineThinkingEnables();
14109     first.maybeThinking = TRUE;
14110     StartClocks();
14111     firstMove = FALSE;
14112
14113     if (appData.autoFlipView && !flipView) {
14114       flipView = !flipView;
14115       DrawPosition(FALSE, NULL);
14116       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14117     }
14118
14119     if(bookHit) { // [HGM] book: simulate book reply
14120         static char bookMove[MSG_SIZ]; // a bit generous?
14121
14122         programStats.nodes = programStats.depth = programStats.time =
14123         programStats.score = programStats.got_only_move = 0;
14124         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14125
14126         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14127         strcat(bookMove, bookHit);
14128         HandleMachineMove(bookMove, &first);
14129     }
14130 }
14131
14132 void
14133 MachineBlackEvent ()
14134 {
14135   char buf[MSG_SIZ];
14136   char *bookHit = NULL;
14137
14138     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14139         return;
14140
14141
14142     if (gameMode == PlayFromGameFile ||
14143         gameMode == TwoMachinesPlay  ||
14144         gameMode == Training         ||
14145         gameMode == AnalyzeMode      ||
14146         gameMode == EndOfGame)
14147         EditGameEvent();
14148
14149     if (gameMode == EditPosition)
14150         EditPositionDone(TRUE);
14151
14152     if (WhiteOnMove(currentMove)) {
14153         DisplayError(_("It is not Black's turn"), 0);
14154         return;
14155     }
14156
14157     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14158       ExitAnalyzeMode();
14159
14160     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14161         gameMode == AnalyzeFile)
14162         TruncateGame();
14163
14164     ResurrectChessProgram();    /* in case it isn't running */
14165     gameMode = MachinePlaysBlack;
14166     pausing = FALSE;
14167     ModeHighlight();
14168     SetGameInfo();
14169     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14170     DisplayTitle(buf);
14171     if (first.sendName) {
14172       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14173       SendToProgram(buf, &first);
14174     }
14175     if (first.sendTime) {
14176       if (first.useColors) {
14177         SendToProgram("white\n", &first); /*gnu kludge*/
14178       }
14179       SendTimeRemaining(&first, FALSE);
14180     }
14181     if (first.useColors) {
14182       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14183     }
14184     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14185     SetMachineThinkingEnables();
14186     first.maybeThinking = TRUE;
14187     StartClocks();
14188
14189     if (appData.autoFlipView && flipView) {
14190       flipView = !flipView;
14191       DrawPosition(FALSE, NULL);
14192       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14193     }
14194     if(bookHit) { // [HGM] book: simulate book reply
14195         static char bookMove[MSG_SIZ]; // a bit generous?
14196
14197         programStats.nodes = programStats.depth = programStats.time =
14198         programStats.score = programStats.got_only_move = 0;
14199         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14200
14201         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14202         strcat(bookMove, bookHit);
14203         HandleMachineMove(bookMove, &first);
14204     }
14205 }
14206
14207
14208 void
14209 DisplayTwoMachinesTitle ()
14210 {
14211     char buf[MSG_SIZ];
14212     if (appData.matchGames > 0) {
14213         if(appData.tourneyFile[0]) {
14214           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14215                    gameInfo.white, _("vs."), gameInfo.black,
14216                    nextGame+1, appData.matchGames+1,
14217                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14218         } else
14219         if (first.twoMachinesColor[0] == 'w') {
14220           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14221                    gameInfo.white, _("vs."),  gameInfo.black,
14222                    first.matchWins, second.matchWins,
14223                    matchGame - 1 - (first.matchWins + second.matchWins));
14224         } else {
14225           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14226                    gameInfo.white, _("vs."), gameInfo.black,
14227                    second.matchWins, first.matchWins,
14228                    matchGame - 1 - (first.matchWins + second.matchWins));
14229         }
14230     } else {
14231       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14232     }
14233     DisplayTitle(buf);
14234 }
14235
14236 void
14237 SettingsMenuIfReady ()
14238 {
14239   if (second.lastPing != second.lastPong) {
14240     DisplayMessage("", _("Waiting for second chess program"));
14241     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14242     return;
14243   }
14244   ThawUI();
14245   DisplayMessage("", "");
14246   SettingsPopUp(&second);
14247 }
14248
14249 int
14250 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14251 {
14252     char buf[MSG_SIZ];
14253     if (cps->pr == NoProc) {
14254         StartChessProgram(cps);
14255         if (cps->protocolVersion == 1) {
14256           retry();
14257           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14258         } else {
14259           /* kludge: allow timeout for initial "feature" command */
14260           if(retry != TwoMachinesEventIfReady) FreezeUI();
14261           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14262           DisplayMessage("", buf);
14263           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14264         }
14265         return 1;
14266     }
14267     return 0;
14268 }
14269
14270 void
14271 TwoMachinesEvent P((void))
14272 {
14273     int i;
14274     char buf[MSG_SIZ];
14275     ChessProgramState *onmove;
14276     char *bookHit = NULL;
14277     static int stalling = 0;
14278     TimeMark now;
14279     long wait;
14280
14281     if (appData.noChessProgram) return;
14282
14283     switch (gameMode) {
14284       case TwoMachinesPlay:
14285         return;
14286       case MachinePlaysWhite:
14287       case MachinePlaysBlack:
14288         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14289             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14290             return;
14291         }
14292         /* fall through */
14293       case BeginningOfGame:
14294       case PlayFromGameFile:
14295       case EndOfGame:
14296         EditGameEvent();
14297         if (gameMode != EditGame) return;
14298         break;
14299       case EditPosition:
14300         EditPositionDone(TRUE);
14301         break;
14302       case AnalyzeMode:
14303       case AnalyzeFile:
14304         ExitAnalyzeMode();
14305         break;
14306       case EditGame:
14307       default:
14308         break;
14309     }
14310
14311 //    forwardMostMove = currentMove;
14312     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14313     startingEngine = TRUE;
14314
14315     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14316
14317     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14318     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14319       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14320       return;
14321     }
14322     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14323
14324     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14325         startingEngine = FALSE;
14326         DisplayError("second engine does not play this", 0);
14327         return;
14328     }
14329
14330     if(!stalling) {
14331       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14332       SendToProgram("force\n", &second);
14333       stalling = 1;
14334       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14335       return;
14336     }
14337     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14338     if(appData.matchPause>10000 || appData.matchPause<10)
14339                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14340     wait = SubtractTimeMarks(&now, &pauseStart);
14341     if(wait < appData.matchPause) {
14342         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14343         return;
14344     }
14345     // we are now committed to starting the game
14346     stalling = 0;
14347     DisplayMessage("", "");
14348     if (startedFromSetupPosition) {
14349         SendBoard(&second, backwardMostMove);
14350     if (appData.debugMode) {
14351         fprintf(debugFP, "Two Machines\n");
14352     }
14353     }
14354     for (i = backwardMostMove; i < forwardMostMove; i++) {
14355         SendMoveToProgram(i, &second);
14356     }
14357
14358     gameMode = TwoMachinesPlay;
14359     pausing = startingEngine = FALSE;
14360     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14361     SetGameInfo();
14362     DisplayTwoMachinesTitle();
14363     firstMove = TRUE;
14364     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14365         onmove = &first;
14366     } else {
14367         onmove = &second;
14368     }
14369     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14370     SendToProgram(first.computerString, &first);
14371     if (first.sendName) {
14372       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14373       SendToProgram(buf, &first);
14374     }
14375     SendToProgram(second.computerString, &second);
14376     if (second.sendName) {
14377       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14378       SendToProgram(buf, &second);
14379     }
14380
14381     ResetClocks();
14382     if (!first.sendTime || !second.sendTime) {
14383         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14384         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14385     }
14386     if (onmove->sendTime) {
14387       if (onmove->useColors) {
14388         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14389       }
14390       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14391     }
14392     if (onmove->useColors) {
14393       SendToProgram(onmove->twoMachinesColor, onmove);
14394     }
14395     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14396 //    SendToProgram("go\n", onmove);
14397     onmove->maybeThinking = TRUE;
14398     SetMachineThinkingEnables();
14399
14400     StartClocks();
14401
14402     if(bookHit) { // [HGM] book: simulate book reply
14403         static char bookMove[MSG_SIZ]; // a bit generous?
14404
14405         programStats.nodes = programStats.depth = programStats.time =
14406         programStats.score = programStats.got_only_move = 0;
14407         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14408
14409         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14410         strcat(bookMove, bookHit);
14411         savedMessage = bookMove; // args for deferred call
14412         savedState = onmove;
14413         ScheduleDelayedEvent(DeferredBookMove, 1);
14414     }
14415 }
14416
14417 void
14418 TrainingEvent ()
14419 {
14420     if (gameMode == Training) {
14421       SetTrainingModeOff();
14422       gameMode = PlayFromGameFile;
14423       DisplayMessage("", _("Training mode off"));
14424     } else {
14425       gameMode = Training;
14426       animateTraining = appData.animate;
14427
14428       /* make sure we are not already at the end of the game */
14429       if (currentMove < forwardMostMove) {
14430         SetTrainingModeOn();
14431         DisplayMessage("", _("Training mode on"));
14432       } else {
14433         gameMode = PlayFromGameFile;
14434         DisplayError(_("Already at end of game"), 0);
14435       }
14436     }
14437     ModeHighlight();
14438 }
14439
14440 void
14441 IcsClientEvent ()
14442 {
14443     if (!appData.icsActive) return;
14444     switch (gameMode) {
14445       case IcsPlayingWhite:
14446       case IcsPlayingBlack:
14447       case IcsObserving:
14448       case IcsIdle:
14449       case BeginningOfGame:
14450       case IcsExamining:
14451         return;
14452
14453       case EditGame:
14454         break;
14455
14456       case EditPosition:
14457         EditPositionDone(TRUE);
14458         break;
14459
14460       case AnalyzeMode:
14461       case AnalyzeFile:
14462         ExitAnalyzeMode();
14463         break;
14464
14465       default:
14466         EditGameEvent();
14467         break;
14468     }
14469
14470     gameMode = IcsIdle;
14471     ModeHighlight();
14472     return;
14473 }
14474
14475 void
14476 EditGameEvent ()
14477 {
14478     int i;
14479
14480     switch (gameMode) {
14481       case Training:
14482         SetTrainingModeOff();
14483         break;
14484       case MachinePlaysWhite:
14485       case MachinePlaysBlack:
14486       case BeginningOfGame:
14487         SendToProgram("force\n", &first);
14488         SetUserThinkingEnables();
14489         break;
14490       case PlayFromGameFile:
14491         (void) StopLoadGameTimer();
14492         if (gameFileFP != NULL) {
14493             gameFileFP = NULL;
14494         }
14495         break;
14496       case EditPosition:
14497         EditPositionDone(TRUE);
14498         break;
14499       case AnalyzeMode:
14500       case AnalyzeFile:
14501         ExitAnalyzeMode();
14502         SendToProgram("force\n", &first);
14503         break;
14504       case TwoMachinesPlay:
14505         GameEnds(EndOfFile, NULL, GE_PLAYER);
14506         ResurrectChessProgram();
14507         SetUserThinkingEnables();
14508         break;
14509       case EndOfGame:
14510         ResurrectChessProgram();
14511         break;
14512       case IcsPlayingBlack:
14513       case IcsPlayingWhite:
14514         DisplayError(_("Warning: You are still playing a game"), 0);
14515         break;
14516       case IcsObserving:
14517         DisplayError(_("Warning: You are still observing a game"), 0);
14518         break;
14519       case IcsExamining:
14520         DisplayError(_("Warning: You are still examining a game"), 0);
14521         break;
14522       case IcsIdle:
14523         break;
14524       case EditGame:
14525       default:
14526         return;
14527     }
14528
14529     pausing = FALSE;
14530     StopClocks();
14531     first.offeredDraw = second.offeredDraw = 0;
14532
14533     if (gameMode == PlayFromGameFile) {
14534         whiteTimeRemaining = timeRemaining[0][currentMove];
14535         blackTimeRemaining = timeRemaining[1][currentMove];
14536         DisplayTitle("");
14537     }
14538
14539     if (gameMode == MachinePlaysWhite ||
14540         gameMode == MachinePlaysBlack ||
14541         gameMode == TwoMachinesPlay ||
14542         gameMode == EndOfGame) {
14543         i = forwardMostMove;
14544         while (i > currentMove) {
14545             SendToProgram("undo\n", &first);
14546             i--;
14547         }
14548         if(!adjustedClock) {
14549         whiteTimeRemaining = timeRemaining[0][currentMove];
14550         blackTimeRemaining = timeRemaining[1][currentMove];
14551         DisplayBothClocks();
14552         }
14553         if (whiteFlag || blackFlag) {
14554             whiteFlag = blackFlag = 0;
14555         }
14556         DisplayTitle("");
14557     }
14558
14559     gameMode = EditGame;
14560     ModeHighlight();
14561     SetGameInfo();
14562 }
14563
14564
14565 void
14566 EditPositionEvent ()
14567 {
14568     if (gameMode == EditPosition) {
14569         EditGameEvent();
14570         return;
14571     }
14572
14573     EditGameEvent();
14574     if (gameMode != EditGame) return;
14575
14576     gameMode = EditPosition;
14577     ModeHighlight();
14578     SetGameInfo();
14579     if (currentMove > 0)
14580       CopyBoard(boards[0], boards[currentMove]);
14581
14582     blackPlaysFirst = !WhiteOnMove(currentMove);
14583     ResetClocks();
14584     currentMove = forwardMostMove = backwardMostMove = 0;
14585     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14586     DisplayMove(-1);
14587     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14588 }
14589
14590 void
14591 ExitAnalyzeMode ()
14592 {
14593     /* [DM] icsEngineAnalyze - possible call from other functions */
14594     if (appData.icsEngineAnalyze) {
14595         appData.icsEngineAnalyze = FALSE;
14596
14597         DisplayMessage("",_("Close ICS engine analyze..."));
14598     }
14599     if (first.analysisSupport && first.analyzing) {
14600       SendToBoth("exit\n");
14601       first.analyzing = second.analyzing = FALSE;
14602     }
14603     thinkOutput[0] = NULLCHAR;
14604 }
14605
14606 void
14607 EditPositionDone (Boolean fakeRights)
14608 {
14609     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14610
14611     startedFromSetupPosition = TRUE;
14612     InitChessProgram(&first, FALSE);
14613     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14614       boards[0][EP_STATUS] = EP_NONE;
14615       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14616       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14617         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14618         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14619       } else boards[0][CASTLING][2] = NoRights;
14620       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14621         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14622         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14623       } else boards[0][CASTLING][5] = NoRights;
14624       if(gameInfo.variant == VariantSChess) {
14625         int i;
14626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14627           boards[0][VIRGIN][i] = 0;
14628           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14629           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14630         }
14631       }
14632     }
14633     SendToProgram("force\n", &first);
14634     if (blackPlaysFirst) {
14635         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14636         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14637         currentMove = forwardMostMove = backwardMostMove = 1;
14638         CopyBoard(boards[1], boards[0]);
14639     } else {
14640         currentMove = forwardMostMove = backwardMostMove = 0;
14641     }
14642     SendBoard(&first, forwardMostMove);
14643     if (appData.debugMode) {
14644         fprintf(debugFP, "EditPosDone\n");
14645     }
14646     DisplayTitle("");
14647     DisplayMessage("", "");
14648     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14649     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14650     gameMode = EditGame;
14651     ModeHighlight();
14652     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14653     ClearHighlights(); /* [AS] */
14654 }
14655
14656 /* Pause for `ms' milliseconds */
14657 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14658 void
14659 TimeDelay (long ms)
14660 {
14661     TimeMark m1, m2;
14662
14663     GetTimeMark(&m1);
14664     do {
14665         GetTimeMark(&m2);
14666     } while (SubtractTimeMarks(&m2, &m1) < ms);
14667 }
14668
14669 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14670 void
14671 SendMultiLineToICS (char *buf)
14672 {
14673     char temp[MSG_SIZ+1], *p;
14674     int len;
14675
14676     len = strlen(buf);
14677     if (len > MSG_SIZ)
14678       len = MSG_SIZ;
14679
14680     strncpy(temp, buf, len);
14681     temp[len] = 0;
14682
14683     p = temp;
14684     while (*p) {
14685         if (*p == '\n' || *p == '\r')
14686           *p = ' ';
14687         ++p;
14688     }
14689
14690     strcat(temp, "\n");
14691     SendToICS(temp);
14692     SendToPlayer(temp, strlen(temp));
14693 }
14694
14695 void
14696 SetWhiteToPlayEvent ()
14697 {
14698     if (gameMode == EditPosition) {
14699         blackPlaysFirst = FALSE;
14700         DisplayBothClocks();    /* works because currentMove is 0 */
14701     } else if (gameMode == IcsExamining) {
14702         SendToICS(ics_prefix);
14703         SendToICS("tomove white\n");
14704     }
14705 }
14706
14707 void
14708 SetBlackToPlayEvent ()
14709 {
14710     if (gameMode == EditPosition) {
14711         blackPlaysFirst = TRUE;
14712         currentMove = 1;        /* kludge */
14713         DisplayBothClocks();
14714         currentMove = 0;
14715     } else if (gameMode == IcsExamining) {
14716         SendToICS(ics_prefix);
14717         SendToICS("tomove black\n");
14718     }
14719 }
14720
14721 void
14722 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14723 {
14724     char buf[MSG_SIZ];
14725     ChessSquare piece = boards[0][y][x];
14726     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14727     static int lastVariant;
14728
14729     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14730
14731     switch (selection) {
14732       case ClearBoard:
14733         CopyBoard(currentBoard, boards[0]);
14734         CopyBoard(menuBoard, initialPosition);
14735         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14736             SendToICS(ics_prefix);
14737             SendToICS("bsetup clear\n");
14738         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14739             SendToICS(ics_prefix);
14740             SendToICS("clearboard\n");
14741         } else {
14742             int nonEmpty = 0;
14743             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14744                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14745                 for (y = 0; y < BOARD_HEIGHT; y++) {
14746                     if (gameMode == IcsExamining) {
14747                         if (boards[currentMove][y][x] != EmptySquare) {
14748                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14749                                     AAA + x, ONE + y);
14750                             SendToICS(buf);
14751                         }
14752                     } else {
14753                         if(boards[0][y][x] != p) nonEmpty++;
14754                         boards[0][y][x] = p;
14755                     }
14756                 }
14757                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14758             }
14759             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14760                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14761                     ChessSquare p = menuBoard[0][x];
14762                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14763                     p = menuBoard[BOARD_HEIGHT-1][x];
14764                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14765                 }
14766                 DisplayMessage("Clicking clock again restores position", "");
14767                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14768                 if(!nonEmpty) { // asked to clear an empty board
14769                     CopyBoard(boards[0], menuBoard);
14770                 } else
14771                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14772                     CopyBoard(boards[0], initialPosition);
14773                 } else
14774                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14775                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14776                     CopyBoard(boards[0], erasedBoard);
14777                 } else
14778                     CopyBoard(erasedBoard, currentBoard);
14779
14780             }
14781         }
14782         if (gameMode == EditPosition) {
14783             DrawPosition(FALSE, boards[0]);
14784         }
14785         break;
14786
14787       case WhitePlay:
14788         SetWhiteToPlayEvent();
14789         break;
14790
14791       case BlackPlay:
14792         SetBlackToPlayEvent();
14793         break;
14794
14795       case EmptySquare:
14796         if (gameMode == IcsExamining) {
14797             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14798             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14799             SendToICS(buf);
14800         } else {
14801             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14802                 if(x == BOARD_LEFT-2) {
14803                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14804                     boards[0][y][1] = 0;
14805                 } else
14806                 if(x == BOARD_RGHT+1) {
14807                     if(y >= gameInfo.holdingsSize) break;
14808                     boards[0][y][BOARD_WIDTH-2] = 0;
14809                 } else break;
14810             }
14811             boards[0][y][x] = EmptySquare;
14812             DrawPosition(FALSE, boards[0]);
14813         }
14814         break;
14815
14816       case PromotePiece:
14817         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14818            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14819             selection = (ChessSquare) (PROMOTED piece);
14820         } else if(piece == EmptySquare) selection = WhiteSilver;
14821         else selection = (ChessSquare)((int)piece - 1);
14822         goto defaultlabel;
14823
14824       case DemotePiece:
14825         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14826            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14827             selection = (ChessSquare) (DEMOTED piece);
14828         } else if(piece == EmptySquare) selection = BlackSilver;
14829         else selection = (ChessSquare)((int)piece + 1);
14830         goto defaultlabel;
14831
14832       case WhiteQueen:
14833       case BlackQueen:
14834         if(gameInfo.variant == VariantShatranj ||
14835            gameInfo.variant == VariantXiangqi  ||
14836            gameInfo.variant == VariantCourier  ||
14837            gameInfo.variant == VariantASEAN    ||
14838            gameInfo.variant == VariantMakruk     )
14839             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14840         goto defaultlabel;
14841
14842       case WhiteKing:
14843       case BlackKing:
14844         if(gameInfo.variant == VariantXiangqi)
14845             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14846         if(gameInfo.variant == VariantKnightmate)
14847             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14848       default:
14849         defaultlabel:
14850         if (gameMode == IcsExamining) {
14851             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14852             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14853                      PieceToChar(selection), AAA + x, ONE + y);
14854             SendToICS(buf);
14855         } else {
14856             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14857                 int n;
14858                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14859                     n = PieceToNumber(selection - BlackPawn);
14860                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14861                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14862                     boards[0][BOARD_HEIGHT-1-n][1]++;
14863                 } else
14864                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14865                     n = PieceToNumber(selection);
14866                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14867                     boards[0][n][BOARD_WIDTH-1] = selection;
14868                     boards[0][n][BOARD_WIDTH-2]++;
14869                 }
14870             } else
14871             boards[0][y][x] = selection;
14872             DrawPosition(TRUE, boards[0]);
14873             ClearHighlights();
14874             fromX = fromY = -1;
14875         }
14876         break;
14877     }
14878 }
14879
14880
14881 void
14882 DropMenuEvent (ChessSquare selection, int x, int y)
14883 {
14884     ChessMove moveType;
14885
14886     switch (gameMode) {
14887       case IcsPlayingWhite:
14888       case MachinePlaysBlack:
14889         if (!WhiteOnMove(currentMove)) {
14890             DisplayMoveError(_("It is Black's turn"));
14891             return;
14892         }
14893         moveType = WhiteDrop;
14894         break;
14895       case IcsPlayingBlack:
14896       case MachinePlaysWhite:
14897         if (WhiteOnMove(currentMove)) {
14898             DisplayMoveError(_("It is White's turn"));
14899             return;
14900         }
14901         moveType = BlackDrop;
14902         break;
14903       case EditGame:
14904         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14905         break;
14906       default:
14907         return;
14908     }
14909
14910     if (moveType == BlackDrop && selection < BlackPawn) {
14911       selection = (ChessSquare) ((int) selection
14912                                  + (int) BlackPawn - (int) WhitePawn);
14913     }
14914     if (boards[currentMove][y][x] != EmptySquare) {
14915         DisplayMoveError(_("That square is occupied"));
14916         return;
14917     }
14918
14919     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14920 }
14921
14922 void
14923 AcceptEvent ()
14924 {
14925     /* Accept a pending offer of any kind from opponent */
14926
14927     if (appData.icsActive) {
14928         SendToICS(ics_prefix);
14929         SendToICS("accept\n");
14930     } else if (cmailMsgLoaded) {
14931         if (currentMove == cmailOldMove &&
14932             commentList[cmailOldMove] != NULL &&
14933             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14934                    "Black offers a draw" : "White offers a draw")) {
14935             TruncateGame();
14936             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14937             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14938         } else {
14939             DisplayError(_("There is no pending offer on this move"), 0);
14940             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14941         }
14942     } else {
14943         /* Not used for offers from chess program */
14944     }
14945 }
14946
14947 void
14948 DeclineEvent ()
14949 {
14950     /* Decline a pending offer of any kind from opponent */
14951
14952     if (appData.icsActive) {
14953         SendToICS(ics_prefix);
14954         SendToICS("decline\n");
14955     } else if (cmailMsgLoaded) {
14956         if (currentMove == cmailOldMove &&
14957             commentList[cmailOldMove] != NULL &&
14958             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14959                    "Black offers a draw" : "White offers a draw")) {
14960 #ifdef NOTDEF
14961             AppendComment(cmailOldMove, "Draw declined", TRUE);
14962             DisplayComment(cmailOldMove - 1, "Draw declined");
14963 #endif /*NOTDEF*/
14964         } else {
14965             DisplayError(_("There is no pending offer on this move"), 0);
14966         }
14967     } else {
14968         /* Not used for offers from chess program */
14969     }
14970 }
14971
14972 void
14973 RematchEvent ()
14974 {
14975     /* Issue ICS rematch command */
14976     if (appData.icsActive) {
14977         SendToICS(ics_prefix);
14978         SendToICS("rematch\n");
14979     }
14980 }
14981
14982 void
14983 CallFlagEvent ()
14984 {
14985     /* Call your opponent's flag (claim a win on time) */
14986     if (appData.icsActive) {
14987         SendToICS(ics_prefix);
14988         SendToICS("flag\n");
14989     } else {
14990         switch (gameMode) {
14991           default:
14992             return;
14993           case MachinePlaysWhite:
14994             if (whiteFlag) {
14995                 if (blackFlag)
14996                   GameEnds(GameIsDrawn, "Both players ran out of time",
14997                            GE_PLAYER);
14998                 else
14999                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15000             } else {
15001                 DisplayError(_("Your opponent is not out of time"), 0);
15002             }
15003             break;
15004           case MachinePlaysBlack:
15005             if (blackFlag) {
15006                 if (whiteFlag)
15007                   GameEnds(GameIsDrawn, "Both players ran out of time",
15008                            GE_PLAYER);
15009                 else
15010                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15011             } else {
15012                 DisplayError(_("Your opponent is not out of time"), 0);
15013             }
15014             break;
15015         }
15016     }
15017 }
15018
15019 void
15020 ClockClick (int which)
15021 {       // [HGM] code moved to back-end from winboard.c
15022         if(which) { // black clock
15023           if (gameMode == EditPosition || gameMode == IcsExamining) {
15024             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15025             SetBlackToPlayEvent();
15026           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15027           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15028           } else if (shiftKey) {
15029             AdjustClock(which, -1);
15030           } else if (gameMode == IcsPlayingWhite ||
15031                      gameMode == MachinePlaysBlack) {
15032             CallFlagEvent();
15033           }
15034         } else { // white clock
15035           if (gameMode == EditPosition || gameMode == IcsExamining) {
15036             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15037             SetWhiteToPlayEvent();
15038           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15039           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15040           } else if (shiftKey) {
15041             AdjustClock(which, -1);
15042           } else if (gameMode == IcsPlayingBlack ||
15043                    gameMode == MachinePlaysWhite) {
15044             CallFlagEvent();
15045           }
15046         }
15047 }
15048
15049 void
15050 DrawEvent ()
15051 {
15052     /* Offer draw or accept pending draw offer from opponent */
15053
15054     if (appData.icsActive) {
15055         /* Note: tournament rules require draw offers to be
15056            made after you make your move but before you punch
15057            your clock.  Currently ICS doesn't let you do that;
15058            instead, you immediately punch your clock after making
15059            a move, but you can offer a draw at any time. */
15060
15061         SendToICS(ics_prefix);
15062         SendToICS("draw\n");
15063         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15064     } else if (cmailMsgLoaded) {
15065         if (currentMove == cmailOldMove &&
15066             commentList[cmailOldMove] != NULL &&
15067             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15068                    "Black offers a draw" : "White offers a draw")) {
15069             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15070             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15071         } else if (currentMove == cmailOldMove + 1) {
15072             char *offer = WhiteOnMove(cmailOldMove) ?
15073               "White offers a draw" : "Black offers a draw";
15074             AppendComment(currentMove, offer, TRUE);
15075             DisplayComment(currentMove - 1, offer);
15076             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15077         } else {
15078             DisplayError(_("You must make your move before offering a draw"), 0);
15079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15080         }
15081     } else if (first.offeredDraw) {
15082         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15083     } else {
15084         if (first.sendDrawOffers) {
15085             SendToProgram("draw\n", &first);
15086             userOfferedDraw = TRUE;
15087         }
15088     }
15089 }
15090
15091 void
15092 AdjournEvent ()
15093 {
15094     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15095
15096     if (appData.icsActive) {
15097         SendToICS(ics_prefix);
15098         SendToICS("adjourn\n");
15099     } else {
15100         /* Currently GNU Chess doesn't offer or accept Adjourns */
15101     }
15102 }
15103
15104
15105 void
15106 AbortEvent ()
15107 {
15108     /* Offer Abort or accept pending Abort offer from opponent */
15109
15110     if (appData.icsActive) {
15111         SendToICS(ics_prefix);
15112         SendToICS("abort\n");
15113     } else {
15114         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15115     }
15116 }
15117
15118 void
15119 ResignEvent ()
15120 {
15121     /* Resign.  You can do this even if it's not your turn. */
15122
15123     if (appData.icsActive) {
15124         SendToICS(ics_prefix);
15125         SendToICS("resign\n");
15126     } else {
15127         switch (gameMode) {
15128           case MachinePlaysWhite:
15129             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15130             break;
15131           case MachinePlaysBlack:
15132             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15133             break;
15134           case EditGame:
15135             if (cmailMsgLoaded) {
15136                 TruncateGame();
15137                 if (WhiteOnMove(cmailOldMove)) {
15138                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15139                 } else {
15140                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15141                 }
15142                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15143             }
15144             break;
15145           default:
15146             break;
15147         }
15148     }
15149 }
15150
15151
15152 void
15153 StopObservingEvent ()
15154 {
15155     /* Stop observing current games */
15156     SendToICS(ics_prefix);
15157     SendToICS("unobserve\n");
15158 }
15159
15160 void
15161 StopExaminingEvent ()
15162 {
15163     /* Stop observing current game */
15164     SendToICS(ics_prefix);
15165     SendToICS("unexamine\n");
15166 }
15167
15168 void
15169 ForwardInner (int target)
15170 {
15171     int limit; int oldSeekGraphUp = seekGraphUp;
15172
15173     if (appData.debugMode)
15174         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15175                 target, currentMove, forwardMostMove);
15176
15177     if (gameMode == EditPosition)
15178       return;
15179
15180     seekGraphUp = FALSE;
15181     MarkTargetSquares(1);
15182
15183     if (gameMode == PlayFromGameFile && !pausing)
15184       PauseEvent();
15185
15186     if (gameMode == IcsExamining && pausing)
15187       limit = pauseExamForwardMostMove;
15188     else
15189       limit = forwardMostMove;
15190
15191     if (target > limit) target = limit;
15192
15193     if (target > 0 && moveList[target - 1][0]) {
15194         int fromX, fromY, toX, toY;
15195         toX = moveList[target - 1][2] - AAA;
15196         toY = moveList[target - 1][3] - ONE;
15197         if (moveList[target - 1][1] == '@') {
15198             if (appData.highlightLastMove) {
15199                 SetHighlights(-1, -1, toX, toY);
15200             }
15201         } else {
15202             fromX = moveList[target - 1][0] - AAA;
15203             fromY = moveList[target - 1][1] - ONE;
15204             if (target == currentMove + 1) {
15205                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15206             }
15207             if (appData.highlightLastMove) {
15208                 SetHighlights(fromX, fromY, toX, toY);
15209             }
15210         }
15211     }
15212     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15213         gameMode == Training || gameMode == PlayFromGameFile ||
15214         gameMode == AnalyzeFile) {
15215         while (currentMove < target) {
15216             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15217             SendMoveToProgram(currentMove++, &first);
15218         }
15219     } else {
15220         currentMove = target;
15221     }
15222
15223     if (gameMode == EditGame || gameMode == EndOfGame) {
15224         whiteTimeRemaining = timeRemaining[0][currentMove];
15225         blackTimeRemaining = timeRemaining[1][currentMove];
15226     }
15227     DisplayBothClocks();
15228     DisplayMove(currentMove - 1);
15229     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15230     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15231     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15232         DisplayComment(currentMove - 1, commentList[currentMove]);
15233     }
15234     ClearMap(); // [HGM] exclude: invalidate map
15235 }
15236
15237
15238 void
15239 ForwardEvent ()
15240 {
15241     if (gameMode == IcsExamining && !pausing) {
15242         SendToICS(ics_prefix);
15243         SendToICS("forward\n");
15244     } else {
15245         ForwardInner(currentMove + 1);
15246     }
15247 }
15248
15249 void
15250 ToEndEvent ()
15251 {
15252     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15253         /* to optimze, we temporarily turn off analysis mode while we feed
15254          * the remaining moves to the engine. Otherwise we get analysis output
15255          * after each move.
15256          */
15257         if (first.analysisSupport) {
15258           SendToProgram("exit\nforce\n", &first);
15259           first.analyzing = FALSE;
15260         }
15261     }
15262
15263     if (gameMode == IcsExamining && !pausing) {
15264         SendToICS(ics_prefix);
15265         SendToICS("forward 999999\n");
15266     } else {
15267         ForwardInner(forwardMostMove);
15268     }
15269
15270     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15271         /* we have fed all the moves, so reactivate analysis mode */
15272         SendToProgram("analyze\n", &first);
15273         first.analyzing = TRUE;
15274         /*first.maybeThinking = TRUE;*/
15275         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15276     }
15277 }
15278
15279 void
15280 BackwardInner (int target)
15281 {
15282     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15283
15284     if (appData.debugMode)
15285         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15286                 target, currentMove, forwardMostMove);
15287
15288     if (gameMode == EditPosition) return;
15289     seekGraphUp = FALSE;
15290     MarkTargetSquares(1);
15291     if (currentMove <= backwardMostMove) {
15292         ClearHighlights();
15293         DrawPosition(full_redraw, boards[currentMove]);
15294         return;
15295     }
15296     if (gameMode == PlayFromGameFile && !pausing)
15297       PauseEvent();
15298
15299     if (moveList[target][0]) {
15300         int fromX, fromY, toX, toY;
15301         toX = moveList[target][2] - AAA;
15302         toY = moveList[target][3] - ONE;
15303         if (moveList[target][1] == '@') {
15304             if (appData.highlightLastMove) {
15305                 SetHighlights(-1, -1, toX, toY);
15306             }
15307         } else {
15308             fromX = moveList[target][0] - AAA;
15309             fromY = moveList[target][1] - ONE;
15310             if (target == currentMove - 1) {
15311                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15312             }
15313             if (appData.highlightLastMove) {
15314                 SetHighlights(fromX, fromY, toX, toY);
15315             }
15316         }
15317     }
15318     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15319         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15320         while (currentMove > target) {
15321             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15322                 // null move cannot be undone. Reload program with move history before it.
15323                 int i;
15324                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15325                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15326                 }
15327                 SendBoard(&first, i);
15328               if(second.analyzing) SendBoard(&second, i);
15329                 for(currentMove=i; currentMove<target; currentMove++) {
15330                     SendMoveToProgram(currentMove, &first);
15331                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15332                 }
15333                 break;
15334             }
15335             SendToBoth("undo\n");
15336             currentMove--;
15337         }
15338     } else {
15339         currentMove = target;
15340     }
15341
15342     if (gameMode == EditGame || gameMode == EndOfGame) {
15343         whiteTimeRemaining = timeRemaining[0][currentMove];
15344         blackTimeRemaining = timeRemaining[1][currentMove];
15345     }
15346     DisplayBothClocks();
15347     DisplayMove(currentMove - 1);
15348     DrawPosition(full_redraw, boards[currentMove]);
15349     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15350     // [HGM] PV info: routine tests if comment empty
15351     DisplayComment(currentMove - 1, commentList[currentMove]);
15352     ClearMap(); // [HGM] exclude: invalidate map
15353 }
15354
15355 void
15356 BackwardEvent ()
15357 {
15358     if (gameMode == IcsExamining && !pausing) {
15359         SendToICS(ics_prefix);
15360         SendToICS("backward\n");
15361     } else {
15362         BackwardInner(currentMove - 1);
15363     }
15364 }
15365
15366 void
15367 ToStartEvent ()
15368 {
15369     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15370         /* to optimize, we temporarily turn off analysis mode while we undo
15371          * all the moves. Otherwise we get analysis output after each undo.
15372          */
15373         if (first.analysisSupport) {
15374           SendToProgram("exit\nforce\n", &first);
15375           first.analyzing = FALSE;
15376         }
15377     }
15378
15379     if (gameMode == IcsExamining && !pausing) {
15380         SendToICS(ics_prefix);
15381         SendToICS("backward 999999\n");
15382     } else {
15383         BackwardInner(backwardMostMove);
15384     }
15385
15386     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15387         /* we have fed all the moves, so reactivate analysis mode */
15388         SendToProgram("analyze\n", &first);
15389         first.analyzing = TRUE;
15390         /*first.maybeThinking = TRUE;*/
15391         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15392     }
15393 }
15394
15395 void
15396 ToNrEvent (int to)
15397 {
15398   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15399   if (to >= forwardMostMove) to = forwardMostMove;
15400   if (to <= backwardMostMove) to = backwardMostMove;
15401   if (to < currentMove) {
15402     BackwardInner(to);
15403   } else {
15404     ForwardInner(to);
15405   }
15406 }
15407
15408 void
15409 RevertEvent (Boolean annotate)
15410 {
15411     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15412         return;
15413     }
15414     if (gameMode != IcsExamining) {
15415         DisplayError(_("You are not examining a game"), 0);
15416         return;
15417     }
15418     if (pausing) {
15419         DisplayError(_("You can't revert while pausing"), 0);
15420         return;
15421     }
15422     SendToICS(ics_prefix);
15423     SendToICS("revert\n");
15424 }
15425
15426 void
15427 RetractMoveEvent ()
15428 {
15429     switch (gameMode) {
15430       case MachinePlaysWhite:
15431       case MachinePlaysBlack:
15432         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15433             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15434             return;
15435         }
15436         if (forwardMostMove < 2) return;
15437         currentMove = forwardMostMove = forwardMostMove - 2;
15438         whiteTimeRemaining = timeRemaining[0][currentMove];
15439         blackTimeRemaining = timeRemaining[1][currentMove];
15440         DisplayBothClocks();
15441         DisplayMove(currentMove - 1);
15442         ClearHighlights();/*!! could figure this out*/
15443         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15444         SendToProgram("remove\n", &first);
15445         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15446         break;
15447
15448       case BeginningOfGame:
15449       default:
15450         break;
15451
15452       case IcsPlayingWhite:
15453       case IcsPlayingBlack:
15454         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15455             SendToICS(ics_prefix);
15456             SendToICS("takeback 2\n");
15457         } else {
15458             SendToICS(ics_prefix);
15459             SendToICS("takeback 1\n");
15460         }
15461         break;
15462     }
15463 }
15464
15465 void
15466 MoveNowEvent ()
15467 {
15468     ChessProgramState *cps;
15469
15470     switch (gameMode) {
15471       case MachinePlaysWhite:
15472         if (!WhiteOnMove(forwardMostMove)) {
15473             DisplayError(_("It is your turn"), 0);
15474             return;
15475         }
15476         cps = &first;
15477         break;
15478       case MachinePlaysBlack:
15479         if (WhiteOnMove(forwardMostMove)) {
15480             DisplayError(_("It is your turn"), 0);
15481             return;
15482         }
15483         cps = &first;
15484         break;
15485       case TwoMachinesPlay:
15486         if (WhiteOnMove(forwardMostMove) ==
15487             (first.twoMachinesColor[0] == 'w')) {
15488             cps = &first;
15489         } else {
15490             cps = &second;
15491         }
15492         break;
15493       case BeginningOfGame:
15494       default:
15495         return;
15496     }
15497     SendToProgram("?\n", cps);
15498 }
15499
15500 void
15501 TruncateGameEvent ()
15502 {
15503     EditGameEvent();
15504     if (gameMode != EditGame) return;
15505     TruncateGame();
15506 }
15507
15508 void
15509 TruncateGame ()
15510 {
15511     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15512     if (forwardMostMove > currentMove) {
15513         if (gameInfo.resultDetails != NULL) {
15514             free(gameInfo.resultDetails);
15515             gameInfo.resultDetails = NULL;
15516             gameInfo.result = GameUnfinished;
15517         }
15518         forwardMostMove = currentMove;
15519         HistorySet(parseList, backwardMostMove, forwardMostMove,
15520                    currentMove-1);
15521     }
15522 }
15523
15524 void
15525 HintEvent ()
15526 {
15527     if (appData.noChessProgram) return;
15528     switch (gameMode) {
15529       case MachinePlaysWhite:
15530         if (WhiteOnMove(forwardMostMove)) {
15531             DisplayError(_("Wait until your turn."), 0);
15532             return;
15533         }
15534         break;
15535       case BeginningOfGame:
15536       case MachinePlaysBlack:
15537         if (!WhiteOnMove(forwardMostMove)) {
15538             DisplayError(_("Wait until your turn."), 0);
15539             return;
15540         }
15541         break;
15542       default:
15543         DisplayError(_("No hint available"), 0);
15544         return;
15545     }
15546     SendToProgram("hint\n", &first);
15547     hintRequested = TRUE;
15548 }
15549
15550 void
15551 CreateBookEvent ()
15552 {
15553     ListGame * lg = (ListGame *) gameList.head;
15554     FILE *f, *g;
15555     int nItem;
15556     static int secondTime = FALSE;
15557
15558     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15559         DisplayError(_("Game list not loaded or empty"), 0);
15560         return;
15561     }
15562
15563     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15564         fclose(g);
15565         secondTime++;
15566         DisplayNote(_("Book file exists! Try again for overwrite."));
15567         return;
15568     }
15569
15570     creatingBook = TRUE;
15571     secondTime = FALSE;
15572
15573     /* Get list size */
15574     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15575         LoadGame(f, nItem, "", TRUE);
15576         AddGameToBook(TRUE);
15577         lg = (ListGame *) lg->node.succ;
15578     }
15579
15580     creatingBook = FALSE;
15581     FlushBook();
15582 }
15583
15584 void
15585 BookEvent ()
15586 {
15587     if (appData.noChessProgram) return;
15588     switch (gameMode) {
15589       case MachinePlaysWhite:
15590         if (WhiteOnMove(forwardMostMove)) {
15591             DisplayError(_("Wait until your turn."), 0);
15592             return;
15593         }
15594         break;
15595       case BeginningOfGame:
15596       case MachinePlaysBlack:
15597         if (!WhiteOnMove(forwardMostMove)) {
15598             DisplayError(_("Wait until your turn."), 0);
15599             return;
15600         }
15601         break;
15602       case EditPosition:
15603         EditPositionDone(TRUE);
15604         break;
15605       case TwoMachinesPlay:
15606         return;
15607       default:
15608         break;
15609     }
15610     SendToProgram("bk\n", &first);
15611     bookOutput[0] = NULLCHAR;
15612     bookRequested = TRUE;
15613 }
15614
15615 void
15616 AboutGameEvent ()
15617 {
15618     char *tags = PGNTags(&gameInfo);
15619     TagsPopUp(tags, CmailMsg());
15620     free(tags);
15621 }
15622
15623 /* end button procedures */
15624
15625 void
15626 PrintPosition (FILE *fp, int move)
15627 {
15628     int i, j;
15629
15630     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15631         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15632             char c = PieceToChar(boards[move][i][j]);
15633             fputc(c == 'x' ? '.' : c, fp);
15634             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15635         }
15636     }
15637     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15638       fprintf(fp, "white to play\n");
15639     else
15640       fprintf(fp, "black to play\n");
15641 }
15642
15643 void
15644 PrintOpponents (FILE *fp)
15645 {
15646     if (gameInfo.white != NULL) {
15647         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15648     } else {
15649         fprintf(fp, "\n");
15650     }
15651 }
15652
15653 /* Find last component of program's own name, using some heuristics */
15654 void
15655 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15656 {
15657     char *p, *q, c;
15658     int local = (strcmp(host, "localhost") == 0);
15659     while (!local && (p = strchr(prog, ';')) != NULL) {
15660         p++;
15661         while (*p == ' ') p++;
15662         prog = p;
15663     }
15664     if (*prog == '"' || *prog == '\'') {
15665         q = strchr(prog + 1, *prog);
15666     } else {
15667         q = strchr(prog, ' ');
15668     }
15669     if (q == NULL) q = prog + strlen(prog);
15670     p = q;
15671     while (p >= prog && *p != '/' && *p != '\\') p--;
15672     p++;
15673     if(p == prog && *p == '"') p++;
15674     c = *q; *q = 0;
15675     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15676     memcpy(buf, p, q - p);
15677     buf[q - p] = NULLCHAR;
15678     if (!local) {
15679         strcat(buf, "@");
15680         strcat(buf, host);
15681     }
15682 }
15683
15684 char *
15685 TimeControlTagValue ()
15686 {
15687     char buf[MSG_SIZ];
15688     if (!appData.clockMode) {
15689       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15690     } else if (movesPerSession > 0) {
15691       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15692     } else if (timeIncrement == 0) {
15693       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15694     } else {
15695       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15696     }
15697     return StrSave(buf);
15698 }
15699
15700 void
15701 SetGameInfo ()
15702 {
15703     /* This routine is used only for certain modes */
15704     VariantClass v = gameInfo.variant;
15705     ChessMove r = GameUnfinished;
15706     char *p = NULL;
15707
15708     if(keepInfo) return;
15709
15710     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15711         r = gameInfo.result;
15712         p = gameInfo.resultDetails;
15713         gameInfo.resultDetails = NULL;
15714     }
15715     ClearGameInfo(&gameInfo);
15716     gameInfo.variant = v;
15717
15718     switch (gameMode) {
15719       case MachinePlaysWhite:
15720         gameInfo.event = StrSave( appData.pgnEventHeader );
15721         gameInfo.site = StrSave(HostName());
15722         gameInfo.date = PGNDate();
15723         gameInfo.round = StrSave("-");
15724         gameInfo.white = StrSave(first.tidy);
15725         gameInfo.black = StrSave(UserName());
15726         gameInfo.timeControl = TimeControlTagValue();
15727         break;
15728
15729       case MachinePlaysBlack:
15730         gameInfo.event = StrSave( appData.pgnEventHeader );
15731         gameInfo.site = StrSave(HostName());
15732         gameInfo.date = PGNDate();
15733         gameInfo.round = StrSave("-");
15734         gameInfo.white = StrSave(UserName());
15735         gameInfo.black = StrSave(first.tidy);
15736         gameInfo.timeControl = TimeControlTagValue();
15737         break;
15738
15739       case TwoMachinesPlay:
15740         gameInfo.event = StrSave( appData.pgnEventHeader );
15741         gameInfo.site = StrSave(HostName());
15742         gameInfo.date = PGNDate();
15743         if (roundNr > 0) {
15744             char buf[MSG_SIZ];
15745             snprintf(buf, MSG_SIZ, "%d", roundNr);
15746             gameInfo.round = StrSave(buf);
15747         } else {
15748             gameInfo.round = StrSave("-");
15749         }
15750         if (first.twoMachinesColor[0] == 'w') {
15751             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15752             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15753         } else {
15754             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15755             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15756         }
15757         gameInfo.timeControl = TimeControlTagValue();
15758         break;
15759
15760       case EditGame:
15761         gameInfo.event = StrSave("Edited game");
15762         gameInfo.site = StrSave(HostName());
15763         gameInfo.date = PGNDate();
15764         gameInfo.round = StrSave("-");
15765         gameInfo.white = StrSave("-");
15766         gameInfo.black = StrSave("-");
15767         gameInfo.result = r;
15768         gameInfo.resultDetails = p;
15769         break;
15770
15771       case EditPosition:
15772         gameInfo.event = StrSave("Edited position");
15773         gameInfo.site = StrSave(HostName());
15774         gameInfo.date = PGNDate();
15775         gameInfo.round = StrSave("-");
15776         gameInfo.white = StrSave("-");
15777         gameInfo.black = StrSave("-");
15778         break;
15779
15780       case IcsPlayingWhite:
15781       case IcsPlayingBlack:
15782       case IcsObserving:
15783       case IcsExamining:
15784         break;
15785
15786       case PlayFromGameFile:
15787         gameInfo.event = StrSave("Game from non-PGN file");
15788         gameInfo.site = StrSave(HostName());
15789         gameInfo.date = PGNDate();
15790         gameInfo.round = StrSave("-");
15791         gameInfo.white = StrSave("?");
15792         gameInfo.black = StrSave("?");
15793         break;
15794
15795       default:
15796         break;
15797     }
15798 }
15799
15800 void
15801 ReplaceComment (int index, char *text)
15802 {
15803     int len;
15804     char *p;
15805     float score;
15806
15807     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15808        pvInfoList[index-1].depth == len &&
15809        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15810        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15811     while (*text == '\n') text++;
15812     len = strlen(text);
15813     while (len > 0 && text[len - 1] == '\n') len--;
15814
15815     if (commentList[index] != NULL)
15816       free(commentList[index]);
15817
15818     if (len == 0) {
15819         commentList[index] = NULL;
15820         return;
15821     }
15822   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15823       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15824       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15825     commentList[index] = (char *) malloc(len + 2);
15826     strncpy(commentList[index], text, len);
15827     commentList[index][len] = '\n';
15828     commentList[index][len + 1] = NULLCHAR;
15829   } else {
15830     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15831     char *p;
15832     commentList[index] = (char *) malloc(len + 7);
15833     safeStrCpy(commentList[index], "{\n", 3);
15834     safeStrCpy(commentList[index]+2, text, len+1);
15835     commentList[index][len+2] = NULLCHAR;
15836     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15837     strcat(commentList[index], "\n}\n");
15838   }
15839 }
15840
15841 void
15842 CrushCRs (char *text)
15843 {
15844   char *p = text;
15845   char *q = text;
15846   char ch;
15847
15848   do {
15849     ch = *p++;
15850     if (ch == '\r') continue;
15851     *q++ = ch;
15852   } while (ch != '\0');
15853 }
15854
15855 void
15856 AppendComment (int index, char *text, Boolean addBraces)
15857 /* addBraces  tells if we should add {} */
15858 {
15859     int oldlen, len;
15860     char *old;
15861
15862 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15863     if(addBraces == 3) addBraces = 0; else // force appending literally
15864     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15865
15866     CrushCRs(text);
15867     while (*text == '\n') text++;
15868     len = strlen(text);
15869     while (len > 0 && text[len - 1] == '\n') len--;
15870     text[len] = NULLCHAR;
15871
15872     if (len == 0) return;
15873
15874     if (commentList[index] != NULL) {
15875       Boolean addClosingBrace = addBraces;
15876         old = commentList[index];
15877         oldlen = strlen(old);
15878         while(commentList[index][oldlen-1] ==  '\n')
15879           commentList[index][--oldlen] = NULLCHAR;
15880         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15881         safeStrCpy(commentList[index], old, oldlen + len + 6);
15882         free(old);
15883         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15884         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15885           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15886           while (*text == '\n') { text++; len--; }
15887           commentList[index][--oldlen] = NULLCHAR;
15888       }
15889         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15890         else          strcat(commentList[index], "\n");
15891         strcat(commentList[index], text);
15892         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15893         else          strcat(commentList[index], "\n");
15894     } else {
15895         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15896         if(addBraces)
15897           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15898         else commentList[index][0] = NULLCHAR;
15899         strcat(commentList[index], text);
15900         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15901         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15902     }
15903 }
15904
15905 static char *
15906 FindStr (char * text, char * sub_text)
15907 {
15908     char * result = strstr( text, sub_text );
15909
15910     if( result != NULL ) {
15911         result += strlen( sub_text );
15912     }
15913
15914     return result;
15915 }
15916
15917 /* [AS] Try to extract PV info from PGN comment */
15918 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15919 char *
15920 GetInfoFromComment (int index, char * text)
15921 {
15922     char * sep = text, *p;
15923
15924     if( text != NULL && index > 0 ) {
15925         int score = 0;
15926         int depth = 0;
15927         int time = -1, sec = 0, deci;
15928         char * s_eval = FindStr( text, "[%eval " );
15929         char * s_emt = FindStr( text, "[%emt " );
15930 #if 0
15931         if( s_eval != NULL || s_emt != NULL ) {
15932 #else
15933         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15934 #endif
15935             /* New style */
15936             char delim;
15937
15938             if( s_eval != NULL ) {
15939                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15940                     return text;
15941                 }
15942
15943                 if( delim != ']' ) {
15944                     return text;
15945                 }
15946             }
15947
15948             if( s_emt != NULL ) {
15949             }
15950                 return text;
15951         }
15952         else {
15953             /* We expect something like: [+|-]nnn.nn/dd */
15954             int score_lo = 0;
15955
15956             if(*text != '{') return text; // [HGM] braces: must be normal comment
15957
15958             sep = strchr( text, '/' );
15959             if( sep == NULL || sep < (text+4) ) {
15960                 return text;
15961             }
15962
15963             p = text;
15964             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15965             if(p[1] == '(') { // comment starts with PV
15966                p = strchr(p, ')'); // locate end of PV
15967                if(p == NULL || sep < p+5) return text;
15968                // at this point we have something like "{(.*) +0.23/6 ..."
15969                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15970                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15971                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15972             }
15973             time = -1; sec = -1; deci = -1;
15974             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15975                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15976                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15977                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15978                 return text;
15979             }
15980
15981             if( score_lo < 0 || score_lo >= 100 ) {
15982                 return text;
15983             }
15984
15985             if(sec >= 0) time = 600*time + 10*sec; else
15986             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15987
15988             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15989
15990             /* [HGM] PV time: now locate end of PV info */
15991             while( *++sep >= '0' && *sep <= '9'); // strip depth
15992             if(time >= 0)
15993             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15994             if(sec >= 0)
15995             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15996             if(deci >= 0)
15997             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15998             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15999         }
16000
16001         if( depth <= 0 ) {
16002             return text;
16003         }
16004
16005         if( time < 0 ) {
16006             time = -1;
16007         }
16008
16009         pvInfoList[index-1].depth = depth;
16010         pvInfoList[index-1].score = score;
16011         pvInfoList[index-1].time  = 10*time; // centi-sec
16012         if(*sep == '}') *sep = 0; else *--sep = '{';
16013         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16014     }
16015     return sep;
16016 }
16017
16018 void
16019 SendToProgram (char *message, ChessProgramState *cps)
16020 {
16021     int count, outCount, error;
16022     char buf[MSG_SIZ];
16023
16024     if (cps->pr == NoProc) return;
16025     Attention(cps);
16026
16027     if (appData.debugMode) {
16028         TimeMark now;
16029         GetTimeMark(&now);
16030         fprintf(debugFP, "%ld >%-6s: %s",
16031                 SubtractTimeMarks(&now, &programStartTime),
16032                 cps->which, message);
16033         if(serverFP)
16034             fprintf(serverFP, "%ld >%-6s: %s",
16035                 SubtractTimeMarks(&now, &programStartTime),
16036                 cps->which, message), fflush(serverFP);
16037     }
16038
16039     count = strlen(message);
16040     outCount = OutputToProcess(cps->pr, message, count, &error);
16041     if (outCount < count && !exiting
16042                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16043       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16044       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16045         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16046             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16047                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16048                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16049                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16050             } else {
16051                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16052                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16053                 gameInfo.result = res;
16054             }
16055             gameInfo.resultDetails = StrSave(buf);
16056         }
16057         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16058         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16059     }
16060 }
16061
16062 void
16063 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16064 {
16065     char *end_str;
16066     char buf[MSG_SIZ];
16067     ChessProgramState *cps = (ChessProgramState *)closure;
16068
16069     if (isr != cps->isr) return; /* Killed intentionally */
16070     if (count <= 0) {
16071         if (count == 0) {
16072             RemoveInputSource(cps->isr);
16073             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16074                     _(cps->which), cps->program);
16075             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16076             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16077                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16078                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16079                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16080                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16081                 } else {
16082                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16083                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16084                     gameInfo.result = res;
16085                 }
16086                 gameInfo.resultDetails = StrSave(buf);
16087             }
16088             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16089             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16090         } else {
16091             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16092                     _(cps->which), cps->program);
16093             RemoveInputSource(cps->isr);
16094
16095             /* [AS] Program is misbehaving badly... kill it */
16096             if( count == -2 ) {
16097                 DestroyChildProcess( cps->pr, 9 );
16098                 cps->pr = NoProc;
16099             }
16100
16101             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16102         }
16103         return;
16104     }
16105
16106     if ((end_str = strchr(message, '\r')) != NULL)
16107       *end_str = NULLCHAR;
16108     if ((end_str = strchr(message, '\n')) != NULL)
16109       *end_str = NULLCHAR;
16110
16111     if (appData.debugMode) {
16112         TimeMark now; int print = 1;
16113         char *quote = ""; char c; int i;
16114
16115         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16116                 char start = message[0];
16117                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16118                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16119                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16120                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16121                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16122                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16123                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16124                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16125                    sscanf(message, "hint: %c", &c)!=1 &&
16126                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16127                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16128                     print = (appData.engineComments >= 2);
16129                 }
16130                 message[0] = start; // restore original message
16131         }
16132         if(print) {
16133                 GetTimeMark(&now);
16134                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16135                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16136                         quote,
16137                         message);
16138                 if(serverFP)
16139                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16140                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16141                         quote,
16142                         message), fflush(serverFP);
16143         }
16144     }
16145
16146     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16147     if (appData.icsEngineAnalyze) {
16148         if (strstr(message, "whisper") != NULL ||
16149              strstr(message, "kibitz") != NULL ||
16150             strstr(message, "tellics") != NULL) return;
16151     }
16152
16153     HandleMachineMove(message, cps);
16154 }
16155
16156
16157 void
16158 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16159 {
16160     char buf[MSG_SIZ];
16161     int seconds;
16162
16163     if( timeControl_2 > 0 ) {
16164         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16165             tc = timeControl_2;
16166         }
16167     }
16168     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16169     inc /= cps->timeOdds;
16170     st  /= cps->timeOdds;
16171
16172     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16173
16174     if (st > 0) {
16175       /* Set exact time per move, normally using st command */
16176       if (cps->stKludge) {
16177         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16178         seconds = st % 60;
16179         if (seconds == 0) {
16180           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16181         } else {
16182           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16183         }
16184       } else {
16185         snprintf(buf, MSG_SIZ, "st %d\n", st);
16186       }
16187     } else {
16188       /* Set conventional or incremental time control, using level command */
16189       if (seconds == 0) {
16190         /* Note old gnuchess bug -- minutes:seconds used to not work.
16191            Fixed in later versions, but still avoid :seconds
16192            when seconds is 0. */
16193         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16194       } else {
16195         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16196                  seconds, inc/1000.);
16197       }
16198     }
16199     SendToProgram(buf, cps);
16200
16201     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16202     /* Orthogonally, limit search to given depth */
16203     if (sd > 0) {
16204       if (cps->sdKludge) {
16205         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16206       } else {
16207         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16208       }
16209       SendToProgram(buf, cps);
16210     }
16211
16212     if(cps->nps >= 0) { /* [HGM] nps */
16213         if(cps->supportsNPS == FALSE)
16214           cps->nps = -1; // don't use if engine explicitly says not supported!
16215         else {
16216           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16217           SendToProgram(buf, cps);
16218         }
16219     }
16220 }
16221
16222 ChessProgramState *
16223 WhitePlayer ()
16224 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16225 {
16226     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16227        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16228         return &second;
16229     return &first;
16230 }
16231
16232 void
16233 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16234 {
16235     char message[MSG_SIZ];
16236     long time, otime;
16237
16238     /* Note: this routine must be called when the clocks are stopped
16239        or when they have *just* been set or switched; otherwise
16240        it will be off by the time since the current tick started.
16241     */
16242     if (machineWhite) {
16243         time = whiteTimeRemaining / 10;
16244         otime = blackTimeRemaining / 10;
16245     } else {
16246         time = blackTimeRemaining / 10;
16247         otime = whiteTimeRemaining / 10;
16248     }
16249     /* [HGM] translate opponent's time by time-odds factor */
16250     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16251
16252     if (time <= 0) time = 1;
16253     if (otime <= 0) otime = 1;
16254
16255     snprintf(message, MSG_SIZ, "time %ld\n", time);
16256     SendToProgram(message, cps);
16257
16258     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16259     SendToProgram(message, cps);
16260 }
16261
16262 char *
16263 EngineDefinedVariant (ChessProgramState *cps, int n)
16264 {   // return name of n-th unknown variant that engine supports
16265     static char buf[MSG_SIZ];
16266     char *p, *s = cps->variants;
16267     if(!s) return NULL;
16268     do { // parse string from variants feature
16269       VariantClass v;
16270         p = strchr(s, ',');
16271         if(p) *p = NULLCHAR;
16272       v = StringToVariant(s);
16273       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16274         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16275             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16276         }
16277         if(p) *p++ = ',';
16278         if(n < 0) return buf;
16279     } while(s = p);
16280     return NULL;
16281 }
16282
16283 int
16284 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16285 {
16286   char buf[MSG_SIZ];
16287   int len = strlen(name);
16288   int val;
16289
16290   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16291     (*p) += len + 1;
16292     sscanf(*p, "%d", &val);
16293     *loc = (val != 0);
16294     while (**p && **p != ' ')
16295       (*p)++;
16296     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16297     SendToProgram(buf, cps);
16298     return TRUE;
16299   }
16300   return FALSE;
16301 }
16302
16303 int
16304 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16305 {
16306   char buf[MSG_SIZ];
16307   int len = strlen(name);
16308   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16309     (*p) += len + 1;
16310     sscanf(*p, "%d", loc);
16311     while (**p && **p != ' ') (*p)++;
16312     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16313     SendToProgram(buf, cps);
16314     return TRUE;
16315   }
16316   return FALSE;
16317 }
16318
16319 int
16320 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16321 {
16322   char buf[MSG_SIZ];
16323   int len = strlen(name);
16324   if (strncmp((*p), name, len) == 0
16325       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16326     (*p) += len + 2;
16327     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16328     sscanf(*p, "%[^\"]", *loc);
16329     while (**p && **p != '\"') (*p)++;
16330     if (**p == '\"') (*p)++;
16331     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16332     SendToProgram(buf, cps);
16333     return TRUE;
16334   }
16335   return FALSE;
16336 }
16337
16338 int
16339 ParseOption (Option *opt, ChessProgramState *cps)
16340 // [HGM] options: process the string that defines an engine option, and determine
16341 // name, type, default value, and allowed value range
16342 {
16343         char *p, *q, buf[MSG_SIZ];
16344         int n, min = (-1)<<31, max = 1<<31, def;
16345
16346         if(p = strstr(opt->name, " -spin ")) {
16347             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16348             if(max < min) max = min; // enforce consistency
16349             if(def < min) def = min;
16350             if(def > max) def = max;
16351             opt->value = def;
16352             opt->min = min;
16353             opt->max = max;
16354             opt->type = Spin;
16355         } else if((p = strstr(opt->name, " -slider "))) {
16356             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16357             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16358             if(max < min) max = min; // enforce consistency
16359             if(def < min) def = min;
16360             if(def > max) def = max;
16361             opt->value = def;
16362             opt->min = min;
16363             opt->max = max;
16364             opt->type = Spin; // Slider;
16365         } else if((p = strstr(opt->name, " -string "))) {
16366             opt->textValue = p+9;
16367             opt->type = TextBox;
16368         } else if((p = strstr(opt->name, " -file "))) {
16369             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16370             opt->textValue = p+7;
16371             opt->type = FileName; // FileName;
16372         } else if((p = strstr(opt->name, " -path "))) {
16373             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16374             opt->textValue = p+7;
16375             opt->type = PathName; // PathName;
16376         } else if(p = strstr(opt->name, " -check ")) {
16377             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16378             opt->value = (def != 0);
16379             opt->type = CheckBox;
16380         } else if(p = strstr(opt->name, " -combo ")) {
16381             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16382             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16383             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16384             opt->value = n = 0;
16385             while(q = StrStr(q, " /// ")) {
16386                 n++; *q = 0;    // count choices, and null-terminate each of them
16387                 q += 5;
16388                 if(*q == '*') { // remember default, which is marked with * prefix
16389                     q++;
16390                     opt->value = n;
16391                 }
16392                 cps->comboList[cps->comboCnt++] = q;
16393             }
16394             cps->comboList[cps->comboCnt++] = NULL;
16395             opt->max = n + 1;
16396             opt->type = ComboBox;
16397         } else if(p = strstr(opt->name, " -button")) {
16398             opt->type = Button;
16399         } else if(p = strstr(opt->name, " -save")) {
16400             opt->type = SaveButton;
16401         } else return FALSE;
16402         *p = 0; // terminate option name
16403         // now look if the command-line options define a setting for this engine option.
16404         if(cps->optionSettings && cps->optionSettings[0])
16405             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16406         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16407           snprintf(buf, MSG_SIZ, "option %s", p);
16408                 if(p = strstr(buf, ",")) *p = 0;
16409                 if(q = strchr(buf, '=')) switch(opt->type) {
16410                     case ComboBox:
16411                         for(n=0; n<opt->max; n++)
16412                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16413                         break;
16414                     case TextBox:
16415                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16416                         break;
16417                     case Spin:
16418                     case CheckBox:
16419                         opt->value = atoi(q+1);
16420                     default:
16421                         break;
16422                 }
16423                 strcat(buf, "\n");
16424                 SendToProgram(buf, cps);
16425         }
16426         return TRUE;
16427 }
16428
16429 void
16430 FeatureDone (ChessProgramState *cps, int val)
16431 {
16432   DelayedEventCallback cb = GetDelayedEvent();
16433   if ((cb == InitBackEnd3 && cps == &first) ||
16434       (cb == SettingsMenuIfReady && cps == &second) ||
16435       (cb == LoadEngine) ||
16436       (cb == TwoMachinesEventIfReady)) {
16437     CancelDelayedEvent();
16438     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16439   }
16440   cps->initDone = val;
16441   if(val) cps->reload = FALSE;
16442 }
16443
16444 /* Parse feature command from engine */
16445 void
16446 ParseFeatures (char *args, ChessProgramState *cps)
16447 {
16448   char *p = args;
16449   char *q = NULL;
16450   int val;
16451   char buf[MSG_SIZ];
16452
16453   for (;;) {
16454     while (*p == ' ') p++;
16455     if (*p == NULLCHAR) return;
16456
16457     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16458     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16459     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16460     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16461     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16462     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16463     if (BoolFeature(&p, "reuse", &val, cps)) {
16464       /* Engine can disable reuse, but can't enable it if user said no */
16465       if (!val) cps->reuse = FALSE;
16466       continue;
16467     }
16468     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16469     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16470       if (gameMode == TwoMachinesPlay) {
16471         DisplayTwoMachinesTitle();
16472       } else {
16473         DisplayTitle("");
16474       }
16475       continue;
16476     }
16477     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16478     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16479     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16480     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16481     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16482     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16483     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16484     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16485     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16486     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16487     if (IntFeature(&p, "done", &val, cps)) {
16488       FeatureDone(cps, val);
16489       continue;
16490     }
16491     /* Added by Tord: */
16492     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16493     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16494     /* End of additions by Tord */
16495
16496     /* [HGM] added features: */
16497     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16498     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16499     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16500     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16501     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16502     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16503     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16504     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16505         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16506         FREE(cps->option[cps->nrOptions].name);
16507         cps->option[cps->nrOptions].name = q; q = NULL;
16508         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16509           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16510             SendToProgram(buf, cps);
16511             continue;
16512         }
16513         if(cps->nrOptions >= MAX_OPTIONS) {
16514             cps->nrOptions--;
16515             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16516             DisplayError(buf, 0);
16517         }
16518         continue;
16519     }
16520     /* End of additions by HGM */
16521
16522     /* unknown feature: complain and skip */
16523     q = p;
16524     while (*q && *q != '=') q++;
16525     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16526     SendToProgram(buf, cps);
16527     p = q;
16528     if (*p == '=') {
16529       p++;
16530       if (*p == '\"') {
16531         p++;
16532         while (*p && *p != '\"') p++;
16533         if (*p == '\"') p++;
16534       } else {
16535         while (*p && *p != ' ') p++;
16536       }
16537     }
16538   }
16539
16540 }
16541
16542 void
16543 PeriodicUpdatesEvent (int newState)
16544 {
16545     if (newState == appData.periodicUpdates)
16546       return;
16547
16548     appData.periodicUpdates=newState;
16549
16550     /* Display type changes, so update it now */
16551 //    DisplayAnalysis();
16552
16553     /* Get the ball rolling again... */
16554     if (newState) {
16555         AnalysisPeriodicEvent(1);
16556         StartAnalysisClock();
16557     }
16558 }
16559
16560 void
16561 PonderNextMoveEvent (int newState)
16562 {
16563     if (newState == appData.ponderNextMove) return;
16564     if (gameMode == EditPosition) EditPositionDone(TRUE);
16565     if (newState) {
16566         SendToProgram("hard\n", &first);
16567         if (gameMode == TwoMachinesPlay) {
16568             SendToProgram("hard\n", &second);
16569         }
16570     } else {
16571         SendToProgram("easy\n", &first);
16572         thinkOutput[0] = NULLCHAR;
16573         if (gameMode == TwoMachinesPlay) {
16574             SendToProgram("easy\n", &second);
16575         }
16576     }
16577     appData.ponderNextMove = newState;
16578 }
16579
16580 void
16581 NewSettingEvent (int option, int *feature, char *command, int value)
16582 {
16583     char buf[MSG_SIZ];
16584
16585     if (gameMode == EditPosition) EditPositionDone(TRUE);
16586     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16587     if(feature == NULL || *feature) SendToProgram(buf, &first);
16588     if (gameMode == TwoMachinesPlay) {
16589         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16590     }
16591 }
16592
16593 void
16594 ShowThinkingEvent ()
16595 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16596 {
16597     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16598     int newState = appData.showThinking
16599         // [HGM] thinking: other features now need thinking output as well
16600         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16601
16602     if (oldState == newState) return;
16603     oldState = newState;
16604     if (gameMode == EditPosition) EditPositionDone(TRUE);
16605     if (oldState) {
16606         SendToProgram("post\n", &first);
16607         if (gameMode == TwoMachinesPlay) {
16608             SendToProgram("post\n", &second);
16609         }
16610     } else {
16611         SendToProgram("nopost\n", &first);
16612         thinkOutput[0] = NULLCHAR;
16613         if (gameMode == TwoMachinesPlay) {
16614             SendToProgram("nopost\n", &second);
16615         }
16616     }
16617 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16618 }
16619
16620 void
16621 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16622 {
16623   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16624   if (pr == NoProc) return;
16625   AskQuestion(title, question, replyPrefix, pr);
16626 }
16627
16628 void
16629 TypeInEvent (char firstChar)
16630 {
16631     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16632         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16633         gameMode == AnalyzeMode || gameMode == EditGame ||
16634         gameMode == EditPosition || gameMode == IcsExamining ||
16635         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16636         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16637                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16638                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16639         gameMode == Training) PopUpMoveDialog(firstChar);
16640 }
16641
16642 void
16643 TypeInDoneEvent (char *move)
16644 {
16645         Board board;
16646         int n, fromX, fromY, toX, toY;
16647         char promoChar;
16648         ChessMove moveType;
16649
16650         // [HGM] FENedit
16651         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16652                 EditPositionPasteFEN(move);
16653                 return;
16654         }
16655         // [HGM] movenum: allow move number to be typed in any mode
16656         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16657           ToNrEvent(2*n-1);
16658           return;
16659         }
16660         // undocumented kludge: allow command-line option to be typed in!
16661         // (potentially fatal, and does not implement the effect of the option.)
16662         // should only be used for options that are values on which future decisions will be made,
16663         // and definitely not on options that would be used during initialization.
16664         if(strstr(move, "!!! -") == move) {
16665             ParseArgsFromString(move+4);
16666             return;
16667         }
16668
16669       if (gameMode != EditGame && currentMove != forwardMostMove &&
16670         gameMode != Training) {
16671         DisplayMoveError(_("Displayed move is not current"));
16672       } else {
16673         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16674           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16675         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16676         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16677           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16678           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16679         } else {
16680           DisplayMoveError(_("Could not parse move"));
16681         }
16682       }
16683 }
16684
16685 void
16686 DisplayMove (int moveNumber)
16687 {
16688     char message[MSG_SIZ];
16689     char res[MSG_SIZ];
16690     char cpThinkOutput[MSG_SIZ];
16691
16692     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16693
16694     if (moveNumber == forwardMostMove - 1 ||
16695         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16696
16697         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16698
16699         if (strchr(cpThinkOutput, '\n')) {
16700             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16701         }
16702     } else {
16703         *cpThinkOutput = NULLCHAR;
16704     }
16705
16706     /* [AS] Hide thinking from human user */
16707     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16708         *cpThinkOutput = NULLCHAR;
16709         if( thinkOutput[0] != NULLCHAR ) {
16710             int i;
16711
16712             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16713                 cpThinkOutput[i] = '.';
16714             }
16715             cpThinkOutput[i] = NULLCHAR;
16716             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16717         }
16718     }
16719
16720     if (moveNumber == forwardMostMove - 1 &&
16721         gameInfo.resultDetails != NULL) {
16722         if (gameInfo.resultDetails[0] == NULLCHAR) {
16723           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16724         } else {
16725           snprintf(res, MSG_SIZ, " {%s} %s",
16726                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16727         }
16728     } else {
16729         res[0] = NULLCHAR;
16730     }
16731
16732     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16733         DisplayMessage(res, cpThinkOutput);
16734     } else {
16735       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16736                 WhiteOnMove(moveNumber) ? " " : ".. ",
16737                 parseList[moveNumber], res);
16738         DisplayMessage(message, cpThinkOutput);
16739     }
16740 }
16741
16742 void
16743 DisplayComment (int moveNumber, char *text)
16744 {
16745     char title[MSG_SIZ];
16746
16747     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16748       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16749     } else {
16750       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16751               WhiteOnMove(moveNumber) ? " " : ".. ",
16752               parseList[moveNumber]);
16753     }
16754     if (text != NULL && (appData.autoDisplayComment || commentUp))
16755         CommentPopUp(title, text);
16756 }
16757
16758 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16759  * might be busy thinking or pondering.  It can be omitted if your
16760  * gnuchess is configured to stop thinking immediately on any user
16761  * input.  However, that gnuchess feature depends on the FIONREAD
16762  * ioctl, which does not work properly on some flavors of Unix.
16763  */
16764 void
16765 Attention (ChessProgramState *cps)
16766 {
16767 #if ATTENTION
16768     if (!cps->useSigint) return;
16769     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16770     switch (gameMode) {
16771       case MachinePlaysWhite:
16772       case MachinePlaysBlack:
16773       case TwoMachinesPlay:
16774       case IcsPlayingWhite:
16775       case IcsPlayingBlack:
16776       case AnalyzeMode:
16777       case AnalyzeFile:
16778         /* Skip if we know it isn't thinking */
16779         if (!cps->maybeThinking) return;
16780         if (appData.debugMode)
16781           fprintf(debugFP, "Interrupting %s\n", cps->which);
16782         InterruptChildProcess(cps->pr);
16783         cps->maybeThinking = FALSE;
16784         break;
16785       default:
16786         break;
16787     }
16788 #endif /*ATTENTION*/
16789 }
16790
16791 int
16792 CheckFlags ()
16793 {
16794     if (whiteTimeRemaining <= 0) {
16795         if (!whiteFlag) {
16796             whiteFlag = TRUE;
16797             if (appData.icsActive) {
16798                 if (appData.autoCallFlag &&
16799                     gameMode == IcsPlayingBlack && !blackFlag) {
16800                   SendToICS(ics_prefix);
16801                   SendToICS("flag\n");
16802                 }
16803             } else {
16804                 if (blackFlag) {
16805                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16806                 } else {
16807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16808                     if (appData.autoCallFlag) {
16809                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16810                         return TRUE;
16811                     }
16812                 }
16813             }
16814         }
16815     }
16816     if (blackTimeRemaining <= 0) {
16817         if (!blackFlag) {
16818             blackFlag = TRUE;
16819             if (appData.icsActive) {
16820                 if (appData.autoCallFlag &&
16821                     gameMode == IcsPlayingWhite && !whiteFlag) {
16822                   SendToICS(ics_prefix);
16823                   SendToICS("flag\n");
16824                 }
16825             } else {
16826                 if (whiteFlag) {
16827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16828                 } else {
16829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16830                     if (appData.autoCallFlag) {
16831                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16832                         return TRUE;
16833                     }
16834                 }
16835             }
16836         }
16837     }
16838     return FALSE;
16839 }
16840
16841 void
16842 CheckTimeControl ()
16843 {
16844     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16845         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16846
16847     /*
16848      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16849      */
16850     if ( !WhiteOnMove(forwardMostMove) ) {
16851         /* White made time control */
16852         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16853         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16854         /* [HGM] time odds: correct new time quota for time odds! */
16855                                             / WhitePlayer()->timeOdds;
16856         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16857     } else {
16858         lastBlack -= blackTimeRemaining;
16859         /* Black made time control */
16860         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16861                                             / WhitePlayer()->other->timeOdds;
16862         lastWhite = whiteTimeRemaining;
16863     }
16864 }
16865
16866 void
16867 DisplayBothClocks ()
16868 {
16869     int wom = gameMode == EditPosition ?
16870       !blackPlaysFirst : WhiteOnMove(currentMove);
16871     DisplayWhiteClock(whiteTimeRemaining, wom);
16872     DisplayBlackClock(blackTimeRemaining, !wom);
16873 }
16874
16875
16876 /* Timekeeping seems to be a portability nightmare.  I think everyone
16877    has ftime(), but I'm really not sure, so I'm including some ifdefs
16878    to use other calls if you don't.  Clocks will be less accurate if
16879    you have neither ftime nor gettimeofday.
16880 */
16881
16882 /* VS 2008 requires the #include outside of the function */
16883 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16884 #include <sys/timeb.h>
16885 #endif
16886
16887 /* Get the current time as a TimeMark */
16888 void
16889 GetTimeMark (TimeMark *tm)
16890 {
16891 #if HAVE_GETTIMEOFDAY
16892
16893     struct timeval timeVal;
16894     struct timezone timeZone;
16895
16896     gettimeofday(&timeVal, &timeZone);
16897     tm->sec = (long) timeVal.tv_sec;
16898     tm->ms = (int) (timeVal.tv_usec / 1000L);
16899
16900 #else /*!HAVE_GETTIMEOFDAY*/
16901 #if HAVE_FTIME
16902
16903 // include <sys/timeb.h> / moved to just above start of function
16904     struct timeb timeB;
16905
16906     ftime(&timeB);
16907     tm->sec = (long) timeB.time;
16908     tm->ms = (int) timeB.millitm;
16909
16910 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16911     tm->sec = (long) time(NULL);
16912     tm->ms = 0;
16913 #endif
16914 #endif
16915 }
16916
16917 /* Return the difference in milliseconds between two
16918    time marks.  We assume the difference will fit in a long!
16919 */
16920 long
16921 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16922 {
16923     return 1000L*(tm2->sec - tm1->sec) +
16924            (long) (tm2->ms - tm1->ms);
16925 }
16926
16927
16928 /*
16929  * Code to manage the game clocks.
16930  *
16931  * In tournament play, black starts the clock and then white makes a move.
16932  * We give the human user a slight advantage if he is playing white---the
16933  * clocks don't run until he makes his first move, so it takes zero time.
16934  * Also, we don't account for network lag, so we could get out of sync
16935  * with GNU Chess's clock -- but then, referees are always right.
16936  */
16937
16938 static TimeMark tickStartTM;
16939 static long intendedTickLength;
16940
16941 long
16942 NextTickLength (long timeRemaining)
16943 {
16944     long nominalTickLength, nextTickLength;
16945
16946     if (timeRemaining > 0L && timeRemaining <= 10000L)
16947       nominalTickLength = 100L;
16948     else
16949       nominalTickLength = 1000L;
16950     nextTickLength = timeRemaining % nominalTickLength;
16951     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16952
16953     return nextTickLength;
16954 }
16955
16956 /* Adjust clock one minute up or down */
16957 void
16958 AdjustClock (Boolean which, int dir)
16959 {
16960     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16961     if(which) blackTimeRemaining += 60000*dir;
16962     else      whiteTimeRemaining += 60000*dir;
16963     DisplayBothClocks();
16964     adjustedClock = TRUE;
16965 }
16966
16967 /* Stop clocks and reset to a fresh time control */
16968 void
16969 ResetClocks ()
16970 {
16971     (void) StopClockTimer();
16972     if (appData.icsActive) {
16973         whiteTimeRemaining = blackTimeRemaining = 0;
16974     } else if (searchTime) {
16975         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16976         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16977     } else { /* [HGM] correct new time quote for time odds */
16978         whiteTC = blackTC = fullTimeControlString;
16979         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16980         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16981     }
16982     if (whiteFlag || blackFlag) {
16983         DisplayTitle("");
16984         whiteFlag = blackFlag = FALSE;
16985     }
16986     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16987     DisplayBothClocks();
16988     adjustedClock = FALSE;
16989 }
16990
16991 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16992
16993 /* Decrement running clock by amount of time that has passed */
16994 void
16995 DecrementClocks ()
16996 {
16997     long timeRemaining;
16998     long lastTickLength, fudge;
16999     TimeMark now;
17000
17001     if (!appData.clockMode) return;
17002     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17003
17004     GetTimeMark(&now);
17005
17006     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17007
17008     /* Fudge if we woke up a little too soon */
17009     fudge = intendedTickLength - lastTickLength;
17010     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17011
17012     if (WhiteOnMove(forwardMostMove)) {
17013         if(whiteNPS >= 0) lastTickLength = 0;
17014         timeRemaining = whiteTimeRemaining -= lastTickLength;
17015         if(timeRemaining < 0 && !appData.icsActive) {
17016             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17017             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17018                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17019                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17020             }
17021         }
17022         DisplayWhiteClock(whiteTimeRemaining - fudge,
17023                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17024     } else {
17025         if(blackNPS >= 0) lastTickLength = 0;
17026         timeRemaining = blackTimeRemaining -= lastTickLength;
17027         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17028             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17029             if(suddenDeath) {
17030                 blackStartMove = forwardMostMove;
17031                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17032             }
17033         }
17034         DisplayBlackClock(blackTimeRemaining - fudge,
17035                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17036     }
17037     if (CheckFlags()) return;
17038
17039     if(twoBoards) { // count down secondary board's clocks as well
17040         activePartnerTime -= lastTickLength;
17041         partnerUp = 1;
17042         if(activePartner == 'W')
17043             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17044         else
17045             DisplayBlackClock(activePartnerTime, TRUE);
17046         partnerUp = 0;
17047     }
17048
17049     tickStartTM = now;
17050     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17051     StartClockTimer(intendedTickLength);
17052
17053     /* if the time remaining has fallen below the alarm threshold, sound the
17054      * alarm. if the alarm has sounded and (due to a takeback or time control
17055      * with increment) the time remaining has increased to a level above the
17056      * threshold, reset the alarm so it can sound again.
17057      */
17058
17059     if (appData.icsActive && appData.icsAlarm) {
17060
17061         /* make sure we are dealing with the user's clock */
17062         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17063                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17064            )) return;
17065
17066         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17067             alarmSounded = FALSE;
17068         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17069             PlayAlarmSound();
17070             alarmSounded = TRUE;
17071         }
17072     }
17073 }
17074
17075
17076 /* A player has just moved, so stop the previously running
17077    clock and (if in clock mode) start the other one.
17078    We redisplay both clocks in case we're in ICS mode, because
17079    ICS gives us an update to both clocks after every move.
17080    Note that this routine is called *after* forwardMostMove
17081    is updated, so the last fractional tick must be subtracted
17082    from the color that is *not* on move now.
17083 */
17084 void
17085 SwitchClocks (int newMoveNr)
17086 {
17087     long lastTickLength;
17088     TimeMark now;
17089     int flagged = FALSE;
17090
17091     GetTimeMark(&now);
17092
17093     if (StopClockTimer() && appData.clockMode) {
17094         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17095         if (!WhiteOnMove(forwardMostMove)) {
17096             if(blackNPS >= 0) lastTickLength = 0;
17097             blackTimeRemaining -= lastTickLength;
17098            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17099 //         if(pvInfoList[forwardMostMove].time == -1)
17100                  pvInfoList[forwardMostMove].time =               // use GUI time
17101                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17102         } else {
17103            if(whiteNPS >= 0) lastTickLength = 0;
17104            whiteTimeRemaining -= lastTickLength;
17105            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17106 //         if(pvInfoList[forwardMostMove].time == -1)
17107                  pvInfoList[forwardMostMove].time =
17108                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17109         }
17110         flagged = CheckFlags();
17111     }
17112     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17113     CheckTimeControl();
17114
17115     if (flagged || !appData.clockMode) return;
17116
17117     switch (gameMode) {
17118       case MachinePlaysBlack:
17119       case MachinePlaysWhite:
17120       case BeginningOfGame:
17121         if (pausing) return;
17122         break;
17123
17124       case EditGame:
17125       case PlayFromGameFile:
17126       case IcsExamining:
17127         return;
17128
17129       default:
17130         break;
17131     }
17132
17133     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17134         if(WhiteOnMove(forwardMostMove))
17135              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17136         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17137     }
17138
17139     tickStartTM = now;
17140     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17141       whiteTimeRemaining : blackTimeRemaining);
17142     StartClockTimer(intendedTickLength);
17143 }
17144
17145
17146 /* Stop both clocks */
17147 void
17148 StopClocks ()
17149 {
17150     long lastTickLength;
17151     TimeMark now;
17152
17153     if (!StopClockTimer()) return;
17154     if (!appData.clockMode) return;
17155
17156     GetTimeMark(&now);
17157
17158     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17159     if (WhiteOnMove(forwardMostMove)) {
17160         if(whiteNPS >= 0) lastTickLength = 0;
17161         whiteTimeRemaining -= lastTickLength;
17162         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17163     } else {
17164         if(blackNPS >= 0) lastTickLength = 0;
17165         blackTimeRemaining -= lastTickLength;
17166         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17167     }
17168     CheckFlags();
17169 }
17170
17171 /* Start clock of player on move.  Time may have been reset, so
17172    if clock is already running, stop and restart it. */
17173 void
17174 StartClocks ()
17175 {
17176     (void) StopClockTimer(); /* in case it was running already */
17177     DisplayBothClocks();
17178     if (CheckFlags()) return;
17179
17180     if (!appData.clockMode) return;
17181     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17182
17183     GetTimeMark(&tickStartTM);
17184     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17185       whiteTimeRemaining : blackTimeRemaining);
17186
17187    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17188     whiteNPS = blackNPS = -1;
17189     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17190        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17191         whiteNPS = first.nps;
17192     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17193        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17194         blackNPS = first.nps;
17195     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17196         whiteNPS = second.nps;
17197     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17198         blackNPS = second.nps;
17199     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17200
17201     StartClockTimer(intendedTickLength);
17202 }
17203
17204 char *
17205 TimeString (long ms)
17206 {
17207     long second, minute, hour, day;
17208     char *sign = "";
17209     static char buf[32];
17210
17211     if (ms > 0 && ms <= 9900) {
17212       /* convert milliseconds to tenths, rounding up */
17213       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17214
17215       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17216       return buf;
17217     }
17218
17219     /* convert milliseconds to seconds, rounding up */
17220     /* use floating point to avoid strangeness of integer division
17221        with negative dividends on many machines */
17222     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17223
17224     if (second < 0) {
17225         sign = "-";
17226         second = -second;
17227     }
17228
17229     day = second / (60 * 60 * 24);
17230     second = second % (60 * 60 * 24);
17231     hour = second / (60 * 60);
17232     second = second % (60 * 60);
17233     minute = second / 60;
17234     second = second % 60;
17235
17236     if (day > 0)
17237       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17238               sign, day, hour, minute, second);
17239     else if (hour > 0)
17240       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17241     else
17242       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17243
17244     return buf;
17245 }
17246
17247
17248 /*
17249  * This is necessary because some C libraries aren't ANSI C compliant yet.
17250  */
17251 char *
17252 StrStr (char *string, char *match)
17253 {
17254     int i, length;
17255
17256     length = strlen(match);
17257
17258     for (i = strlen(string) - length; i >= 0; i--, string++)
17259       if (!strncmp(match, string, length))
17260         return string;
17261
17262     return NULL;
17263 }
17264
17265 char *
17266 StrCaseStr (char *string, char *match)
17267 {
17268     int i, j, length;
17269
17270     length = strlen(match);
17271
17272     for (i = strlen(string) - length; i >= 0; i--, string++) {
17273         for (j = 0; j < length; j++) {
17274             if (ToLower(match[j]) != ToLower(string[j]))
17275               break;
17276         }
17277         if (j == length) return string;
17278     }
17279
17280     return NULL;
17281 }
17282
17283 #ifndef _amigados
17284 int
17285 StrCaseCmp (char *s1, char *s2)
17286 {
17287     char c1, c2;
17288
17289     for (;;) {
17290         c1 = ToLower(*s1++);
17291         c2 = ToLower(*s2++);
17292         if (c1 > c2) return 1;
17293         if (c1 < c2) return -1;
17294         if (c1 == NULLCHAR) return 0;
17295     }
17296 }
17297
17298
17299 int
17300 ToLower (int c)
17301 {
17302     return isupper(c) ? tolower(c) : c;
17303 }
17304
17305
17306 int
17307 ToUpper (int c)
17308 {
17309     return islower(c) ? toupper(c) : c;
17310 }
17311 #endif /* !_amigados    */
17312
17313 char *
17314 StrSave (char *s)
17315 {
17316   char *ret;
17317
17318   if ((ret = (char *) malloc(strlen(s) + 1)))
17319     {
17320       safeStrCpy(ret, s, strlen(s)+1);
17321     }
17322   return ret;
17323 }
17324
17325 char *
17326 StrSavePtr (char *s, char **savePtr)
17327 {
17328     if (*savePtr) {
17329         free(*savePtr);
17330     }
17331     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17332       safeStrCpy(*savePtr, s, strlen(s)+1);
17333     }
17334     return(*savePtr);
17335 }
17336
17337 char *
17338 PGNDate ()
17339 {
17340     time_t clock;
17341     struct tm *tm;
17342     char buf[MSG_SIZ];
17343
17344     clock = time((time_t *)NULL);
17345     tm = localtime(&clock);
17346     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17347             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17348     return StrSave(buf);
17349 }
17350
17351
17352 char *
17353 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17354 {
17355     int i, j, fromX, fromY, toX, toY;
17356     int whiteToPlay;
17357     char buf[MSG_SIZ];
17358     char *p, *q;
17359     int emptycount;
17360     ChessSquare piece;
17361
17362     whiteToPlay = (gameMode == EditPosition) ?
17363       !blackPlaysFirst : (move % 2 == 0);
17364     p = buf;
17365
17366     /* Piece placement data */
17367     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17368         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17369         emptycount = 0;
17370         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17371             if (boards[move][i][j] == EmptySquare) {
17372                 emptycount++;
17373             } else { ChessSquare piece = boards[move][i][j];
17374                 if (emptycount > 0) {
17375                     if(emptycount<10) /* [HGM] can be >= 10 */
17376                         *p++ = '0' + emptycount;
17377                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17378                     emptycount = 0;
17379                 }
17380                 if(PieceToChar(piece) == '+') {
17381                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17382                     *p++ = '+';
17383                     piece = (ChessSquare)(DEMOTED piece);
17384                 }
17385                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17386                 if(p[-1] == '~') {
17387                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17388                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17389                     *p++ = '~';
17390                 }
17391             }
17392         }
17393         if (emptycount > 0) {
17394             if(emptycount<10) /* [HGM] can be >= 10 */
17395                 *p++ = '0' + emptycount;
17396             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17397             emptycount = 0;
17398         }
17399         *p++ = '/';
17400     }
17401     *(p - 1) = ' ';
17402
17403     /* [HGM] print Crazyhouse or Shogi holdings */
17404     if( gameInfo.holdingsWidth ) {
17405         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17406         q = p;
17407         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17408             piece = boards[move][i][BOARD_WIDTH-1];
17409             if( piece != EmptySquare )
17410               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17411                   *p++ = PieceToChar(piece);
17412         }
17413         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17414             piece = boards[move][BOARD_HEIGHT-i-1][0];
17415             if( piece != EmptySquare )
17416               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17417                   *p++ = PieceToChar(piece);
17418         }
17419
17420         if( q == p ) *p++ = '-';
17421         *p++ = ']';
17422         *p++ = ' ';
17423     }
17424
17425     /* Active color */
17426     *p++ = whiteToPlay ? 'w' : 'b';
17427     *p++ = ' ';
17428
17429   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17430     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17431   } else {
17432   if(nrCastlingRights) {
17433      q = p;
17434      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17435        /* [HGM] write directly from rights */
17436            if(boards[move][CASTLING][2] != NoRights &&
17437               boards[move][CASTLING][0] != NoRights   )
17438                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17439            if(boards[move][CASTLING][2] != NoRights &&
17440               boards[move][CASTLING][1] != NoRights   )
17441                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17442            if(boards[move][CASTLING][5] != NoRights &&
17443               boards[move][CASTLING][3] != NoRights   )
17444                 *p++ = boards[move][CASTLING][3] + AAA;
17445            if(boards[move][CASTLING][5] != NoRights &&
17446               boards[move][CASTLING][4] != NoRights   )
17447                 *p++ = boards[move][CASTLING][4] + AAA;
17448      } else {
17449
17450         /* [HGM] write true castling rights */
17451         if( nrCastlingRights == 6 ) {
17452             int q, k=0;
17453             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17454                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17455             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17456                  boards[move][CASTLING][2] != NoRights  );
17457             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17458                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17459                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17460                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17461                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17462             }
17463             if(q) *p++ = 'Q';
17464             k = 0;
17465             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17466                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17467             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17468                  boards[move][CASTLING][5] != NoRights  );
17469             if(gameInfo.variant == VariantSChess) {
17470                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17471                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17472                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17473                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17474             }
17475             if(q) *p++ = 'q';
17476         }
17477      }
17478      if (q == p) *p++ = '-'; /* No castling rights */
17479      *p++ = ' ';
17480   }
17481
17482   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17483      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17484      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17485     /* En passant target square */
17486     if (move > backwardMostMove) {
17487         fromX = moveList[move - 1][0] - AAA;
17488         fromY = moveList[move - 1][1] - ONE;
17489         toX = moveList[move - 1][2] - AAA;
17490         toY = moveList[move - 1][3] - ONE;
17491         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17492             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17493             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17494             fromX == toX) {
17495             /* 2-square pawn move just happened */
17496             *p++ = toX + AAA;
17497             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17498         } else {
17499             *p++ = '-';
17500         }
17501     } else if(move == backwardMostMove) {
17502         // [HGM] perhaps we should always do it like this, and forget the above?
17503         if((signed char)boards[move][EP_STATUS] >= 0) {
17504             *p++ = boards[move][EP_STATUS] + AAA;
17505             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17506         } else {
17507             *p++ = '-';
17508         }
17509     } else {
17510         *p++ = '-';
17511     }
17512     *p++ = ' ';
17513   }
17514   }
17515
17516     if(moveCounts)
17517     {   int i = 0, j=move;
17518
17519         /* [HGM] find reversible plies */
17520         if (appData.debugMode) { int k;
17521             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17522             for(k=backwardMostMove; k<=forwardMostMove; k++)
17523                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17524
17525         }
17526
17527         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17528         if( j == backwardMostMove ) i += initialRulePlies;
17529         sprintf(p, "%d ", i);
17530         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17531
17532         /* Fullmove number */
17533         sprintf(p, "%d", (move / 2) + 1);
17534     } else *--p = NULLCHAR;
17535
17536     return StrSave(buf);
17537 }
17538
17539 Boolean
17540 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17541 {
17542     int i, j, k, w=0;
17543     char *p, c;
17544     int emptycount, virgin[BOARD_FILES];
17545     ChessSquare piece;
17546
17547     p = fen;
17548
17549     /* Piece placement data */
17550     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17551         j = 0;
17552         for (;;) {
17553             if (*p == '/' || *p == ' ' || *p == '[' ) {
17554                 if(j > w) w = j;
17555                 emptycount = gameInfo.boardWidth - j;
17556                 while (emptycount--)
17557                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17558                 if (*p == '/') p++;
17559                 else if(autoSize) { // we stumbled unexpectedly into end of board
17560                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17561                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17562                     }
17563                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17564                 }
17565                 break;
17566 #if(BOARD_FILES >= 10)
17567             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17568                 p++; emptycount=10;
17569                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17570                 while (emptycount--)
17571                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17572 #endif
17573             } else if (*p == '*') {
17574                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17575             } else if (isdigit(*p)) {
17576                 emptycount = *p++ - '0';
17577                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17578                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17579                 while (emptycount--)
17580                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17581             } else if (*p == '+' || isalpha(*p)) {
17582                 if (j >= gameInfo.boardWidth) return FALSE;
17583                 if(*p=='+') {
17584                     piece = CharToPiece(*++p);
17585                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17586                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17587                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17588                 } else piece = CharToPiece(*p++);
17589
17590                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17591                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17592                     piece = (ChessSquare) (PROMOTED piece);
17593                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17594                     p++;
17595                 }
17596                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17597             } else {
17598                 return FALSE;
17599             }
17600         }
17601     }
17602     while (*p == '/' || *p == ' ') p++;
17603
17604     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17605
17606     /* [HGM] by default clear Crazyhouse holdings, if present */
17607     if(gameInfo.holdingsWidth) {
17608        for(i=0; i<BOARD_HEIGHT; i++) {
17609            board[i][0]             = EmptySquare; /* black holdings */
17610            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17611            board[i][1]             = (ChessSquare) 0; /* black counts */
17612            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17613        }
17614     }
17615
17616     /* [HGM] look for Crazyhouse holdings here */
17617     while(*p==' ') p++;
17618     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17619         if(*p == '[') p++;
17620         if(*p == '-' ) p++; /* empty holdings */ else {
17621             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17622             /* if we would allow FEN reading to set board size, we would   */
17623             /* have to add holdings and shift the board read so far here   */
17624             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17625                 p++;
17626                 if((int) piece >= (int) BlackPawn ) {
17627                     i = (int)piece - (int)BlackPawn;
17628                     i = PieceToNumber((ChessSquare)i);
17629                     if( i >= gameInfo.holdingsSize ) return FALSE;
17630                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17631                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17632                 } else {
17633                     i = (int)piece - (int)WhitePawn;
17634                     i = PieceToNumber((ChessSquare)i);
17635                     if( i >= gameInfo.holdingsSize ) return FALSE;
17636                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17637                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17638                 }
17639             }
17640         }
17641         if(*p == ']') p++;
17642     }
17643
17644     while(*p == ' ') p++;
17645
17646     /* Active color */
17647     c = *p++;
17648     if(appData.colorNickNames) {
17649       if( c == appData.colorNickNames[0] ) c = 'w'; else
17650       if( c == appData.colorNickNames[1] ) c = 'b';
17651     }
17652     switch (c) {
17653       case 'w':
17654         *blackPlaysFirst = FALSE;
17655         break;
17656       case 'b':
17657         *blackPlaysFirst = TRUE;
17658         break;
17659       default:
17660         return FALSE;
17661     }
17662
17663     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17664     /* return the extra info in global variiables             */
17665
17666     /* set defaults in case FEN is incomplete */
17667     board[EP_STATUS] = EP_UNKNOWN;
17668     for(i=0; i<nrCastlingRights; i++ ) {
17669         board[CASTLING][i] =
17670             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17671     }   /* assume possible unless obviously impossible */
17672     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17673     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17674     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17675                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17676     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17677     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17678     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17679                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17680     FENrulePlies = 0;
17681
17682     while(*p==' ') p++;
17683     if(nrCastlingRights) {
17684       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17685       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17686           /* castling indicator present, so default becomes no castlings */
17687           for(i=0; i<nrCastlingRights; i++ ) {
17688                  board[CASTLING][i] = NoRights;
17689           }
17690       }
17691       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17692              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17693              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17694              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17695         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17696
17697         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17698             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17699             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17700         }
17701         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17702             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17703         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17704                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17705         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17706                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17707         switch(c) {
17708           case'K':
17709               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17710               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17711               board[CASTLING][2] = whiteKingFile;
17712               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17713               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17714               break;
17715           case'Q':
17716               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17717               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17718               board[CASTLING][2] = whiteKingFile;
17719               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17720               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17721               break;
17722           case'k':
17723               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17724               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17725               board[CASTLING][5] = blackKingFile;
17726               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17727               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17728               break;
17729           case'q':
17730               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17731               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17732               board[CASTLING][5] = blackKingFile;
17733               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17734               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17735           case '-':
17736               break;
17737           default: /* FRC castlings */
17738               if(c >= 'a') { /* black rights */
17739                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17740                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17741                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17742                   if(i == BOARD_RGHT) break;
17743                   board[CASTLING][5] = i;
17744                   c -= AAA;
17745                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17746                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17747                   if(c > i)
17748                       board[CASTLING][3] = c;
17749                   else
17750                       board[CASTLING][4] = c;
17751               } else { /* white rights */
17752                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17753                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17754                     if(board[0][i] == WhiteKing) break;
17755                   if(i == BOARD_RGHT) break;
17756                   board[CASTLING][2] = i;
17757                   c -= AAA - 'a' + 'A';
17758                   if(board[0][c] >= WhiteKing) break;
17759                   if(c > i)
17760                       board[CASTLING][0] = c;
17761                   else
17762                       board[CASTLING][1] = c;
17763               }
17764         }
17765       }
17766       for(i=0; i<nrCastlingRights; i++)
17767         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17768       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17769     if (appData.debugMode) {
17770         fprintf(debugFP, "FEN castling rights:");
17771         for(i=0; i<nrCastlingRights; i++)
17772         fprintf(debugFP, " %d", board[CASTLING][i]);
17773         fprintf(debugFP, "\n");
17774     }
17775
17776       while(*p==' ') p++;
17777     }
17778
17779     /* read e.p. field in games that know e.p. capture */
17780     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17781        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17782        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17783       if(*p=='-') {
17784         p++; board[EP_STATUS] = EP_NONE;
17785       } else {
17786          char c = *p++ - AAA;
17787
17788          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17789          if(*p >= '0' && *p <='9') p++;
17790          board[EP_STATUS] = c;
17791       }
17792     }
17793
17794
17795     if(sscanf(p, "%d", &i) == 1) {
17796         FENrulePlies = i; /* 50-move ply counter */
17797         /* (The move number is still ignored)    */
17798     }
17799
17800     return TRUE;
17801 }
17802
17803 void
17804 EditPositionPasteFEN (char *fen)
17805 {
17806   if (fen != NULL) {
17807     Board initial_position;
17808
17809     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17810       DisplayError(_("Bad FEN position in clipboard"), 0);
17811       return ;
17812     } else {
17813       int savedBlackPlaysFirst = blackPlaysFirst;
17814       EditPositionEvent();
17815       blackPlaysFirst = savedBlackPlaysFirst;
17816       CopyBoard(boards[0], initial_position);
17817       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17818       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17819       DisplayBothClocks();
17820       DrawPosition(FALSE, boards[currentMove]);
17821     }
17822   }
17823 }
17824
17825 static char cseq[12] = "\\   ";
17826
17827 Boolean
17828 set_cont_sequence (char *new_seq)
17829 {
17830     int len;
17831     Boolean ret;
17832
17833     // handle bad attempts to set the sequence
17834         if (!new_seq)
17835                 return 0; // acceptable error - no debug
17836
17837     len = strlen(new_seq);
17838     ret = (len > 0) && (len < sizeof(cseq));
17839     if (ret)
17840       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17841     else if (appData.debugMode)
17842       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17843     return ret;
17844 }
17845
17846 /*
17847     reformat a source message so words don't cross the width boundary.  internal
17848     newlines are not removed.  returns the wrapped size (no null character unless
17849     included in source message).  If dest is NULL, only calculate the size required
17850     for the dest buffer.  lp argument indicats line position upon entry, and it's
17851     passed back upon exit.
17852 */
17853 int
17854 wrap (char *dest, char *src, int count, int width, int *lp)
17855 {
17856     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17857
17858     cseq_len = strlen(cseq);
17859     old_line = line = *lp;
17860     ansi = len = clen = 0;
17861
17862     for (i=0; i < count; i++)
17863     {
17864         if (src[i] == '\033')
17865             ansi = 1;
17866
17867         // if we hit the width, back up
17868         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17869         {
17870             // store i & len in case the word is too long
17871             old_i = i, old_len = len;
17872
17873             // find the end of the last word
17874             while (i && src[i] != ' ' && src[i] != '\n')
17875             {
17876                 i--;
17877                 len--;
17878             }
17879
17880             // word too long?  restore i & len before splitting it
17881             if ((old_i-i+clen) >= width)
17882             {
17883                 i = old_i;
17884                 len = old_len;
17885             }
17886
17887             // extra space?
17888             if (i && src[i-1] == ' ')
17889                 len--;
17890
17891             if (src[i] != ' ' && src[i] != '\n')
17892             {
17893                 i--;
17894                 if (len)
17895                     len--;
17896             }
17897
17898             // now append the newline and continuation sequence
17899             if (dest)
17900                 dest[len] = '\n';
17901             len++;
17902             if (dest)
17903                 strncpy(dest+len, cseq, cseq_len);
17904             len += cseq_len;
17905             line = cseq_len;
17906             clen = cseq_len;
17907             continue;
17908         }
17909
17910         if (dest)
17911             dest[len] = src[i];
17912         len++;
17913         if (!ansi)
17914             line++;
17915         if (src[i] == '\n')
17916             line = 0;
17917         if (src[i] == 'm')
17918             ansi = 0;
17919     }
17920     if (dest && appData.debugMode)
17921     {
17922         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17923             count, width, line, len, *lp);
17924         show_bytes(debugFP, src, count);
17925         fprintf(debugFP, "\ndest: ");
17926         show_bytes(debugFP, dest, len);
17927         fprintf(debugFP, "\n");
17928     }
17929     *lp = dest ? line : old_line;
17930
17931     return len;
17932 }
17933
17934 // [HGM] vari: routines for shelving variations
17935 Boolean modeRestore = FALSE;
17936
17937 void
17938 PushInner (int firstMove, int lastMove)
17939 {
17940         int i, j, nrMoves = lastMove - firstMove;
17941
17942         // push current tail of game on stack
17943         savedResult[storedGames] = gameInfo.result;
17944         savedDetails[storedGames] = gameInfo.resultDetails;
17945         gameInfo.resultDetails = NULL;
17946         savedFirst[storedGames] = firstMove;
17947         savedLast [storedGames] = lastMove;
17948         savedFramePtr[storedGames] = framePtr;
17949         framePtr -= nrMoves; // reserve space for the boards
17950         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17951             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17952             for(j=0; j<MOVE_LEN; j++)
17953                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17954             for(j=0; j<2*MOVE_LEN; j++)
17955                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17956             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17957             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17958             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17959             pvInfoList[firstMove+i-1].depth = 0;
17960             commentList[framePtr+i] = commentList[firstMove+i];
17961             commentList[firstMove+i] = NULL;
17962         }
17963
17964         storedGames++;
17965         forwardMostMove = firstMove; // truncate game so we can start variation
17966 }
17967
17968 void
17969 PushTail (int firstMove, int lastMove)
17970 {
17971         if(appData.icsActive) { // only in local mode
17972                 forwardMostMove = currentMove; // mimic old ICS behavior
17973                 return;
17974         }
17975         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17976
17977         PushInner(firstMove, lastMove);
17978         if(storedGames == 1) GreyRevert(FALSE);
17979         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17980 }
17981
17982 void
17983 PopInner (Boolean annotate)
17984 {
17985         int i, j, nrMoves;
17986         char buf[8000], moveBuf[20];
17987
17988         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17989         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17990         nrMoves = savedLast[storedGames] - currentMove;
17991         if(annotate) {
17992                 int cnt = 10;
17993                 if(!WhiteOnMove(currentMove))
17994                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17995                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17996                 for(i=currentMove; i<forwardMostMove; i++) {
17997                         if(WhiteOnMove(i))
17998                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17999                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18000                         strcat(buf, moveBuf);
18001                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18002                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18003                 }
18004                 strcat(buf, ")");
18005         }
18006         for(i=1; i<=nrMoves; i++) { // copy last variation back
18007             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18008             for(j=0; j<MOVE_LEN; j++)
18009                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18010             for(j=0; j<2*MOVE_LEN; j++)
18011                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18012             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18013             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18014             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18015             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18016             commentList[currentMove+i] = commentList[framePtr+i];
18017             commentList[framePtr+i] = NULL;
18018         }
18019         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18020         framePtr = savedFramePtr[storedGames];
18021         gameInfo.result = savedResult[storedGames];
18022         if(gameInfo.resultDetails != NULL) {
18023             free(gameInfo.resultDetails);
18024       }
18025         gameInfo.resultDetails = savedDetails[storedGames];
18026         forwardMostMove = currentMove + nrMoves;
18027 }
18028
18029 Boolean
18030 PopTail (Boolean annotate)
18031 {
18032         if(appData.icsActive) return FALSE; // only in local mode
18033         if(!storedGames) return FALSE; // sanity
18034         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18035
18036         PopInner(annotate);
18037         if(currentMove < forwardMostMove) ForwardEvent(); else
18038         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18039
18040         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18041         return TRUE;
18042 }
18043
18044 void
18045 CleanupTail ()
18046 {       // remove all shelved variations
18047         int i;
18048         for(i=0; i<storedGames; i++) {
18049             if(savedDetails[i])
18050                 free(savedDetails[i]);
18051             savedDetails[i] = NULL;
18052         }
18053         for(i=framePtr; i<MAX_MOVES; i++) {
18054                 if(commentList[i]) free(commentList[i]);
18055                 commentList[i] = NULL;
18056         }
18057         framePtr = MAX_MOVES-1;
18058         storedGames = 0;
18059 }
18060
18061 void
18062 LoadVariation (int index, char *text)
18063 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18064         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18065         int level = 0, move;
18066
18067         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18068         // first find outermost bracketing variation
18069         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18070             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18071                 if(*p == '{') wait = '}'; else
18072                 if(*p == '[') wait = ']'; else
18073                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18074                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18075             }
18076             if(*p == wait) wait = NULLCHAR; // closing ]} found
18077             p++;
18078         }
18079         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18080         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18081         end[1] = NULLCHAR; // clip off comment beyond variation
18082         ToNrEvent(currentMove-1);
18083         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18084         // kludge: use ParsePV() to append variation to game
18085         move = currentMove;
18086         ParsePV(start, TRUE, TRUE);
18087         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18088         ClearPremoveHighlights();
18089         CommentPopDown();
18090         ToNrEvent(currentMove+1);
18091 }
18092
18093 void
18094 LoadTheme ()
18095 {
18096     char *p, *q, buf[MSG_SIZ];
18097     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18098         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18099         ParseArgsFromString(buf);
18100         ActivateTheme(TRUE); // also redo colors
18101         return;
18102     }
18103     p = nickName;
18104     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18105     {
18106         int len;
18107         q = appData.themeNames;
18108         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18109       if(appData.useBitmaps) {
18110         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18111                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18112                 appData.liteBackTextureMode,
18113                 appData.darkBackTextureMode );
18114       } else {
18115         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18116                 Col2Text(2),   // lightSquareColor
18117                 Col2Text(3) ); // darkSquareColor
18118       }
18119       if(appData.useBorder) {
18120         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18121                 appData.border);
18122       } else {
18123         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18124       }
18125       if(appData.useFont) {
18126         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18127                 appData.renderPiecesWithFont,
18128                 appData.fontToPieceTable,
18129                 Col2Text(9),    // appData.fontBackColorWhite
18130                 Col2Text(10) ); // appData.fontForeColorBlack
18131       } else {
18132         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18133                 appData.pieceDirectory);
18134         if(!appData.pieceDirectory[0])
18135           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18136                 Col2Text(0),   // whitePieceColor
18137                 Col2Text(1) ); // blackPieceColor
18138       }
18139       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18140                 Col2Text(4),   // highlightSquareColor
18141                 Col2Text(5) ); // premoveHighlightColor
18142         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18143         if(insert != q) insert[-1] = NULLCHAR;
18144         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18145         if(q)   free(q);
18146     }
18147     ActivateTheme(FALSE);
18148 }