Fix hover event (again)
[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((VariantClass v, int w, int h, int s));
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 static int initPing = -1;
279
280 /* States for ics_getting_history */
281 #define H_FALSE 0
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
287
288 /* whosays values for GameEnds */
289 #define GE_ICS 0
290 #define GE_ENGINE 1
291 #define GE_PLAYER 2
292 #define GE_FILE 3
293 #define GE_XBOARD 4
294 #define GE_ENGINE1 5
295 #define GE_ENGINE2 6
296
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
299
300 /* Different types of move when calling RegisterMove */
301 #define CMAIL_MOVE   0
302 #define CMAIL_RESIGN 1
303 #define CMAIL_DRAW   2
304 #define CMAIL_ACCEPT 3
305
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
310
311 /* Telnet protocol constants */
312 #define TN_WILL 0373
313 #define TN_WONT 0374
314 #define TN_DO   0375
315 #define TN_DONT 0376
316 #define TN_IAC  0377
317 #define TN_ECHO 0001
318 #define TN_SGA  0003
319 #define TN_PORT 23
320
321 char*
322 safeStrCpy (char *dst, const char *src, size_t count)
323 { // [HGM] made safe
324   int i;
325   assert( dst != NULL );
326   assert( src != NULL );
327   assert( count > 0 );
328
329   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330   if(  i == count && dst[count-1] != NULLCHAR)
331     {
332       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333       if(appData.debugMode)
334         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335     }
336
337   return dst;
338 }
339
340 /* Some compiler can't cast u64 to double
341  * This function do the job for us:
342
343  * We use the highest bit for cast, this only
344  * works if the highest bit is not
345  * in use (This should not happen)
346  *
347  * We used this for all compiler
348  */
349 double
350 u64ToDouble (u64 value)
351 {
352   double r;
353   u64 tmp = value & u64Const(0x7fffffffffffffff);
354   r = (double)(s64)tmp;
355   if (value & u64Const(0x8000000000000000))
356        r +=  9.2233720368547758080e18; /* 2^63 */
357  return r;
358 }
359
360 /* Fake up flags for now, as we aren't keeping track of castling
361    availability yet. [HGM] Change of logic: the flag now only
362    indicates the type of castlings allowed by the rule of the game.
363    The actual rights themselves are maintained in the array
364    castlingRights, as part of the game history, and are not probed
365    by this function.
366  */
367 int
368 PosFlags (index)
369 {
370   int flags = F_ALL_CASTLE_OK;
371   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372   switch (gameInfo.variant) {
373   case VariantSuicide:
374     flags &= ~F_ALL_CASTLE_OK;
375   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376     flags |= F_IGNORE_CHECK;
377   case VariantLosers:
378     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379     break;
380   case VariantAtomic:
381     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382     break;
383   case VariantKriegspiel:
384     flags |= F_KRIEGSPIEL_CAPTURE;
385     break;
386   case VariantCapaRandom:
387   case VariantFischeRandom:
388     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389   case VariantNoCastle:
390   case VariantShatranj:
391   case VariantCourier:
392   case VariantMakruk:
393   case VariantASEAN:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second, pairing;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackMan, BlackFerz,
567         BlackKing, BlackMan, BlackKnight, BlackRook }
568 };
569
570 ChessSquare  lionArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackLion, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackKnight, BlackRook }
575 };
576
577
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
584 };
585
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
591 };
592
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
598 };
599
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
605 };
606
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
612 };
613
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
619 };
620
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
626 };
627
628 #ifdef GOTHIC
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
634 };
635 #else // !GOTHIC
636 #define GothicArray CapablancaArray
637 #endif // !GOTHIC
638
639 #ifdef FALCON
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
645 };
646 #else // !FALCON
647 #define FalconArray CapablancaArray
648 #endif // !FALCON
649
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
656
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
663 };
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
677 };
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
682
683
684 Board initialPosition;
685
686
687 /* Convert str to a rating. Checks for special cases of "----",
688
689    "++++", etc. Also strips ()'s */
690 int
691 string_to_rating (char *str)
692 {
693   while(*str && !isdigit(*str)) ++str;
694   if (!*str)
695     return 0;   /* One of the special "no rating" cases */
696   else
697     return atoi(str);
698 }
699
700 void
701 ClearProgramStats ()
702 {
703     /* Init programStats */
704     programStats.movelist[0] = 0;
705     programStats.depth = 0;
706     programStats.nr_moves = 0;
707     programStats.moves_left = 0;
708     programStats.nodes = 0;
709     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
710     programStats.score = 0;
711     programStats.got_only_move = 0;
712     programStats.got_fail = 0;
713     programStats.line_is_book = 0;
714 }
715
716 void
717 CommonEngineInit ()
718 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719     if (appData.firstPlaysBlack) {
720         first.twoMachinesColor = "black\n";
721         second.twoMachinesColor = "white\n";
722     } else {
723         first.twoMachinesColor = "white\n";
724         second.twoMachinesColor = "black\n";
725     }
726
727     first.other = &second;
728     second.other = &first;
729
730     { float norm = 1;
731         if(appData.timeOddsMode) {
732             norm = appData.timeOdds[0];
733             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
734         }
735         first.timeOdds  = appData.timeOdds[0]/norm;
736         second.timeOdds = appData.timeOdds[1]/norm;
737     }
738
739     if(programVersion) free(programVersion);
740     if (appData.noChessProgram) {
741         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742         sprintf(programVersion, "%s", PACKAGE_STRING);
743     } else {
744       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
747     }
748 }
749
750 void
751 UnloadEngine (ChessProgramState *cps)
752 {
753         /* Kill off first chess program */
754         if (cps->isr != NULL)
755           RemoveInputSource(cps->isr);
756         cps->isr = NULL;
757
758         if (cps->pr != NoProc) {
759             ExitAnalyzeMode();
760             DoSleep( appData.delayBeforeQuit );
761             SendToProgram("quit\n", cps);
762             DoSleep( appData.delayAfterQuit );
763             DestroyChildProcess(cps->pr, cps->useSigterm);
764         }
765         cps->pr = NoProc;
766         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
767 }
768
769 void
770 ClearOptions (ChessProgramState *cps)
771 {
772     int i;
773     cps->nrOptions = cps->comboCnt = 0;
774     for(i=0; i<MAX_OPTIONS; i++) {
775         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776         cps->option[i].textValue = 0;
777     }
778 }
779
780 char *engineNames[] = {
781   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 N_("first"),
784   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
786 N_("second")
787 };
788
789 void
790 InitEngine (ChessProgramState *cps, int n)
791 {   // [HGM] all engine initialiation put in a function that does one engine
792
793     ClearOptions(cps);
794
795     cps->which = engineNames[n];
796     cps->maybeThinking = FALSE;
797     cps->pr = NoProc;
798     cps->isr = NULL;
799     cps->sendTime = 2;
800     cps->sendDrawOffers = 1;
801
802     cps->program = appData.chessProgram[n];
803     cps->host = appData.host[n];
804     cps->dir = appData.directory[n];
805     cps->initString = appData.engInitString[n];
806     cps->computerString = appData.computerString[n];
807     cps->useSigint  = TRUE;
808     cps->useSigterm = TRUE;
809     cps->reuse = appData.reuse[n];
810     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
811     cps->useSetboard = FALSE;
812     cps->useSAN = FALSE;
813     cps->usePing = FALSE;
814     cps->lastPing = 0;
815     cps->lastPong = 0;
816     cps->usePlayother = FALSE;
817     cps->useColors = TRUE;
818     cps->useUsermove = FALSE;
819     cps->sendICS = FALSE;
820     cps->sendName = appData.icsActive;
821     cps->sdKludge = FALSE;
822     cps->stKludge = FALSE;
823     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824     TidyProgramName(cps->program, cps->host, cps->tidy);
825     cps->matchWins = 0;
826     ASSIGN(cps->variants, appData.variant);
827     cps->analysisSupport = 2; /* detect */
828     cps->analyzing = FALSE;
829     cps->initDone = FALSE;
830     cps->reload = FALSE;
831
832     /* New features added by Tord: */
833     cps->useFEN960 = FALSE;
834     cps->useOOCastle = TRUE;
835     /* End of new features added by Tord. */
836     cps->fenOverride  = appData.fenOverride[n];
837
838     /* [HGM] time odds: set factor for each machine */
839     cps->timeOdds  = appData.timeOdds[n];
840
841     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842     cps->accumulateTC = appData.accumulateTC[n];
843     cps->maxNrOfSessions = 1;
844
845     /* [HGM] debug */
846     cps->debug = FALSE;
847
848     cps->supportsNPS = UNKNOWN;
849     cps->memSize = FALSE;
850     cps->maxCores = FALSE;
851     ASSIGN(cps->egtFormats, "");
852
853     /* [HGM] options */
854     cps->optionSettings  = appData.engOptions[n];
855
856     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857     cps->isUCI = appData.isUCI[n]; /* [AS] */
858     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
859     cps->highlight = 0;
860
861     if (appData.protocolVersion[n] > PROTOVER
862         || appData.protocolVersion[n] < 1)
863       {
864         char buf[MSG_SIZ];
865         int len;
866
867         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868                        appData.protocolVersion[n]);
869         if( (len >= MSG_SIZ) && appData.debugMode )
870           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
871
872         DisplayFatalError(buf, 0, 2);
873       }
874     else
875       {
876         cps->protocolVersion = appData.protocolVersion[n];
877       }
878
879     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
880     ParseFeatures(appData.featureDefaults, cps);
881 }
882
883 ChessProgramState *savCps;
884
885 GameMode oldMode;
886
887 void
888 LoadEngine ()
889 {
890     int i;
891     if(WaitForEngine(savCps, LoadEngine)) return;
892     CommonEngineInit(); // recalculate time odds
893     if(gameInfo.variant != StringToVariant(appData.variant)) {
894         // we changed variant when loading the engine; this forces us to reset
895         Reset(TRUE, savCps != &first);
896         oldMode = BeginningOfGame; // to prevent restoring old mode
897     }
898     InitChessProgram(savCps, FALSE);
899     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900     DisplayMessage("", "");
901     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
903     ThawUI();
904     SetGNUMode();
905     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
906 }
907
908 void
909 ReplaceEngine (ChessProgramState *cps, int n)
910 {
911     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
912     keepInfo = 1;
913     if(oldMode != BeginningOfGame) EditGameEvent();
914     keepInfo = 0;
915     UnloadEngine(cps);
916     appData.noChessProgram = FALSE;
917     appData.clockMode = TRUE;
918     InitEngine(cps, n);
919     UpdateLogos(TRUE);
920     if(n) return; // only startup first engine immediately; second can wait
921     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
922     LoadEngine();
923 }
924
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
927
928 static char resetOptions[] =
929         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
933
934 void
935 FloatToFront(char **list, char *engineLine)
936 {
937     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
938     int i=0;
939     if(appData.recentEngines <= 0) return;
940     TidyProgramName(engineLine, "localhost", tidy+1);
941     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942     strncpy(buf+1, *list, MSG_SIZ-50);
943     if(p = strstr(buf, tidy)) { // tidy name appears in list
944         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945         while(*p++ = *++q); // squeeze out
946     }
947     strcat(tidy, buf+1); // put list behind tidy name
948     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950     ASSIGN(*list, tidy+1);
951 }
952
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
954
955 void
956 Load (ChessProgramState *cps, int i)
957 {
958     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964         appData.firstProtocolVersion = PROTOVER;
965         ParseArgsFromString(buf);
966         SwapEngines(i);
967         ReplaceEngine(cps, i);
968         FloatToFront(&appData.recentEngineList, engineLine);
969         return;
970     }
971     p = engineName;
972     while(q = strchr(p, SLASH)) p = q+1;
973     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974     if(engineDir[0] != NULLCHAR) {
975         ASSIGN(appData.directory[i], engineDir); p = engineName;
976     } else if(p != engineName) { // derive directory from engine path, when not given
977         p[-1] = 0;
978         ASSIGN(appData.directory[i], engineName);
979         p[-1] = SLASH;
980         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981     } else { ASSIGN(appData.directory[i], "."); }
982     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
983     if(params[0]) {
984         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985         snprintf(command, MSG_SIZ, "%s %s", p, params);
986         p = command;
987     }
988     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989     ASSIGN(appData.chessProgram[i], p);
990     appData.isUCI[i] = isUCI;
991     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992     appData.hasOwnBookUCI[i] = hasBook;
993     if(!nickName[0]) useNick = FALSE;
994     if(useNick) ASSIGN(appData.pgnName[i], nickName);
995     if(addToList) {
996         int len;
997         char quote;
998         q = firstChessProgramNames;
999         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002                         quote, p, quote, appData.directory[i],
1003                         useNick ? " -fn \"" : "",
1004                         useNick ? nickName : "",
1005                         useNick ? "\"" : "",
1006                         v1 ? " -firstProtocolVersion 1" : "",
1007                         hasBook ? "" : " -fNoOwnBookUCI",
1008                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009                         storeVariant ? " -variant " : "",
1010                         storeVariant ? VariantName(gameInfo.variant) : "");
1011         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013         if(insert != q) insert[-1] = NULLCHAR;
1014         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1015         if(q)   free(q);
1016         FloatToFront(&appData.recentEngineList, buf);
1017     }
1018     ReplaceEngine(cps, i);
1019 }
1020
1021 void
1022 InitTimeControls ()
1023 {
1024     int matched, min, sec;
1025     /*
1026      * Parse timeControl resource
1027      */
1028     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029                           appData.movesPerSession)) {
1030         char buf[MSG_SIZ];
1031         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032         DisplayFatalError(buf, 0, 2);
1033     }
1034
1035     /*
1036      * Parse searchTime resource
1037      */
1038     if (*appData.searchTime != NULLCHAR) {
1039         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1040         if (matched == 1) {
1041             searchTime = min * 60;
1042         } else if (matched == 2) {
1043             searchTime = min * 60 + sec;
1044         } else {
1045             char buf[MSG_SIZ];
1046             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047             DisplayFatalError(buf, 0, 2);
1048         }
1049     }
1050 }
1051
1052 void
1053 InitBackEnd1 ()
1054 {
1055
1056     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1058
1059     GetTimeMark(&programStartTime);
1060     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061     appData.seedBase = random() + (random()<<15);
1062     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1063
1064     ClearProgramStats();
1065     programStats.ok_to_send = 1;
1066     programStats.seen_stat = 0;
1067
1068     /*
1069      * Initialize game list
1070      */
1071     ListNew(&gameList);
1072
1073
1074     /*
1075      * Internet chess server status
1076      */
1077     if (appData.icsActive) {
1078         appData.matchMode = FALSE;
1079         appData.matchGames = 0;
1080 #if ZIPPY
1081         appData.noChessProgram = !appData.zippyPlay;
1082 #else
1083         appData.zippyPlay = FALSE;
1084         appData.zippyTalk = FALSE;
1085         appData.noChessProgram = TRUE;
1086 #endif
1087         if (*appData.icsHelper != NULLCHAR) {
1088             appData.useTelnet = TRUE;
1089             appData.telnetProgram = appData.icsHelper;
1090         }
1091     } else {
1092         appData.zippyTalk = appData.zippyPlay = FALSE;
1093     }
1094
1095     /* [AS] Initialize pv info list [HGM] and game state */
1096     {
1097         int i, j;
1098
1099         for( i=0; i<=framePtr; i++ ) {
1100             pvInfoList[i].depth = -1;
1101             boards[i][EP_STATUS] = EP_NONE;
1102             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1103         }
1104     }
1105
1106     InitTimeControls();
1107
1108     /* [AS] Adjudication threshold */
1109     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1110
1111     InitEngine(&first, 0);
1112     InitEngine(&second, 1);
1113     CommonEngineInit();
1114
1115     pairing.which = "pairing"; // pairing engine
1116     pairing.pr = NoProc;
1117     pairing.isr = NULL;
1118     pairing.program = appData.pairingEngine;
1119     pairing.host = "localhost";
1120     pairing.dir = ".";
1121
1122     if (appData.icsActive) {
1123         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1124     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125         appData.clockMode = FALSE;
1126         first.sendTime = second.sendTime = 0;
1127     }
1128
1129 #if ZIPPY
1130     /* Override some settings from environment variables, for backward
1131        compatibility.  Unfortunately it's not feasible to have the env
1132        vars just set defaults, at least in xboard.  Ugh.
1133     */
1134     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1135       ZippyInit();
1136     }
1137 #endif
1138
1139     if (!appData.icsActive) {
1140       char buf[MSG_SIZ];
1141       int len;
1142
1143       /* Check for variants that are supported only in ICS mode,
1144          or not at all.  Some that are accepted here nevertheless
1145          have bugs; see comments below.
1146       */
1147       VariantClass variant = StringToVariant(appData.variant);
1148       switch (variant) {
1149       case VariantBughouse:     /* need four players and two boards */
1150       case VariantKriegspiel:   /* need to hide pieces and move details */
1151         /* case VariantFischeRandom: (Fabien: moved below) */
1152         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153         if( (len >= MSG_SIZ) && appData.debugMode )
1154           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1155
1156         DisplayFatalError(buf, 0, 2);
1157         return;
1158
1159       case VariantUnknown:
1160       case VariantLoadable:
1161       case Variant29:
1162       case Variant30:
1163       case Variant31:
1164       case Variant32:
1165       case Variant33:
1166       case Variant34:
1167       case Variant35:
1168       case Variant36:
1169       default:
1170         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171         if( (len >= MSG_SIZ) && appData.debugMode )
1172           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1173
1174         DisplayFatalError(buf, 0, 2);
1175         return;
1176
1177       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1178       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1179       case VariantGothic:     /* [HGM] should work */
1180       case VariantCapablanca: /* [HGM] should work */
1181       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1182       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1183       case VariantChu:        /* [HGM] experimental */
1184       case VariantKnightmate: /* [HGM] should work */
1185       case VariantCylinder:   /* [HGM] untested */
1186       case VariantFalcon:     /* [HGM] untested */
1187       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188                                  offboard interposition not understood */
1189       case VariantNormal:     /* definitely works! */
1190       case VariantWildCastle: /* pieces not automatically shuffled */
1191       case VariantNoCastle:   /* pieces not automatically shuffled */
1192       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193       case VariantLosers:     /* should work except for win condition,
1194                                  and doesn't know captures are mandatory */
1195       case VariantSuicide:    /* should work except for win condition,
1196                                  and doesn't know captures are mandatory */
1197       case VariantGiveaway:   /* should work except for win condition,
1198                                  and doesn't know captures are mandatory */
1199       case VariantTwoKings:   /* should work */
1200       case VariantAtomic:     /* should work except for win condition */
1201       case Variant3Check:     /* should work except for win condition */
1202       case VariantShatranj:   /* should work except for all win conditions */
1203       case VariantMakruk:     /* should work except for draw countdown */
1204       case VariantASEAN :     /* should work except for draw countdown */
1205       case VariantBerolina:   /* might work if TestLegality is off */
1206       case VariantCapaRandom: /* should work */
1207       case VariantJanus:      /* should work */
1208       case VariantSuper:      /* experimental */
1209       case VariantGreat:      /* experimental, requires legality testing to be off */
1210       case VariantSChess:     /* S-Chess, should work */
1211       case VariantGrand:      /* should work */
1212       case VariantSpartan:    /* should work */
1213       case VariantLion:       /* should work */
1214       case VariantChuChess:   /* should work */
1215         break;
1216       }
1217     }
1218
1219 }
1220
1221 int
1222 NextIntegerFromString (char ** str, long * value)
1223 {
1224     int result = -1;
1225     char * s = *str;
1226
1227     while( *s == ' ' || *s == '\t' ) {
1228         s++;
1229     }
1230
1231     *value = 0;
1232
1233     if( *s >= '0' && *s <= '9' ) {
1234         while( *s >= '0' && *s <= '9' ) {
1235             *value = *value * 10 + (*s - '0');
1236             s++;
1237         }
1238
1239         result = 0;
1240     }
1241
1242     *str = s;
1243
1244     return result;
1245 }
1246
1247 int
1248 NextTimeControlFromString (char ** str, long * value)
1249 {
1250     long temp;
1251     int result = NextIntegerFromString( str, &temp );
1252
1253     if( result == 0 ) {
1254         *value = temp * 60; /* Minutes */
1255         if( **str == ':' ) {
1256             (*str)++;
1257             result = NextIntegerFromString( str, &temp );
1258             *value += temp; /* Seconds */
1259         }
1260     }
1261
1262     return result;
1263 }
1264
1265 int
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268     int result = -1, type = 0; long temp, temp2;
1269
1270     if(**str != ':') return -1; // old params remain in force!
1271     (*str)++;
1272     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273     if( NextIntegerFromString( str, &temp ) ) return -1;
1274     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1275
1276     if(**str != '/') {
1277         /* time only: incremental or sudden-death time control */
1278         if(**str == '+') { /* increment follows; read it */
1279             (*str)++;
1280             if(**str == '!') type = *(*str)++; // Bronstein TC
1281             if(result = NextIntegerFromString( str, &temp2)) return -1;
1282             *inc = temp2 * 1000;
1283             if(**str == '.') { // read fraction of increment
1284                 char *start = ++(*str);
1285                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1286                 temp2 *= 1000;
1287                 while(start++ < *str) temp2 /= 10;
1288                 *inc += temp2;
1289             }
1290         } else *inc = 0;
1291         *moves = 0; *tc = temp * 1000; *incType = type;
1292         return 0;
1293     }
1294
1295     (*str)++; /* classical time control */
1296     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1297
1298     if(result == 0) {
1299         *moves = temp;
1300         *tc    = temp2 * 1000;
1301         *inc   = 0;
1302         *incType = type;
1303     }
1304     return result;
1305 }
1306
1307 int
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 {   /* [HGM] get time to add from the multi-session time-control string */
1310     int incType, moves=1; /* kludge to force reading of first session */
1311     long time, increment;
1312     char *s = tcString;
1313
1314     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1315     do {
1316         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318         if(movenr == -1) return time;    /* last move before new session     */
1319         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321         if(!moves) return increment;     /* current session is incremental   */
1322         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323     } while(movenr >= -1);               /* try again for next session       */
1324
1325     return 0; // no new time quota on this move
1326 }
1327
1328 int
1329 ParseTimeControl (char *tc, float ti, int mps)
1330 {
1331   long tc1;
1332   long tc2;
1333   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1334   int min, sec=0;
1335
1336   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1339   if(ti > 0) {
1340
1341     if(mps)
1342       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1343     else
1344       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1345   } else {
1346     if(mps)
1347       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1348     else
1349       snprintf(buf, MSG_SIZ, ":%s", mytc);
1350   }
1351   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1352
1353   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1354     return FALSE;
1355   }
1356
1357   if( *tc == '/' ) {
1358     /* Parse second time control */
1359     tc++;
1360
1361     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1362       return FALSE;
1363     }
1364
1365     if( tc2 == 0 ) {
1366       return FALSE;
1367     }
1368
1369     timeControl_2 = tc2 * 1000;
1370   }
1371   else {
1372     timeControl_2 = 0;
1373   }
1374
1375   if( tc1 == 0 ) {
1376     return FALSE;
1377   }
1378
1379   timeControl = tc1 * 1000;
1380
1381   if (ti >= 0) {
1382     timeIncrement = ti * 1000;  /* convert to ms */
1383     movesPerSession = 0;
1384   } else {
1385     timeIncrement = 0;
1386     movesPerSession = mps;
1387   }
1388   return TRUE;
1389 }
1390
1391 void
1392 InitBackEnd2 ()
1393 {
1394     if (appData.debugMode) {
1395 #    ifdef __GIT_VERSION
1396       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1397 #    else
1398       fprintf(debugFP, "Version: %s\n", programVersion);
1399 #    endif
1400     }
1401     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1402
1403     set_cont_sequence(appData.wrapContSeq);
1404     if (appData.matchGames > 0) {
1405         appData.matchMode = TRUE;
1406     } else if (appData.matchMode) {
1407         appData.matchGames = 1;
1408     }
1409     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410         appData.matchGames = appData.sameColorGames;
1411     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1414     }
1415     Reset(TRUE, FALSE);
1416     if (appData.noChessProgram || first.protocolVersion == 1) {
1417       InitBackEnd3();
1418     } else {
1419       /* kludge: allow timeout for initial "feature" commands */
1420       FreezeUI();
1421       DisplayMessage("", _("Starting chess program"));
1422       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1423     }
1424 }
1425
1426 int
1427 CalculateIndex (int index, int gameNr)
1428 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1429     int res;
1430     if(index > 0) return index; // fixed nmber
1431     if(index == 0) return 1;
1432     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1434     return res;
1435 }
1436
1437 int
1438 LoadGameOrPosition (int gameNr)
1439 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440     if (*appData.loadGameFile != NULLCHAR) {
1441         if (!LoadGameFromFile(appData.loadGameFile,
1442                 CalculateIndex(appData.loadGameIndex, gameNr),
1443                               appData.loadGameFile, FALSE)) {
1444             DisplayFatalError(_("Bad game file"), 0, 1);
1445             return 0;
1446         }
1447     } else if (*appData.loadPositionFile != NULLCHAR) {
1448         if (!LoadPositionFromFile(appData.loadPositionFile,
1449                 CalculateIndex(appData.loadPositionIndex, gameNr),
1450                                   appData.loadPositionFile)) {
1451             DisplayFatalError(_("Bad position file"), 0, 1);
1452             return 0;
1453         }
1454     }
1455     return 1;
1456 }
1457
1458 void
1459 ReserveGame (int gameNr, char resChar)
1460 {
1461     FILE *tf = fopen(appData.tourneyFile, "r+");
1462     char *p, *q, c, buf[MSG_SIZ];
1463     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464     safeStrCpy(buf, lastMsg, MSG_SIZ);
1465     DisplayMessage(_("Pick new game"), "");
1466     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467     ParseArgsFromFile(tf);
1468     p = q = appData.results;
1469     if(appData.debugMode) {
1470       char *r = appData.participants;
1471       fprintf(debugFP, "results = '%s'\n", p);
1472       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473       fprintf(debugFP, "\n");
1474     }
1475     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1476     nextGame = q - p;
1477     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478     safeStrCpy(q, p, strlen(p) + 2);
1479     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1483         q[nextGame] = '*';
1484     }
1485     fseek(tf, -(strlen(p)+4), SEEK_END);
1486     c = fgetc(tf);
1487     if(c != '"') // depending on DOS or Unix line endings we can be one off
1488          fseek(tf, -(strlen(p)+2), SEEK_END);
1489     else fseek(tf, -(strlen(p)+3), SEEK_END);
1490     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491     DisplayMessage(buf, "");
1492     free(p); appData.results = q;
1493     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495       int round = appData.defaultMatchGames * appData.tourneyType;
1496       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1497          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498         UnloadEngine(&first);  // next game belongs to other pairing;
1499         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1500     }
1501     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1502 }
1503
1504 void
1505 MatchEvent (int mode)
1506 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1507         int dummy;
1508         if(matchMode) { // already in match mode: switch it off
1509             abortMatch = TRUE;
1510             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1511             return;
1512         }
1513 //      if(gameMode != BeginningOfGame) {
1514 //          DisplayError(_("You can only start a match from the initial position."), 0);
1515 //          return;
1516 //      }
1517         abortMatch = FALSE;
1518         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519         /* Set up machine vs. machine match */
1520         nextGame = 0;
1521         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522         if(appData.tourneyFile[0]) {
1523             ReserveGame(-1, 0);
1524             if(nextGame > appData.matchGames) {
1525                 char buf[MSG_SIZ];
1526                 if(strchr(appData.results, '*') == NULL) {
1527                     FILE *f;
1528                     appData.tourneyCycles++;
1529                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1530                         fclose(f);
1531                         NextTourneyGame(-1, &dummy);
1532                         ReserveGame(-1, 0);
1533                         if(nextGame <= appData.matchGames) {
1534                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1535                             matchMode = mode;
1536                             ScheduleDelayedEvent(NextMatchGame, 10000);
1537                             return;
1538                         }
1539                     }
1540                 }
1541                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542                 DisplayError(buf, 0);
1543                 appData.tourneyFile[0] = 0;
1544                 return;
1545             }
1546         } else
1547         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1548             DisplayFatalError(_("Can't have a match with no chess programs"),
1549                               0, 2);
1550             return;
1551         }
1552         matchMode = mode;
1553         matchGame = roundNr = 1;
1554         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1555         NextMatchGame();
1556 }
1557
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1559
1560 void
1561 InitBackEnd3 P((void))
1562 {
1563     GameMode initialMode;
1564     char buf[MSG_SIZ];
1565     int err, len;
1566
1567     InitChessProgram(&first, startedFromSetupPosition);
1568
1569     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1570         free(programVersion);
1571         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1572         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1573         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1574     }
1575
1576     if (appData.icsActive) {
1577 #ifdef WIN32
1578         /* [DM] Make a console window if needed [HGM] merged ifs */
1579         ConsoleCreate();
1580 #endif
1581         err = establish();
1582         if (err != 0)
1583           {
1584             if (*appData.icsCommPort != NULLCHAR)
1585               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1586                              appData.icsCommPort);
1587             else
1588               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1589                         appData.icsHost, appData.icsPort);
1590
1591             if( (len >= MSG_SIZ) && appData.debugMode )
1592               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1593
1594             DisplayFatalError(buf, err, 1);
1595             return;
1596         }
1597         SetICSMode();
1598         telnetISR =
1599           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1600         fromUserISR =
1601           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1602         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1603             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1604     } else if (appData.noChessProgram) {
1605         SetNCPMode();
1606     } else {
1607         SetGNUMode();
1608     }
1609
1610     if (*appData.cmailGameName != NULLCHAR) {
1611         SetCmailMode();
1612         OpenLoopback(&cmailPR);
1613         cmailISR =
1614           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1615     }
1616
1617     ThawUI();
1618     DisplayMessage("", "");
1619     if (StrCaseCmp(appData.initialMode, "") == 0) {
1620       initialMode = BeginningOfGame;
1621       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1622         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1623         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1624         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1625         ModeHighlight();
1626       }
1627     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1628       initialMode = TwoMachinesPlay;
1629     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1630       initialMode = AnalyzeFile;
1631     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1632       initialMode = AnalyzeMode;
1633     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1634       initialMode = MachinePlaysWhite;
1635     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1636       initialMode = MachinePlaysBlack;
1637     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1638       initialMode = EditGame;
1639     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1640       initialMode = EditPosition;
1641     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1642       initialMode = Training;
1643     } else {
1644       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1645       if( (len >= MSG_SIZ) && appData.debugMode )
1646         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648       DisplayFatalError(buf, 0, 2);
1649       return;
1650     }
1651
1652     if (appData.matchMode) {
1653         if(appData.tourneyFile[0]) { // start tourney from command line
1654             FILE *f;
1655             if(f = fopen(appData.tourneyFile, "r")) {
1656                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1657                 fclose(f);
1658                 appData.clockMode = TRUE;
1659                 SetGNUMode();
1660             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1661         }
1662         MatchEvent(TRUE);
1663     } else if (*appData.cmailGameName != NULLCHAR) {
1664         /* Set up cmail mode */
1665         ReloadCmailMsgEvent(TRUE);
1666     } else {
1667         /* Set up other modes */
1668         if (initialMode == AnalyzeFile) {
1669           if (*appData.loadGameFile == NULLCHAR) {
1670             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1671             return;
1672           }
1673         }
1674         if (*appData.loadGameFile != NULLCHAR) {
1675             (void) LoadGameFromFile(appData.loadGameFile,
1676                                     appData.loadGameIndex,
1677                                     appData.loadGameFile, TRUE);
1678         } else if (*appData.loadPositionFile != NULLCHAR) {
1679             (void) LoadPositionFromFile(appData.loadPositionFile,
1680                                         appData.loadPositionIndex,
1681                                         appData.loadPositionFile);
1682             /* [HGM] try to make self-starting even after FEN load */
1683             /* to allow automatic setup of fairy variants with wtm */
1684             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1685                 gameMode = BeginningOfGame;
1686                 setboardSpoiledMachineBlack = 1;
1687             }
1688             /* [HGM] loadPos: make that every new game uses the setup */
1689             /* from file as long as we do not switch variant          */
1690             if(!blackPlaysFirst) {
1691                 startedFromPositionFile = TRUE;
1692                 CopyBoard(filePosition, boards[0]);
1693             }
1694         }
1695         if (initialMode == AnalyzeMode) {
1696           if (appData.noChessProgram) {
1697             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1698             return;
1699           }
1700           if (appData.icsActive) {
1701             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1702             return;
1703           }
1704           AnalyzeModeEvent();
1705         } else if (initialMode == AnalyzeFile) {
1706           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1707           ShowThinkingEvent();
1708           AnalyzeFileEvent();
1709           AnalysisPeriodicEvent(1);
1710         } else if (initialMode == MachinePlaysWhite) {
1711           if (appData.noChessProgram) {
1712             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1713                               0, 2);
1714             return;
1715           }
1716           if (appData.icsActive) {
1717             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1718                               0, 2);
1719             return;
1720           }
1721           MachineWhiteEvent();
1722         } else if (initialMode == MachinePlaysBlack) {
1723           if (appData.noChessProgram) {
1724             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1725                               0, 2);
1726             return;
1727           }
1728           if (appData.icsActive) {
1729             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1730                               0, 2);
1731             return;
1732           }
1733           MachineBlackEvent();
1734         } else if (initialMode == TwoMachinesPlay) {
1735           if (appData.noChessProgram) {
1736             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1737                               0, 2);
1738             return;
1739           }
1740           if (appData.icsActive) {
1741             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1742                               0, 2);
1743             return;
1744           }
1745           TwoMachinesEvent();
1746         } else if (initialMode == EditGame) {
1747           EditGameEvent();
1748         } else if (initialMode == EditPosition) {
1749           EditPositionEvent();
1750         } else if (initialMode == Training) {
1751           if (*appData.loadGameFile == NULLCHAR) {
1752             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1753             return;
1754           }
1755           TrainingEvent();
1756         }
1757     }
1758 }
1759
1760 void
1761 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1762 {
1763     DisplayBook(current+1);
1764
1765     MoveHistorySet( movelist, first, last, current, pvInfoList );
1766
1767     EvalGraphSet( first, last, current, pvInfoList );
1768
1769     MakeEngineOutputTitle();
1770 }
1771
1772 /*
1773  * Establish will establish a contact to a remote host.port.
1774  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1775  *  used to talk to the host.
1776  * Returns 0 if okay, error code if not.
1777  */
1778 int
1779 establish ()
1780 {
1781     char buf[MSG_SIZ];
1782
1783     if (*appData.icsCommPort != NULLCHAR) {
1784         /* Talk to the host through a serial comm port */
1785         return OpenCommPort(appData.icsCommPort, &icsPR);
1786
1787     } else if (*appData.gateway != NULLCHAR) {
1788         if (*appData.remoteShell == NULLCHAR) {
1789             /* Use the rcmd protocol to run telnet program on a gateway host */
1790             snprintf(buf, sizeof(buf), "%s %s %s",
1791                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1792             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1793
1794         } else {
1795             /* Use the rsh program to run telnet program on a gateway host */
1796             if (*appData.remoteUser == NULLCHAR) {
1797                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1798                         appData.gateway, appData.telnetProgram,
1799                         appData.icsHost, appData.icsPort);
1800             } else {
1801                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1802                         appData.remoteShell, appData.gateway,
1803                         appData.remoteUser, appData.telnetProgram,
1804                         appData.icsHost, appData.icsPort);
1805             }
1806             return StartChildProcess(buf, "", &icsPR);
1807
1808         }
1809     } else if (appData.useTelnet) {
1810         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1811
1812     } else {
1813         /* TCP socket interface differs somewhat between
1814            Unix and NT; handle details in the front end.
1815            */
1816         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1817     }
1818 }
1819
1820 void
1821 EscapeExpand (char *p, char *q)
1822 {       // [HGM] initstring: routine to shape up string arguments
1823         while(*p++ = *q++) if(p[-1] == '\\')
1824             switch(*q++) {
1825                 case 'n': p[-1] = '\n'; break;
1826                 case 'r': p[-1] = '\r'; break;
1827                 case 't': p[-1] = '\t'; break;
1828                 case '\\': p[-1] = '\\'; break;
1829                 case 0: *p = 0; return;
1830                 default: p[-1] = q[-1]; break;
1831             }
1832 }
1833
1834 void
1835 show_bytes (FILE *fp, char *buf, int count)
1836 {
1837     while (count--) {
1838         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1839             fprintf(fp, "\\%03o", *buf & 0xff);
1840         } else {
1841             putc(*buf, fp);
1842         }
1843         buf++;
1844     }
1845     fflush(fp);
1846 }
1847
1848 /* Returns an errno value */
1849 int
1850 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1851 {
1852     char buf[8192], *p, *q, *buflim;
1853     int left, newcount, outcount;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1856         *appData.gateway != NULLCHAR) {
1857         if (appData.debugMode) {
1858             fprintf(debugFP, ">ICS: ");
1859             show_bytes(debugFP, message, count);
1860             fprintf(debugFP, "\n");
1861         }
1862         return OutputToProcess(pr, message, count, outError);
1863     }
1864
1865     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1866     p = message;
1867     q = buf;
1868     left = count;
1869     newcount = 0;
1870     while (left) {
1871         if (q >= buflim) {
1872             if (appData.debugMode) {
1873                 fprintf(debugFP, ">ICS: ");
1874                 show_bytes(debugFP, buf, newcount);
1875                 fprintf(debugFP, "\n");
1876             }
1877             outcount = OutputToProcess(pr, buf, newcount, outError);
1878             if (outcount < newcount) return -1; /* to be sure */
1879             q = buf;
1880             newcount = 0;
1881         }
1882         if (*p == '\n') {
1883             *q++ = '\r';
1884             newcount++;
1885         } else if (((unsigned char) *p) == TN_IAC) {
1886             *q++ = (char) TN_IAC;
1887             newcount ++;
1888         }
1889         *q++ = *p++;
1890         newcount++;
1891         left--;
1892     }
1893     if (appData.debugMode) {
1894         fprintf(debugFP, ">ICS: ");
1895         show_bytes(debugFP, buf, newcount);
1896         fprintf(debugFP, "\n");
1897     }
1898     outcount = OutputToProcess(pr, buf, newcount, outError);
1899     if (outcount < newcount) return -1; /* to be sure */
1900     return count;
1901 }
1902
1903 void
1904 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1905 {
1906     int outError, outCount;
1907     static int gotEof = 0;
1908     static FILE *ini;
1909
1910     /* Pass data read from player on to ICS */
1911     if (count > 0) {
1912         gotEof = 0;
1913         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1914         if (outCount < count) {
1915             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916         }
1917         if(have_sent_ICS_logon == 2) {
1918           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1919             fprintf(ini, "%s", message);
1920             have_sent_ICS_logon = 3;
1921           } else
1922             have_sent_ICS_logon = 1;
1923         } else if(have_sent_ICS_logon == 3) {
1924             fprintf(ini, "%s", message);
1925             fclose(ini);
1926           have_sent_ICS_logon = 1;
1927         }
1928     } else if (count < 0) {
1929         RemoveInputSource(isr);
1930         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1931     } else if (gotEof++ > 0) {
1932         RemoveInputSource(isr);
1933         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1934     }
1935 }
1936
1937 void
1938 KeepAlive ()
1939 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1940     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1941     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1942     SendToICS("date\n");
1943     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1944 }
1945
1946 /* added routine for printf style output to ics */
1947 void
1948 ics_printf (char *format, ...)
1949 {
1950     char buffer[MSG_SIZ];
1951     va_list args;
1952
1953     va_start(args, format);
1954     vsnprintf(buffer, sizeof(buffer), format, args);
1955     buffer[sizeof(buffer)-1] = '\0';
1956     SendToICS(buffer);
1957     va_end(args);
1958 }
1959
1960 void
1961 SendToICS (char *s)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1969     if (outCount < count) {
1970         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1971     }
1972 }
1973
1974 /* This is used for sending logon scripts to the ICS. Sending
1975    without a delay causes problems when using timestamp on ICC
1976    (at least on my machine). */
1977 void
1978 SendToICSDelayed (char *s, long msdelay)
1979 {
1980     int count, outCount, outError;
1981
1982     if (icsPR == NoProc) return;
1983
1984     count = strlen(s);
1985     if (appData.debugMode) {
1986         fprintf(debugFP, ">ICS: ");
1987         show_bytes(debugFP, s, count);
1988         fprintf(debugFP, "\n");
1989     }
1990     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1991                                       msdelay);
1992     if (outCount < count) {
1993         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1994     }
1995 }
1996
1997
1998 /* Remove all highlighting escape sequences in s
1999    Also deletes any suffix starting with '('
2000    */
2001 char *
2002 StripHighlightAndTitle (char *s)
2003 {
2004     static char retbuf[MSG_SIZ];
2005     char *p = retbuf;
2006
2007     while (*s != NULLCHAR) {
2008         while (*s == '\033') {
2009             while (*s != NULLCHAR && !isalpha(*s)) s++;
2010             if (*s != NULLCHAR) s++;
2011         }
2012         while (*s != NULLCHAR && *s != '\033') {
2013             if (*s == '(' || *s == '[') {
2014                 *p = NULLCHAR;
2015                 return retbuf;
2016             }
2017             *p++ = *s++;
2018         }
2019     }
2020     *p = NULLCHAR;
2021     return retbuf;
2022 }
2023
2024 /* Remove all highlighting escape sequences in s */
2025 char *
2026 StripHighlight (char *s)
2027 {
2028     static char retbuf[MSG_SIZ];
2029     char *p = retbuf;
2030
2031     while (*s != NULLCHAR) {
2032         while (*s == '\033') {
2033             while (*s != NULLCHAR && !isalpha(*s)) s++;
2034             if (*s != NULLCHAR) s++;
2035         }
2036         while (*s != NULLCHAR && *s != '\033') {
2037             *p++ = *s++;
2038         }
2039     }
2040     *p = NULLCHAR;
2041     return retbuf;
2042 }
2043
2044 char engineVariant[MSG_SIZ];
2045 char *variantNames[] = VARIANT_NAMES;
2046 char *
2047 VariantName (VariantClass v)
2048 {
2049     if(v == VariantUnknown || *engineVariant) return engineVariant;
2050     return variantNames[v];
2051 }
2052
2053
2054 /* Identify a variant from the strings the chess servers use or the
2055    PGN Variant tag names we use. */
2056 VariantClass
2057 StringToVariant (char *e)
2058 {
2059     char *p;
2060     int wnum = -1;
2061     VariantClass v = VariantNormal;
2062     int i, found = FALSE;
2063     char buf[MSG_SIZ];
2064     int len;
2065
2066     if (!e) return v;
2067
2068     /* [HGM] skip over optional board-size prefixes */
2069     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2070         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2071         while( *e++ != '_');
2072     }
2073
2074     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2075         v = VariantNormal;
2076         found = TRUE;
2077     } else
2078     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2079       if (p = StrCaseStr(e, variantNames[i])) {
2080         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2081         v = (VariantClass) i;
2082         found = TRUE;
2083         break;
2084       }
2085     }
2086
2087     if (!found) {
2088       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2089           || StrCaseStr(e, "wild/fr")
2090           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2091         v = VariantFischeRandom;
2092       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2093                  (i = 1, p = StrCaseStr(e, "w"))) {
2094         p += i;
2095         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2096         if (isdigit(*p)) {
2097           wnum = atoi(p);
2098         } else {
2099           wnum = -1;
2100         }
2101         switch (wnum) {
2102         case 0: /* FICS only, actually */
2103         case 1:
2104           /* Castling legal even if K starts on d-file */
2105           v = VariantWildCastle;
2106           break;
2107         case 2:
2108         case 3:
2109         case 4:
2110           /* Castling illegal even if K & R happen to start in
2111              normal positions. */
2112           v = VariantNoCastle;
2113           break;
2114         case 5:
2115         case 7:
2116         case 8:
2117         case 10:
2118         case 11:
2119         case 12:
2120         case 13:
2121         case 14:
2122         case 15:
2123         case 18:
2124         case 19:
2125           /* Castling legal iff K & R start in normal positions */
2126           v = VariantNormal;
2127           break;
2128         case 6:
2129         case 20:
2130         case 21:
2131           /* Special wilds for position setup; unclear what to do here */
2132           v = VariantLoadable;
2133           break;
2134         case 9:
2135           /* Bizarre ICC game */
2136           v = VariantTwoKings;
2137           break;
2138         case 16:
2139           v = VariantKriegspiel;
2140           break;
2141         case 17:
2142           v = VariantLosers;
2143           break;
2144         case 22:
2145           v = VariantFischeRandom;
2146           break;
2147         case 23:
2148           v = VariantCrazyhouse;
2149           break;
2150         case 24:
2151           v = VariantBughouse;
2152           break;
2153         case 25:
2154           v = Variant3Check;
2155           break;
2156         case 26:
2157           /* Not quite the same as FICS suicide! */
2158           v = VariantGiveaway;
2159           break;
2160         case 27:
2161           v = VariantAtomic;
2162           break;
2163         case 28:
2164           v = VariantShatranj;
2165           break;
2166
2167         /* Temporary names for future ICC types.  The name *will* change in
2168            the next xboard/WinBoard release after ICC defines it. */
2169         case 29:
2170           v = Variant29;
2171           break;
2172         case 30:
2173           v = Variant30;
2174           break;
2175         case 31:
2176           v = Variant31;
2177           break;
2178         case 32:
2179           v = Variant32;
2180           break;
2181         case 33:
2182           v = Variant33;
2183           break;
2184         case 34:
2185           v = Variant34;
2186           break;
2187         case 35:
2188           v = Variant35;
2189           break;
2190         case 36:
2191           v = Variant36;
2192           break;
2193         case 37:
2194           v = VariantShogi;
2195           break;
2196         case 38:
2197           v = VariantXiangqi;
2198           break;
2199         case 39:
2200           v = VariantCourier;
2201           break;
2202         case 40:
2203           v = VariantGothic;
2204           break;
2205         case 41:
2206           v = VariantCapablanca;
2207           break;
2208         case 42:
2209           v = VariantKnightmate;
2210           break;
2211         case 43:
2212           v = VariantFairy;
2213           break;
2214         case 44:
2215           v = VariantCylinder;
2216           break;
2217         case 45:
2218           v = VariantFalcon;
2219           break;
2220         case 46:
2221           v = VariantCapaRandom;
2222           break;
2223         case 47:
2224           v = VariantBerolina;
2225           break;
2226         case 48:
2227           v = VariantJanus;
2228           break;
2229         case 49:
2230           v = VariantSuper;
2231           break;
2232         case 50:
2233           v = VariantGreat;
2234           break;
2235         case -1:
2236           /* Found "wild" or "w" in the string but no number;
2237              must assume it's normal chess. */
2238           v = VariantNormal;
2239           break;
2240         default:
2241           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2242           if( (len >= MSG_SIZ) && appData.debugMode )
2243             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2244
2245           DisplayError(buf, 0);
2246           v = VariantUnknown;
2247           break;
2248         }
2249       }
2250     }
2251     if (appData.debugMode) {
2252       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2253               e, wnum, VariantName(v));
2254     }
2255     return v;
2256 }
2257
2258 static int leftover_start = 0, leftover_len = 0;
2259 char star_match[STAR_MATCH_N][MSG_SIZ];
2260
2261 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2262    advance *index beyond it, and set leftover_start to the new value of
2263    *index; else return FALSE.  If pattern contains the character '*', it
2264    matches any sequence of characters not containing '\r', '\n', or the
2265    character following the '*' (if any), and the matched sequence(s) are
2266    copied into star_match.
2267    */
2268 int
2269 looking_at ( char *buf, int *index, char *pattern)
2270 {
2271     char *bufp = &buf[*index], *patternp = pattern;
2272     int star_count = 0;
2273     char *matchp = star_match[0];
2274
2275     for (;;) {
2276         if (*patternp == NULLCHAR) {
2277             *index = leftover_start = bufp - buf;
2278             *matchp = NULLCHAR;
2279             return TRUE;
2280         }
2281         if (*bufp == NULLCHAR) return FALSE;
2282         if (*patternp == '*') {
2283             if (*bufp == *(patternp + 1)) {
2284                 *matchp = NULLCHAR;
2285                 matchp = star_match[++star_count];
2286                 patternp += 2;
2287                 bufp++;
2288                 continue;
2289             } else if (*bufp == '\n' || *bufp == '\r') {
2290                 patternp++;
2291                 if (*patternp == NULLCHAR)
2292                   continue;
2293                 else
2294                   return FALSE;
2295             } else {
2296                 *matchp++ = *bufp++;
2297                 continue;
2298             }
2299         }
2300         if (*patternp != *bufp) return FALSE;
2301         patternp++;
2302         bufp++;
2303     }
2304 }
2305
2306 void
2307 SendToPlayer (char *data, int length)
2308 {
2309     int error, outCount;
2310     outCount = OutputToProcess(NoProc, data, length, &error);
2311     if (outCount < length) {
2312         DisplayFatalError(_("Error writing to display"), error, 1);
2313     }
2314 }
2315
2316 void
2317 PackHolding (char packed[], char *holding)
2318 {
2319     char *p = holding;
2320     char *q = packed;
2321     int runlength = 0;
2322     int curr = 9999;
2323     do {
2324         if (*p == curr) {
2325             runlength++;
2326         } else {
2327             switch (runlength) {
2328               case 0:
2329                 break;
2330               case 1:
2331                 *q++ = curr;
2332                 break;
2333               case 2:
2334                 *q++ = curr;
2335                 *q++ = curr;
2336                 break;
2337               default:
2338                 sprintf(q, "%d", runlength);
2339                 while (*q) q++;
2340                 *q++ = curr;
2341                 break;
2342             }
2343             runlength = 1;
2344             curr = *p;
2345         }
2346     } while (*p++);
2347     *q = NULLCHAR;
2348 }
2349
2350 /* Telnet protocol requests from the front end */
2351 void
2352 TelnetRequest (unsigned char ddww, unsigned char option)
2353 {
2354     unsigned char msg[3];
2355     int outCount, outError;
2356
2357     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2358
2359     if (appData.debugMode) {
2360         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2361         switch (ddww) {
2362           case TN_DO:
2363             ddwwStr = "DO";
2364             break;
2365           case TN_DONT:
2366             ddwwStr = "DONT";
2367             break;
2368           case TN_WILL:
2369             ddwwStr = "WILL";
2370             break;
2371           case TN_WONT:
2372             ddwwStr = "WONT";
2373             break;
2374           default:
2375             ddwwStr = buf1;
2376             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2377             break;
2378         }
2379         switch (option) {
2380           case TN_ECHO:
2381             optionStr = "ECHO";
2382             break;
2383           default:
2384             optionStr = buf2;
2385             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2386             break;
2387         }
2388         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2389     }
2390     msg[0] = TN_IAC;
2391     msg[1] = ddww;
2392     msg[2] = option;
2393     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2394     if (outCount < 3) {
2395         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2396     }
2397 }
2398
2399 void
2400 DoEcho ()
2401 {
2402     if (!appData.icsActive) return;
2403     TelnetRequest(TN_DO, TN_ECHO);
2404 }
2405
2406 void
2407 DontEcho ()
2408 {
2409     if (!appData.icsActive) return;
2410     TelnetRequest(TN_DONT, TN_ECHO);
2411 }
2412
2413 void
2414 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2415 {
2416     /* put the holdings sent to us by the server on the board holdings area */
2417     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2418     char p;
2419     ChessSquare piece;
2420
2421     if(gameInfo.holdingsWidth < 2)  return;
2422     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2423         return; // prevent overwriting by pre-board holdings
2424
2425     if( (int)lowestPiece >= BlackPawn ) {
2426         holdingsColumn = 0;
2427         countsColumn = 1;
2428         holdingsStartRow = BOARD_HEIGHT-1;
2429         direction = -1;
2430     } else {
2431         holdingsColumn = BOARD_WIDTH-1;
2432         countsColumn = BOARD_WIDTH-2;
2433         holdingsStartRow = 0;
2434         direction = 1;
2435     }
2436
2437     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2438         board[i][holdingsColumn] = EmptySquare;
2439         board[i][countsColumn]   = (ChessSquare) 0;
2440     }
2441     while( (p=*holdings++) != NULLCHAR ) {
2442         piece = CharToPiece( ToUpper(p) );
2443         if(piece == EmptySquare) continue;
2444         /*j = (int) piece - (int) WhitePawn;*/
2445         j = PieceToNumber(piece);
2446         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2447         if(j < 0) continue;               /* should not happen */
2448         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2449         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2450         board[holdingsStartRow+j*direction][countsColumn]++;
2451     }
2452 }
2453
2454
2455 void
2456 VariantSwitch (Board board, VariantClass newVariant)
2457 {
2458    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2459    static Board oldBoard;
2460
2461    startedFromPositionFile = FALSE;
2462    if(gameInfo.variant == newVariant) return;
2463
2464    /* [HGM] This routine is called each time an assignment is made to
2465     * gameInfo.variant during a game, to make sure the board sizes
2466     * are set to match the new variant. If that means adding or deleting
2467     * holdings, we shift the playing board accordingly
2468     * This kludge is needed because in ICS observe mode, we get boards
2469     * of an ongoing game without knowing the variant, and learn about the
2470     * latter only later. This can be because of the move list we requested,
2471     * in which case the game history is refilled from the beginning anyway,
2472     * but also when receiving holdings of a crazyhouse game. In the latter
2473     * case we want to add those holdings to the already received position.
2474     */
2475
2476
2477    if (appData.debugMode) {
2478      fprintf(debugFP, "Switch board from %s to %s\n",
2479              VariantName(gameInfo.variant), VariantName(newVariant));
2480      setbuf(debugFP, NULL);
2481    }
2482    shuffleOpenings = 0;       /* [HGM] shuffle */
2483    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2484    switch(newVariant)
2485      {
2486      case VariantShogi:
2487        newWidth = 9;  newHeight = 9;
2488        gameInfo.holdingsSize = 7;
2489      case VariantBughouse:
2490      case VariantCrazyhouse:
2491        newHoldingsWidth = 2; break;
2492      case VariantGreat:
2493        newWidth = 10;
2494      case VariantSuper:
2495        newHoldingsWidth = 2;
2496        gameInfo.holdingsSize = 8;
2497        break;
2498      case VariantGothic:
2499      case VariantCapablanca:
2500      case VariantCapaRandom:
2501        newWidth = 10;
2502      default:
2503        newHoldingsWidth = gameInfo.holdingsSize = 0;
2504      };
2505
2506    if(newWidth  != gameInfo.boardWidth  ||
2507       newHeight != gameInfo.boardHeight ||
2508       newHoldingsWidth != gameInfo.holdingsWidth ) {
2509
2510      /* shift position to new playing area, if needed */
2511      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2512        for(i=0; i<BOARD_HEIGHT; i++)
2513          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2514            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2515              board[i][j];
2516        for(i=0; i<newHeight; i++) {
2517          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2518          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2519        }
2520      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2521        for(i=0; i<BOARD_HEIGHT; i++)
2522          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2523            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2524              board[i][j];
2525      }
2526      board[HOLDINGS_SET] = 0;
2527      gameInfo.boardWidth  = newWidth;
2528      gameInfo.boardHeight = newHeight;
2529      gameInfo.holdingsWidth = newHoldingsWidth;
2530      gameInfo.variant = newVariant;
2531      InitDrawingSizes(-2, 0);
2532    } else gameInfo.variant = newVariant;
2533    CopyBoard(oldBoard, board);   // remember correctly formatted board
2534      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2535    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2536 }
2537
2538 static int loggedOn = FALSE;
2539
2540 /*-- Game start info cache: --*/
2541 int gs_gamenum;
2542 char gs_kind[MSG_SIZ];
2543 static char player1Name[128] = "";
2544 static char player2Name[128] = "";
2545 static char cont_seq[] = "\n\\   ";
2546 static int player1Rating = -1;
2547 static int player2Rating = -1;
2548 /*----------------------------*/
2549
2550 ColorClass curColor = ColorNormal;
2551 int suppressKibitz = 0;
2552
2553 // [HGM] seekgraph
2554 Boolean soughtPending = FALSE;
2555 Boolean seekGraphUp;
2556 #define MAX_SEEK_ADS 200
2557 #define SQUARE 0x80
2558 char *seekAdList[MAX_SEEK_ADS];
2559 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2560 float tcList[MAX_SEEK_ADS];
2561 char colorList[MAX_SEEK_ADS];
2562 int nrOfSeekAds = 0;
2563 int minRating = 1010, maxRating = 2800;
2564 int hMargin = 10, vMargin = 20, h, w;
2565 extern int squareSize, lineGap;
2566
2567 void
2568 PlotSeekAd (int i)
2569 {
2570         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2571         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2572         if(r < minRating+100 && r >=0 ) r = minRating+100;
2573         if(r > maxRating) r = maxRating;
2574         if(tc < 1.f) tc = 1.f;
2575         if(tc > 95.f) tc = 95.f;
2576         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2577         y = ((double)r - minRating)/(maxRating - minRating)
2578             * (h-vMargin-squareSize/8-1) + vMargin;
2579         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2580         if(strstr(seekAdList[i], " u ")) color = 1;
2581         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2582            !strstr(seekAdList[i], "bullet") &&
2583            !strstr(seekAdList[i], "blitz") &&
2584            !strstr(seekAdList[i], "standard") ) color = 2;
2585         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2586         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2587 }
2588
2589 void
2590 PlotSingleSeekAd (int i)
2591 {
2592         PlotSeekAd(i);
2593 }
2594
2595 void
2596 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2597 {
2598         char buf[MSG_SIZ], *ext = "";
2599         VariantClass v = StringToVariant(type);
2600         if(strstr(type, "wild")) {
2601             ext = type + 4; // append wild number
2602             if(v == VariantFischeRandom) type = "chess960"; else
2603             if(v == VariantLoadable) type = "setup"; else
2604             type = VariantName(v);
2605         }
2606         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2607         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2608             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2609             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2610             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2611             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2612             seekNrList[nrOfSeekAds] = nr;
2613             zList[nrOfSeekAds] = 0;
2614             seekAdList[nrOfSeekAds++] = StrSave(buf);
2615             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2616         }
2617 }
2618
2619 void
2620 EraseSeekDot (int i)
2621 {
2622     int x = xList[i], y = yList[i], d=squareSize/4, k;
2623     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2624     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2625     // now replot every dot that overlapped
2626     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2627         int xx = xList[k], yy = yList[k];
2628         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2629             DrawSeekDot(xx, yy, colorList[k]);
2630     }
2631 }
2632
2633 void
2634 RemoveSeekAd (int nr)
2635 {
2636         int i;
2637         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2638             EraseSeekDot(i);
2639             if(seekAdList[i]) free(seekAdList[i]);
2640             seekAdList[i] = seekAdList[--nrOfSeekAds];
2641             seekNrList[i] = seekNrList[nrOfSeekAds];
2642             ratingList[i] = ratingList[nrOfSeekAds];
2643             colorList[i]  = colorList[nrOfSeekAds];
2644             tcList[i] = tcList[nrOfSeekAds];
2645             xList[i]  = xList[nrOfSeekAds];
2646             yList[i]  = yList[nrOfSeekAds];
2647             zList[i]  = zList[nrOfSeekAds];
2648             seekAdList[nrOfSeekAds] = NULL;
2649             break;
2650         }
2651 }
2652
2653 Boolean
2654 MatchSoughtLine (char *line)
2655 {
2656     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2657     int nr, base, inc, u=0; char dummy;
2658
2659     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2660        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2661        (u=1) &&
2662        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2663         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2664         // match: compact and save the line
2665         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2666         return TRUE;
2667     }
2668     return FALSE;
2669 }
2670
2671 int
2672 DrawSeekGraph ()
2673 {
2674     int i;
2675     if(!seekGraphUp) return FALSE;
2676     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2677     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2678
2679     DrawSeekBackground(0, 0, w, h);
2680     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2681     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2682     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2683         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2684         yy = h-1-yy;
2685         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2686         if(i%500 == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2690         }
2691     }
2692     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2693     for(i=1; i<100; i+=(i<10?1:5)) {
2694         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2695         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2696         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2697             char buf[MSG_SIZ];
2698             snprintf(buf, MSG_SIZ, "%d", i);
2699             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2700         }
2701     }
2702     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2703     return TRUE;
2704 }
2705
2706 int
2707 SeekGraphClick (ClickType click, int x, int y, int moving)
2708 {
2709     static int lastDown = 0, displayed = 0, lastSecond;
2710     if(y < 0) return FALSE;
2711     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2712         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2713         if(!seekGraphUp) return FALSE;
2714         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2715         DrawPosition(TRUE, NULL);
2716         return TRUE;
2717     }
2718     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2719         if(click == Release || moving) return FALSE;
2720         nrOfSeekAds = 0;
2721         soughtPending = TRUE;
2722         SendToICS(ics_prefix);
2723         SendToICS("sought\n"); // should this be "sought all"?
2724     } else { // issue challenge based on clicked ad
2725         int dist = 10000; int i, closest = 0, second = 0;
2726         for(i=0; i<nrOfSeekAds; i++) {
2727             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2728             if(d < dist) { dist = d; closest = i; }
2729             second += (d - zList[i] < 120); // count in-range ads
2730             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2731         }
2732         if(dist < 120) {
2733             char buf[MSG_SIZ];
2734             second = (second > 1);
2735             if(displayed != closest || second != lastSecond) {
2736                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2737                 lastSecond = second; displayed = closest;
2738             }
2739             if(click == Press) {
2740                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2741                 lastDown = closest;
2742                 return TRUE;
2743             } // on press 'hit', only show info
2744             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2745             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2746             SendToICS(ics_prefix);
2747             SendToICS(buf);
2748             return TRUE; // let incoming board of started game pop down the graph
2749         } else if(click == Release) { // release 'miss' is ignored
2750             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2751             if(moving == 2) { // right up-click
2752                 nrOfSeekAds = 0; // refresh graph
2753                 soughtPending = TRUE;
2754                 SendToICS(ics_prefix);
2755                 SendToICS("sought\n"); // should this be "sought all"?
2756             }
2757             return TRUE;
2758         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2759         // press miss or release hit 'pop down' seek graph
2760         seekGraphUp = FALSE;
2761         DrawPosition(TRUE, NULL);
2762     }
2763     return TRUE;
2764 }
2765
2766 void
2767 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2768 {
2769 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2770 #define STARTED_NONE 0
2771 #define STARTED_MOVES 1
2772 #define STARTED_BOARD 2
2773 #define STARTED_OBSERVE 3
2774 #define STARTED_HOLDINGS 4
2775 #define STARTED_CHATTER 5
2776 #define STARTED_COMMENT 6
2777 #define STARTED_MOVES_NOHIDE 7
2778
2779     static int started = STARTED_NONE;
2780     static char parse[20000];
2781     static int parse_pos = 0;
2782     static char buf[BUF_SIZE + 1];
2783     static int firstTime = TRUE, intfSet = FALSE;
2784     static ColorClass prevColor = ColorNormal;
2785     static int savingComment = FALSE;
2786     static int cmatch = 0; // continuation sequence match
2787     char *bp;
2788     char str[MSG_SIZ];
2789     int i, oldi;
2790     int buf_len;
2791     int next_out;
2792     int tkind;
2793     int backup;    /* [DM] For zippy color lines */
2794     char *p;
2795     char talker[MSG_SIZ]; // [HGM] chat
2796     int channel;
2797
2798     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2799
2800     if (appData.debugMode) {
2801       if (!error) {
2802         fprintf(debugFP, "<ICS: ");
2803         show_bytes(debugFP, data, count);
2804         fprintf(debugFP, "\n");
2805       }
2806     }
2807
2808     if (appData.debugMode) { int f = forwardMostMove;
2809         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2810                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2811                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2812     }
2813     if (count > 0) {
2814         /* If last read ended with a partial line that we couldn't parse,
2815            prepend it to the new read and try again. */
2816         if (leftover_len > 0) {
2817             for (i=0; i<leftover_len; i++)
2818               buf[i] = buf[leftover_start + i];
2819         }
2820
2821     /* copy new characters into the buffer */
2822     bp = buf + leftover_len;
2823     buf_len=leftover_len;
2824     for (i=0; i<count; i++)
2825     {
2826         // ignore these
2827         if (data[i] == '\r')
2828             continue;
2829
2830         // join lines split by ICS?
2831         if (!appData.noJoin)
2832         {
2833             /*
2834                 Joining just consists of finding matches against the
2835                 continuation sequence, and discarding that sequence
2836                 if found instead of copying it.  So, until a match
2837                 fails, there's nothing to do since it might be the
2838                 complete sequence, and thus, something we don't want
2839                 copied.
2840             */
2841             if (data[i] == cont_seq[cmatch])
2842             {
2843                 cmatch++;
2844                 if (cmatch == strlen(cont_seq))
2845                 {
2846                     cmatch = 0; // complete match.  just reset the counter
2847
2848                     /*
2849                         it's possible for the ICS to not include the space
2850                         at the end of the last word, making our [correct]
2851                         join operation fuse two separate words.  the server
2852                         does this when the space occurs at the width setting.
2853                     */
2854                     if (!buf_len || buf[buf_len-1] != ' ')
2855                     {
2856                         *bp++ = ' ';
2857                         buf_len++;
2858                     }
2859                 }
2860                 continue;
2861             }
2862             else if (cmatch)
2863             {
2864                 /*
2865                     match failed, so we have to copy what matched before
2866                     falling through and copying this character.  In reality,
2867                     this will only ever be just the newline character, but
2868                     it doesn't hurt to be precise.
2869                 */
2870                 strncpy(bp, cont_seq, cmatch);
2871                 bp += cmatch;
2872                 buf_len += cmatch;
2873                 cmatch = 0;
2874             }
2875         }
2876
2877         // copy this char
2878         *bp++ = data[i];
2879         buf_len++;
2880     }
2881
2882         buf[buf_len] = NULLCHAR;
2883 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2884         next_out = 0;
2885         leftover_start = 0;
2886
2887         i = 0;
2888         while (i < buf_len) {
2889             /* Deal with part of the TELNET option negotiation
2890                protocol.  We refuse to do anything beyond the
2891                defaults, except that we allow the WILL ECHO option,
2892                which ICS uses to turn off password echoing when we are
2893                directly connected to it.  We reject this option
2894                if localLineEditing mode is on (always on in xboard)
2895                and we are talking to port 23, which might be a real
2896                telnet server that will try to keep WILL ECHO on permanently.
2897              */
2898             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2899                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2900                 unsigned char option;
2901                 oldi = i;
2902                 switch ((unsigned char) buf[++i]) {
2903                   case TN_WILL:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<WILL ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       case TN_ECHO:
2908                         if (appData.debugMode)
2909                           fprintf(debugFP, "ECHO ");
2910                         /* Reply only if this is a change, according
2911                            to the protocol rules. */
2912                         if (remoteEchoOption) break;
2913                         if (appData.localLineEditing &&
2914                             atoi(appData.icsPort) == TN_PORT) {
2915                             TelnetRequest(TN_DONT, TN_ECHO);
2916                         } else {
2917                             EchoOff();
2918                             TelnetRequest(TN_DO, TN_ECHO);
2919                             remoteEchoOption = TRUE;
2920                         }
2921                         break;
2922                       default:
2923                         if (appData.debugMode)
2924                           fprintf(debugFP, "%d ", option);
2925                         /* Whatever this is, we don't want it. */
2926                         TelnetRequest(TN_DONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_WONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<WONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       case TN_ECHO:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "ECHO ");
2937                         /* Reply only if this is a change, according
2938                            to the protocol rules. */
2939                         if (!remoteEchoOption) break;
2940                         EchoOn();
2941                         TelnetRequest(TN_DONT, TN_ECHO);
2942                         remoteEchoOption = FALSE;
2943                         break;
2944                       default:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "%d ", (unsigned char) option);
2947                         /* Whatever this is, it must already be turned
2948                            off, because we never agree to turn on
2949                            anything non-default, so according to the
2950                            protocol rules, we don't reply. */
2951                         break;
2952                     }
2953                     break;
2954                   case TN_DO:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<DO ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       default:
2959                         /* Whatever this is, we refuse to do it. */
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         TelnetRequest(TN_WONT, option);
2963                         break;
2964                     }
2965                     break;
2966                   case TN_DONT:
2967                     if (appData.debugMode)
2968                       fprintf(debugFP, "\n<DONT ");
2969                     switch (option = (unsigned char) buf[++i]) {
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we are already not doing
2974                            it, because we never agree to do anything
2975                            non-default, so according to the protocol
2976                            rules, we don't reply. */
2977                         break;
2978                     }
2979                     break;
2980                   case TN_IAC:
2981                     if (appData.debugMode)
2982                       fprintf(debugFP, "\n<IAC ");
2983                     /* Doubled IAC; pass it through */
2984                     i--;
2985                     break;
2986                   default:
2987                     if (appData.debugMode)
2988                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2989                     /* Drop all other telnet commands on the floor */
2990                     break;
2991                 }
2992                 if (oldi > next_out)
2993                   SendToPlayer(&buf[next_out], oldi - next_out);
2994                 if (++i > next_out)
2995                   next_out = i;
2996                 continue;
2997             }
2998
2999             /* OK, this at least will *usually* work */
3000             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3001                 loggedOn = TRUE;
3002             }
3003
3004             if (loggedOn && !intfSet) {
3005                 if (ics_type == ICS_ICC) {
3006                   snprintf(str, MSG_SIZ,
3007                           "/set-quietly interface %s\n/set-quietly style 12\n",
3008                           programVersion);
3009                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3010                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3011                 } else if (ics_type == ICS_CHESSNET) {
3012                   snprintf(str, MSG_SIZ, "/style 12\n");
3013                 } else {
3014                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3015                   strcat(str, programVersion);
3016                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3017                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3018                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3019 #ifdef WIN32
3020                   strcat(str, "$iset nohighlight 1\n");
3021 #endif
3022                   strcat(str, "$iset lock 1\n$style 12\n");
3023                 }
3024                 SendToICS(str);
3025                 NotifyFrontendLogin();
3026                 intfSet = TRUE;
3027             }
3028
3029             if (started == STARTED_COMMENT) {
3030                 /* Accumulate characters in comment */
3031                 parse[parse_pos++] = buf[i];
3032                 if (buf[i] == '\n') {
3033                     parse[parse_pos] = NULLCHAR;
3034                     if(chattingPartner>=0) {
3035                         char mess[MSG_SIZ];
3036                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3037                         OutputChatMessage(chattingPartner, mess);
3038                         chattingPartner = -1;
3039                         next_out = i+1; // [HGM] suppress printing in ICS window
3040                     } else
3041                     if(!suppressKibitz) // [HGM] kibitz
3042                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3043                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3044                         int nrDigit = 0, nrAlph = 0, j;
3045                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3046                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3047                         parse[parse_pos] = NULLCHAR;
3048                         // try to be smart: if it does not look like search info, it should go to
3049                         // ICS interaction window after all, not to engine-output window.
3050                         for(j=0; j<parse_pos; j++) { // count letters and digits
3051                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3052                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3053                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3054                         }
3055                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3056                             int depth=0; float score;
3057                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3058                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3059                                 pvInfoList[forwardMostMove-1].depth = depth;
3060                                 pvInfoList[forwardMostMove-1].score = 100*score;
3061                             }
3062                             OutputKibitz(suppressKibitz, parse);
3063                         } else {
3064                             char tmp[MSG_SIZ];
3065                             if(gameMode == IcsObserving) // restore original ICS messages
3066                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3067                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3068                             else
3069                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3070                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3071                             SendToPlayer(tmp, strlen(tmp));
3072                         }
3073                         next_out = i+1; // [HGM] suppress printing in ICS window
3074                     }
3075                     started = STARTED_NONE;
3076                 } else {
3077                     /* Don't match patterns against characters in comment */
3078                     i++;
3079                     continue;
3080                 }
3081             }
3082             if (started == STARTED_CHATTER) {
3083                 if (buf[i] != '\n') {
3084                     /* Don't match patterns against characters in chatter */
3085                     i++;
3086                     continue;
3087                 }
3088                 started = STARTED_NONE;
3089                 if(suppressKibitz) next_out = i+1;
3090             }
3091
3092             /* Kludge to deal with rcmd protocol */
3093             if (firstTime && looking_at(buf, &i, "\001*")) {
3094                 DisplayFatalError(&buf[1], 0, 1);
3095                 continue;
3096             } else {
3097                 firstTime = FALSE;
3098             }
3099
3100             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3101                 ics_type = ICS_ICC;
3102                 ics_prefix = "/";
3103                 if (appData.debugMode)
3104                   fprintf(debugFP, "ics_type %d\n", ics_type);
3105                 continue;
3106             }
3107             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3108                 ics_type = ICS_FICS;
3109                 ics_prefix = "$";
3110                 if (appData.debugMode)
3111                   fprintf(debugFP, "ics_type %d\n", ics_type);
3112                 continue;
3113             }
3114             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3115                 ics_type = ICS_CHESSNET;
3116                 ics_prefix = "/";
3117                 if (appData.debugMode)
3118                   fprintf(debugFP, "ics_type %d\n", ics_type);
3119                 continue;
3120             }
3121
3122             if (!loggedOn &&
3123                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3124                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3125                  looking_at(buf, &i, "will be \"*\""))) {
3126               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3127               continue;
3128             }
3129
3130             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3131               char buf[MSG_SIZ];
3132               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3133               DisplayIcsInteractionTitle(buf);
3134               have_set_title = TRUE;
3135             }
3136
3137             /* skip finger notes */
3138             if (started == STARTED_NONE &&
3139                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3140                  (buf[i] == '1' && buf[i+1] == '0')) &&
3141                 buf[i+2] == ':' && buf[i+3] == ' ') {
3142               started = STARTED_CHATTER;
3143               i += 3;
3144               continue;
3145             }
3146
3147             oldi = i;
3148             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3149             if(appData.seekGraph) {
3150                 if(soughtPending && MatchSoughtLine(buf+i)) {
3151                     i = strstr(buf+i, "rated") - buf;
3152                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153                     next_out = leftover_start = i;
3154                     started = STARTED_CHATTER;
3155                     suppressKibitz = TRUE;
3156                     continue;
3157                 }
3158                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3159                         && looking_at(buf, &i, "* ads displayed")) {
3160                     soughtPending = FALSE;
3161                     seekGraphUp = TRUE;
3162                     DrawSeekGraph();
3163                     continue;
3164                 }
3165                 if(appData.autoRefresh) {
3166                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3167                         int s = (ics_type == ICS_ICC); // ICC format differs
3168                         if(seekGraphUp)
3169                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3170                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3171                         looking_at(buf, &i, "*% "); // eat prompt
3172                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3173                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174                         next_out = i; // suppress
3175                         continue;
3176                     }
3177                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3178                         char *p = star_match[0];
3179                         while(*p) {
3180                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3181                             while(*p && *p++ != ' '); // next
3182                         }
3183                         looking_at(buf, &i, "*% "); // eat prompt
3184                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3185                         next_out = i;
3186                         continue;
3187                     }
3188                 }
3189             }
3190
3191             /* skip formula vars */
3192             if (started == STARTED_NONE &&
3193                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3194               started = STARTED_CHATTER;
3195               i += 3;
3196               continue;
3197             }
3198
3199             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3200             if (appData.autoKibitz && started == STARTED_NONE &&
3201                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3202                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3203                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3204                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3205                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3206                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3207                         suppressKibitz = TRUE;
3208                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3209                         next_out = i;
3210                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3211                                 && (gameMode == IcsPlayingWhite)) ||
3212                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3213                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3214                             started = STARTED_CHATTER; // own kibitz we simply discard
3215                         else {
3216                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3217                             parse_pos = 0; parse[0] = NULLCHAR;
3218                             savingComment = TRUE;
3219                             suppressKibitz = gameMode != IcsObserving ? 2 :
3220                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3221                         }
3222                         continue;
3223                 } else
3224                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3225                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3226                          && atoi(star_match[0])) {
3227                     // suppress the acknowledgements of our own autoKibitz
3228                     char *p;
3229                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3231                     SendToPlayer(star_match[0], strlen(star_match[0]));
3232                     if(looking_at(buf, &i, "*% ")) // eat prompt
3233                         suppressKibitz = FALSE;
3234                     next_out = i;
3235                     continue;
3236                 }
3237             } // [HGM] kibitz: end of patch
3238
3239             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3240
3241             // [HGM] chat: intercept tells by users for which we have an open chat window
3242             channel = -1;
3243             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3244                                            looking_at(buf, &i, "* whispers:") ||
3245                                            looking_at(buf, &i, "* kibitzes:") ||
3246                                            looking_at(buf, &i, "* shouts:") ||
3247                                            looking_at(buf, &i, "* c-shouts:") ||
3248                                            looking_at(buf, &i, "--> * ") ||
3249                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3250                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3251                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3252                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3253                 int p;
3254                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3255                 chattingPartner = -1;
3256
3257                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3258                 for(p=0; p<MAX_CHAT; p++) {
3259                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3260                     talker[0] = '['; strcat(talker, "] ");
3261                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3262                     chattingPartner = p; break;
3263                     }
3264                 } else
3265                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3266                 for(p=0; p<MAX_CHAT; p++) {
3267                     if(!strcmp("kibitzes", chatPartner[p])) {
3268                         talker[0] = '['; strcat(talker, "] ");
3269                         chattingPartner = p; break;
3270                     }
3271                 } else
3272                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3273                 for(p=0; p<MAX_CHAT; p++) {
3274                     if(!strcmp("whispers", chatPartner[p])) {
3275                         talker[0] = '['; strcat(talker, "] ");
3276                         chattingPartner = p; break;
3277                     }
3278                 } else
3279                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3280                   if(buf[i-8] == '-' && buf[i-3] == 't')
3281                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3282                     if(!strcmp("c-shouts", chatPartner[p])) {
3283                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3284                         chattingPartner = p; break;
3285                     }
3286                   }
3287                   if(chattingPartner < 0)
3288                   for(p=0; p<MAX_CHAT; p++) {
3289                     if(!strcmp("shouts", chatPartner[p])) {
3290                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3291                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3292                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3293                         chattingPartner = p; break;
3294                     }
3295                   }
3296                 }
3297                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3298                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3299                     talker[0] = 0; Colorize(ColorTell, FALSE);
3300                     chattingPartner = p; break;
3301                 }
3302                 if(chattingPartner<0) i = oldi; else {
3303                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3304                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3305                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3306                     started = STARTED_COMMENT;
3307                     parse_pos = 0; parse[0] = NULLCHAR;
3308                     savingComment = 3 + chattingPartner; // counts as TRUE
3309                     suppressKibitz = TRUE;
3310                     continue;
3311                 }
3312             } // [HGM] chat: end of patch
3313
3314           backup = i;
3315             if (appData.zippyTalk || appData.zippyPlay) {
3316                 /* [DM] Backup address for color zippy lines */
3317 #if ZIPPY
3318                if (loggedOn == TRUE)
3319                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3320                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3321 #endif
3322             } // [DM] 'else { ' deleted
3323                 if (
3324                     /* Regular tells and says */
3325                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3326                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3327                     looking_at(buf, &i, "* says: ") ||
3328                     /* Don't color "message" or "messages" output */
3329                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3330                     looking_at(buf, &i, "*. * at *:*: ") ||
3331                     looking_at(buf, &i, "--* (*:*): ") ||
3332                     /* Message notifications (same color as tells) */
3333                     looking_at(buf, &i, "* has left a message ") ||
3334                     looking_at(buf, &i, "* just sent you a message:\n") ||
3335                     /* Whispers and kibitzes */
3336                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3337                     looking_at(buf, &i, "* kibitzes: ") ||
3338                     /* Channel tells */
3339                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3340
3341                   if (tkind == 1 && strchr(star_match[0], ':')) {
3342                       /* Avoid "tells you:" spoofs in channels */
3343                      tkind = 3;
3344                   }
3345                   if (star_match[0][0] == NULLCHAR ||
3346                       strchr(star_match[0], ' ') ||
3347                       (tkind == 3 && strchr(star_match[1], ' '))) {
3348                     /* Reject bogus matches */
3349                     i = oldi;
3350                   } else {
3351                     if (appData.colorize) {
3352                       if (oldi > next_out) {
3353                         SendToPlayer(&buf[next_out], oldi - next_out);
3354                         next_out = oldi;
3355                       }
3356                       switch (tkind) {
3357                       case 1:
3358                         Colorize(ColorTell, FALSE);
3359                         curColor = ColorTell;
3360                         break;
3361                       case 2:
3362                         Colorize(ColorKibitz, FALSE);
3363                         curColor = ColorKibitz;
3364                         break;
3365                       case 3:
3366                         p = strrchr(star_match[1], '(');
3367                         if (p == NULL) {
3368                           p = star_match[1];
3369                         } else {
3370                           p++;
3371                         }
3372                         if (atoi(p) == 1) {
3373                           Colorize(ColorChannel1, FALSE);
3374                           curColor = ColorChannel1;
3375                         } else {
3376                           Colorize(ColorChannel, FALSE);
3377                           curColor = ColorChannel;
3378                         }
3379                         break;
3380                       case 5:
3381                         curColor = ColorNormal;
3382                         break;
3383                       }
3384                     }
3385                     if (started == STARTED_NONE && appData.autoComment &&
3386                         (gameMode == IcsObserving ||
3387                          gameMode == IcsPlayingWhite ||
3388                          gameMode == IcsPlayingBlack)) {
3389                       parse_pos = i - oldi;
3390                       memcpy(parse, &buf[oldi], parse_pos);
3391                       parse[parse_pos] = NULLCHAR;
3392                       started = STARTED_COMMENT;
3393                       savingComment = TRUE;
3394                     } else {
3395                       started = STARTED_CHATTER;
3396                       savingComment = FALSE;
3397                     }
3398                     loggedOn = TRUE;
3399                     continue;
3400                   }
3401                 }
3402
3403                 if (looking_at(buf, &i, "* s-shouts: ") ||
3404                     looking_at(buf, &i, "* c-shouts: ")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorSShout, FALSE);
3411                         curColor = ColorSShout;
3412                     }
3413                     loggedOn = TRUE;
3414                     started = STARTED_CHATTER;
3415                     continue;
3416                 }
3417
3418                 if (looking_at(buf, &i, "--->")) {
3419                     loggedOn = TRUE;
3420                     continue;
3421                 }
3422
3423                 if (looking_at(buf, &i, "* shouts: ") ||
3424                     looking_at(buf, &i, "--> ")) {
3425                     if (appData.colorize) {
3426                         if (oldi > next_out) {
3427                             SendToPlayer(&buf[next_out], oldi - next_out);
3428                             next_out = oldi;
3429                         }
3430                         Colorize(ColorShout, FALSE);
3431                         curColor = ColorShout;
3432                     }
3433                     loggedOn = TRUE;
3434                     started = STARTED_CHATTER;
3435                     continue;
3436                 }
3437
3438                 if (looking_at( buf, &i, "Challenge:")) {
3439                     if (appData.colorize) {
3440                         if (oldi > next_out) {
3441                             SendToPlayer(&buf[next_out], oldi - next_out);
3442                             next_out = oldi;
3443                         }
3444                         Colorize(ColorChallenge, FALSE);
3445                         curColor = ColorChallenge;
3446                     }
3447                     loggedOn = TRUE;
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* offers you") ||
3452                     looking_at(buf, &i, "* offers to be") ||
3453                     looking_at(buf, &i, "* would like to") ||
3454                     looking_at(buf, &i, "* requests to") ||
3455                     looking_at(buf, &i, "Your opponent offers") ||
3456                     looking_at(buf, &i, "Your opponent requests")) {
3457
3458                     if (appData.colorize) {
3459                         if (oldi > next_out) {
3460                             SendToPlayer(&buf[next_out], oldi - next_out);
3461                             next_out = oldi;
3462                         }
3463                         Colorize(ColorRequest, FALSE);
3464                         curColor = ColorRequest;
3465                     }
3466                     continue;
3467                 }
3468
3469                 if (looking_at(buf, &i, "* (*) seeking")) {
3470                     if (appData.colorize) {
3471                         if (oldi > next_out) {
3472                             SendToPlayer(&buf[next_out], oldi - next_out);
3473                             next_out = oldi;
3474                         }
3475                         Colorize(ColorSeek, FALSE);
3476                         curColor = ColorSeek;
3477                     }
3478                     continue;
3479             }
3480
3481           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3482
3483             if (looking_at(buf, &i, "\\   ")) {
3484                 if (prevColor != ColorNormal) {
3485                     if (oldi > next_out) {
3486                         SendToPlayer(&buf[next_out], oldi - next_out);
3487                         next_out = oldi;
3488                     }
3489                     Colorize(prevColor, TRUE);
3490                     curColor = prevColor;
3491                 }
3492                 if (savingComment) {
3493                     parse_pos = i - oldi;
3494                     memcpy(parse, &buf[oldi], parse_pos);
3495                     parse[parse_pos] = NULLCHAR;
3496                     started = STARTED_COMMENT;
3497                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3498                         chattingPartner = savingComment - 3; // kludge to remember the box
3499                 } else {
3500                     started = STARTED_CHATTER;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "Black Strength :") ||
3506                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3507                 looking_at(buf, &i, "<10>") ||
3508                 looking_at(buf, &i, "#@#")) {
3509                 /* Wrong board style */
3510                 loggedOn = TRUE;
3511                 SendToICS(ics_prefix);
3512                 SendToICS("set style 12\n");
3513                 SendToICS(ics_prefix);
3514                 SendToICS("refresh\n");
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "login:")) {
3519               if (!have_sent_ICS_logon) {
3520                 if(ICSInitScript())
3521                   have_sent_ICS_logon = 1;
3522                 else // no init script was found
3523                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3524               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3525                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3526               }
3527                 continue;
3528             }
3529
3530             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3531                 (looking_at(buf, &i, "\n<12> ") ||
3532                  looking_at(buf, &i, "<12> "))) {
3533                 loggedOn = TRUE;
3534                 if (oldi > next_out) {
3535                     SendToPlayer(&buf[next_out], oldi - next_out);
3536                 }
3537                 next_out = i;
3538                 started = STARTED_BOARD;
3539                 parse_pos = 0;
3540                 continue;
3541             }
3542
3543             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3544                 looking_at(buf, &i, "<b1> ")) {
3545                 if (oldi > next_out) {
3546                     SendToPlayer(&buf[next_out], oldi - next_out);
3547                 }
3548                 next_out = i;
3549                 started = STARTED_HOLDINGS;
3550                 parse_pos = 0;
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3555                 loggedOn = TRUE;
3556                 /* Header for a move list -- first line */
3557
3558                 switch (ics_getting_history) {
3559                   case H_FALSE:
3560                     switch (gameMode) {
3561                       case IcsIdle:
3562                       case BeginningOfGame:
3563                         /* User typed "moves" or "oldmoves" while we
3564                            were idle.  Pretend we asked for these
3565                            moves and soak them up so user can step
3566                            through them and/or save them.
3567                            */
3568                         Reset(FALSE, TRUE);
3569                         gameMode = IcsObserving;
3570                         ModeHighlight();
3571                         ics_gamenum = -1;
3572                         ics_getting_history = H_GOT_UNREQ_HEADER;
3573                         break;
3574                       case EditGame: /*?*/
3575                       case EditPosition: /*?*/
3576                         /* Should above feature work in these modes too? */
3577                         /* For now it doesn't */
3578                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3579                         break;
3580                       default:
3581                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3582                         break;
3583                     }
3584                     break;
3585                   case H_REQUESTED:
3586                     /* Is this the right one? */
3587                     if (gameInfo.white && gameInfo.black &&
3588                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3589                         strcmp(gameInfo.black, star_match[2]) == 0) {
3590                         /* All is well */
3591                         ics_getting_history = H_GOT_REQ_HEADER;
3592                     }
3593                     break;
3594                   case H_GOT_REQ_HEADER:
3595                   case H_GOT_UNREQ_HEADER:
3596                   case H_GOT_UNWANTED_HEADER:
3597                   case H_GETTING_MOVES:
3598                     /* Should not happen */
3599                     DisplayError(_("Error gathering move list: two headers"), 0);
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603
3604                 /* Save player ratings into gameInfo if needed */
3605                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3606                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3607                     (gameInfo.whiteRating == -1 ||
3608                      gameInfo.blackRating == -1)) {
3609
3610                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3611                     gameInfo.blackRating = string_to_rating(star_match[3]);
3612                     if (appData.debugMode)
3613                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3614                               gameInfo.whiteRating, gameInfo.blackRating);
3615                 }
3616                 continue;
3617             }
3618
3619             if (looking_at(buf, &i,
3620               "* * match, initial time: * minute*, increment: * second")) {
3621                 /* Header for a move list -- second line */
3622                 /* Initial board will follow if this is a wild game */
3623                 if (gameInfo.event != NULL) free(gameInfo.event);
3624                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3625                 gameInfo.event = StrSave(str);
3626                 /* [HGM] we switched variant. Translate boards if needed. */
3627                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "Move  ")) {
3632                 /* Beginning of a move list */
3633                 switch (ics_getting_history) {
3634                   case H_FALSE:
3635                     /* Normally should not happen */
3636                     /* Maybe user hit reset while we were parsing */
3637                     break;
3638                   case H_REQUESTED:
3639                     /* Happens if we are ignoring a move list that is not
3640                      * the one we just requested.  Common if the user
3641                      * tries to observe two games without turning off
3642                      * getMoveList */
3643                     break;
3644                   case H_GETTING_MOVES:
3645                     /* Should not happen */
3646                     DisplayError(_("Error gathering move list: nested"), 0);
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649                   case H_GOT_REQ_HEADER:
3650                     ics_getting_history = H_GETTING_MOVES;
3651                     started = STARTED_MOVES;
3652                     parse_pos = 0;
3653                     if (oldi > next_out) {
3654                         SendToPlayer(&buf[next_out], oldi - next_out);
3655                     }
3656                     break;
3657                   case H_GOT_UNREQ_HEADER:
3658                     ics_getting_history = H_GETTING_MOVES;
3659                     started = STARTED_MOVES_NOHIDE;
3660                     parse_pos = 0;
3661                     break;
3662                   case H_GOT_UNWANTED_HEADER:
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666                 continue;
3667             }
3668
3669             if (looking_at(buf, &i, "% ") ||
3670                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3671                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3672                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3673                     soughtPending = FALSE;
3674                     seekGraphUp = TRUE;
3675                     DrawSeekGraph();
3676                 }
3677                 if(suppressKibitz) next_out = i;
3678                 savingComment = FALSE;
3679                 suppressKibitz = 0;
3680                 switch (started) {
3681                   case STARTED_MOVES:
3682                   case STARTED_MOVES_NOHIDE:
3683                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3684                     parse[parse_pos + i - oldi] = NULLCHAR;
3685                     ParseGameHistory(parse);
3686 #if ZIPPY
3687                     if (appData.zippyPlay && first.initDone) {
3688                         FeedMovesToProgram(&first, forwardMostMove);
3689                         if (gameMode == IcsPlayingWhite) {
3690                             if (WhiteOnMove(forwardMostMove)) {
3691                                 if (first.sendTime) {
3692                                   if (first.useColors) {
3693                                     SendToProgram("black\n", &first);
3694                                   }
3695                                   SendTimeRemaining(&first, TRUE);
3696                                 }
3697                                 if (first.useColors) {
3698                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3699                                 }
3700                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3701                                 first.maybeThinking = TRUE;
3702                             } else {
3703                                 if (first.usePlayother) {
3704                                   if (first.sendTime) {
3705                                     SendTimeRemaining(&first, TRUE);
3706                                   }
3707                                   SendToProgram("playother\n", &first);
3708                                   firstMove = FALSE;
3709                                 } else {
3710                                   firstMove = TRUE;
3711                                 }
3712                             }
3713                         } else if (gameMode == IcsPlayingBlack) {
3714                             if (!WhiteOnMove(forwardMostMove)) {
3715                                 if (first.sendTime) {
3716                                   if (first.useColors) {
3717                                     SendToProgram("white\n", &first);
3718                                   }
3719                                   SendTimeRemaining(&first, FALSE);
3720                                 }
3721                                 if (first.useColors) {
3722                                   SendToProgram("black\n", &first);
3723                                 }
3724                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3725                                 first.maybeThinking = TRUE;
3726                             } else {
3727                                 if (first.usePlayother) {
3728                                   if (first.sendTime) {
3729                                     SendTimeRemaining(&first, FALSE);
3730                                   }
3731                                   SendToProgram("playother\n", &first);
3732                                   firstMove = FALSE;
3733                                 } else {
3734                                   firstMove = TRUE;
3735                                 }
3736                             }
3737                         }
3738                     }
3739 #endif
3740                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3741                         /* Moves came from oldmoves or moves command
3742                            while we weren't doing anything else.
3743                            */
3744                         currentMove = forwardMostMove;
3745                         ClearHighlights();/*!!could figure this out*/
3746                         flipView = appData.flipView;
3747                         DrawPosition(TRUE, boards[currentMove]);
3748                         DisplayBothClocks();
3749                         snprintf(str, MSG_SIZ, "%s %s %s",
3750                                 gameInfo.white, _("vs."),  gameInfo.black);
3751                         DisplayTitle(str);
3752                         gameMode = IcsIdle;
3753                     } else {
3754                         /* Moves were history of an active game */
3755                         if (gameInfo.resultDetails != NULL) {
3756                             free(gameInfo.resultDetails);
3757                             gameInfo.resultDetails = NULL;
3758                         }
3759                     }
3760                     HistorySet(parseList, backwardMostMove,
3761                                forwardMostMove, currentMove-1);
3762                     DisplayMove(currentMove - 1);
3763                     if (started == STARTED_MOVES) next_out = i;
3764                     started = STARTED_NONE;
3765                     ics_getting_history = H_FALSE;
3766                     break;
3767
3768                   case STARTED_OBSERVE:
3769                     started = STARTED_NONE;
3770                     SendToICS(ics_prefix);
3771                     SendToICS("refresh\n");
3772                     break;
3773
3774                   default:
3775                     break;
3776                 }
3777                 if(bookHit) { // [HGM] book: simulate book reply
3778                     static char bookMove[MSG_SIZ]; // a bit generous?
3779
3780                     programStats.nodes = programStats.depth = programStats.time =
3781                     programStats.score = programStats.got_only_move = 0;
3782                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3783
3784                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3785                     strcat(bookMove, bookHit);
3786                     HandleMachineMove(bookMove, &first);
3787                 }
3788                 continue;
3789             }
3790
3791             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3792                  started == STARTED_HOLDINGS ||
3793                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3794                 /* Accumulate characters in move list or board */
3795                 parse[parse_pos++] = buf[i];
3796             }
3797
3798             /* Start of game messages.  Mostly we detect start of game
3799                when the first board image arrives.  On some versions
3800                of the ICS, though, we need to do a "refresh" after starting
3801                to observe in order to get the current board right away. */
3802             if (looking_at(buf, &i, "Adding game * to observation list")) {
3803                 started = STARTED_OBSERVE;
3804                 continue;
3805             }
3806
3807             /* Handle auto-observe */
3808             if (appData.autoObserve &&
3809                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3810                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3811                 char *player;
3812                 /* Choose the player that was highlighted, if any. */
3813                 if (star_match[0][0] == '\033' ||
3814                     star_match[1][0] != '\033') {
3815                     player = star_match[0];
3816                 } else {
3817                     player = star_match[2];
3818                 }
3819                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3820                         ics_prefix, StripHighlightAndTitle(player));
3821                 SendToICS(str);
3822
3823                 /* Save ratings from notify string */
3824                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3825                 player1Rating = string_to_rating(star_match[1]);
3826                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3827                 player2Rating = string_to_rating(star_match[3]);
3828
3829                 if (appData.debugMode)
3830                   fprintf(debugFP,
3831                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3832                           player1Name, player1Rating,
3833                           player2Name, player2Rating);
3834
3835                 continue;
3836             }
3837
3838             /* Deal with automatic examine mode after a game,
3839                and with IcsObserving -> IcsExamining transition */
3840             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3841                 looking_at(buf, &i, "has made you an examiner of game *")) {
3842
3843                 int gamenum = atoi(star_match[0]);
3844                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3845                     gamenum == ics_gamenum) {
3846                     /* We were already playing or observing this game;
3847                        no need to refetch history */
3848                     gameMode = IcsExamining;
3849                     if (pausing) {
3850                         pauseExamForwardMostMove = forwardMostMove;
3851                     } else if (currentMove < forwardMostMove) {
3852                         ForwardInner(forwardMostMove);
3853                     }
3854                 } else {
3855                     /* I don't think this case really can happen */
3856                     SendToICS(ics_prefix);
3857                     SendToICS("refresh\n");
3858                 }
3859                 continue;
3860             }
3861
3862             /* Error messages */
3863 //          if (ics_user_moved) {
3864             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3865                 if (looking_at(buf, &i, "Illegal move") ||
3866                     looking_at(buf, &i, "Not a legal move") ||
3867                     looking_at(buf, &i, "Your king is in check") ||
3868                     looking_at(buf, &i, "It isn't your turn") ||
3869                     looking_at(buf, &i, "It is not your move")) {
3870                     /* Illegal move */
3871                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3872                         currentMove = forwardMostMove-1;
3873                         DisplayMove(currentMove - 1); /* before DMError */
3874                         DrawPosition(FALSE, boards[currentMove]);
3875                         SwitchClocks(forwardMostMove-1); // [HGM] race
3876                         DisplayBothClocks();
3877                     }
3878                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3879                     ics_user_moved = 0;
3880                     continue;
3881                 }
3882             }
3883
3884             if (looking_at(buf, &i, "still have time") ||
3885                 looking_at(buf, &i, "not out of time") ||
3886                 looking_at(buf, &i, "either player is out of time") ||
3887                 looking_at(buf, &i, "has timeseal; checking")) {
3888                 /* We must have called his flag a little too soon */
3889                 whiteFlag = blackFlag = FALSE;
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "added * seconds to") ||
3894                 looking_at(buf, &i, "seconds were added to")) {
3895                 /* Update the clocks */
3896                 SendToICS(ics_prefix);
3897                 SendToICS("refresh\n");
3898                 continue;
3899             }
3900
3901             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3902                 ics_clock_paused = TRUE;
3903                 StopClocks();
3904                 continue;
3905             }
3906
3907             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3908                 ics_clock_paused = FALSE;
3909                 StartClocks();
3910                 continue;
3911             }
3912
3913             /* Grab player ratings from the Creating: message.
3914                Note we have to check for the special case when
3915                the ICS inserts things like [white] or [black]. */
3916             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3917                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3918                 /* star_matches:
3919                    0    player 1 name (not necessarily white)
3920                    1    player 1 rating
3921                    2    empty, white, or black (IGNORED)
3922                    3    player 2 name (not necessarily black)
3923                    4    player 2 rating
3924
3925                    The names/ratings are sorted out when the game
3926                    actually starts (below).
3927                 */
3928                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3929                 player1Rating = string_to_rating(star_match[1]);
3930                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3931                 player2Rating = string_to_rating(star_match[4]);
3932
3933                 if (appData.debugMode)
3934                   fprintf(debugFP,
3935                           "Ratings from 'Creating:' %s %d, %s %d\n",
3936                           player1Name, player1Rating,
3937                           player2Name, player2Rating);
3938
3939                 continue;
3940             }
3941
3942             /* Improved generic start/end-of-game messages */
3943             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3944                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3945                 /* If tkind == 0: */
3946                 /* star_match[0] is the game number */
3947                 /*           [1] is the white player's name */
3948                 /*           [2] is the black player's name */
3949                 /* For end-of-game: */
3950                 /*           [3] is the reason for the game end */
3951                 /*           [4] is a PGN end game-token, preceded by " " */
3952                 /* For start-of-game: */
3953                 /*           [3] begins with "Creating" or "Continuing" */
3954                 /*           [4] is " *" or empty (don't care). */
3955                 int gamenum = atoi(star_match[0]);
3956                 char *whitename, *blackname, *why, *endtoken;
3957                 ChessMove endtype = EndOfFile;
3958
3959                 if (tkind == 0) {
3960                   whitename = star_match[1];
3961                   blackname = star_match[2];
3962                   why = star_match[3];
3963                   endtoken = star_match[4];
3964                 } else {
3965                   whitename = star_match[1];
3966                   blackname = star_match[3];
3967                   why = star_match[5];
3968                   endtoken = star_match[6];
3969                 }
3970
3971                 /* Game start messages */
3972                 if (strncmp(why, "Creating ", 9) == 0 ||
3973                     strncmp(why, "Continuing ", 11) == 0) {
3974                     gs_gamenum = gamenum;
3975                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3976                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3977                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3978 #if ZIPPY
3979                     if (appData.zippyPlay) {
3980                         ZippyGameStart(whitename, blackname);
3981                     }
3982 #endif /*ZIPPY*/
3983                     partnerBoardValid = FALSE; // [HGM] bughouse
3984                     continue;
3985                 }
3986
3987                 /* Game end messages */
3988                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3989                     ics_gamenum != gamenum) {
3990                     continue;
3991                 }
3992                 while (endtoken[0] == ' ') endtoken++;
3993                 switch (endtoken[0]) {
3994                   case '*':
3995                   default:
3996                     endtype = GameUnfinished;
3997                     break;
3998                   case '0':
3999                     endtype = BlackWins;
4000                     break;
4001                   case '1':
4002                     if (endtoken[1] == '/')
4003                       endtype = GameIsDrawn;
4004                     else
4005                       endtype = WhiteWins;
4006                     break;
4007                 }
4008                 GameEnds(endtype, why, GE_ICS);
4009 #if ZIPPY
4010                 if (appData.zippyPlay && first.initDone) {
4011                     ZippyGameEnd(endtype, why);
4012                     if (first.pr == NoProc) {
4013                       /* Start the next process early so that we'll
4014                          be ready for the next challenge */
4015                       StartChessProgram(&first);
4016                     }
4017                     /* Send "new" early, in case this command takes
4018                        a long time to finish, so that we'll be ready
4019                        for the next challenge. */
4020                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4021                     Reset(TRUE, TRUE);
4022                 }
4023 #endif /*ZIPPY*/
4024                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4025                 continue;
4026             }
4027
4028             if (looking_at(buf, &i, "Removing game * from observation") ||
4029                 looking_at(buf, &i, "no longer observing game *") ||
4030                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4031                 if (gameMode == IcsObserving &&
4032                     atoi(star_match[0]) == ics_gamenum)
4033                   {
4034                       /* icsEngineAnalyze */
4035                       if (appData.icsEngineAnalyze) {
4036                             ExitAnalyzeMode();
4037                             ModeHighlight();
4038                       }
4039                       StopClocks();
4040                       gameMode = IcsIdle;
4041                       ics_gamenum = -1;
4042                       ics_user_moved = FALSE;
4043                   }
4044                 continue;
4045             }
4046
4047             if (looking_at(buf, &i, "no longer examining game *")) {
4048                 if (gameMode == IcsExamining &&
4049                     atoi(star_match[0]) == ics_gamenum)
4050                   {
4051                       gameMode = IcsIdle;
4052                       ics_gamenum = -1;
4053                       ics_user_moved = FALSE;
4054                   }
4055                 continue;
4056             }
4057
4058             /* Advance leftover_start past any newlines we find,
4059                so only partial lines can get reparsed */
4060             if (looking_at(buf, &i, "\n")) {
4061                 prevColor = curColor;
4062                 if (curColor != ColorNormal) {
4063                     if (oldi > next_out) {
4064                         SendToPlayer(&buf[next_out], oldi - next_out);
4065                         next_out = oldi;
4066                     }
4067                     Colorize(ColorNormal, FALSE);
4068                     curColor = ColorNormal;
4069                 }
4070                 if (started == STARTED_BOARD) {
4071                     started = STARTED_NONE;
4072                     parse[parse_pos] = NULLCHAR;
4073                     ParseBoard12(parse);
4074                     ics_user_moved = 0;
4075
4076                     /* Send premove here */
4077                     if (appData.premove) {
4078                       char str[MSG_SIZ];
4079                       if (currentMove == 0 &&
4080                           gameMode == IcsPlayingWhite &&
4081                           appData.premoveWhite) {
4082                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4083                         if (appData.debugMode)
4084                           fprintf(debugFP, "Sending premove:\n");
4085                         SendToICS(str);
4086                       } else if (currentMove == 1 &&
4087                                  gameMode == IcsPlayingBlack &&
4088                                  appData.premoveBlack) {
4089                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4090                         if (appData.debugMode)
4091                           fprintf(debugFP, "Sending premove:\n");
4092                         SendToICS(str);
4093                       } else if (gotPremove) {
4094                         gotPremove = 0;
4095                         ClearPremoveHighlights();
4096                         if (appData.debugMode)
4097                           fprintf(debugFP, "Sending premove:\n");
4098                           UserMoveEvent(premoveFromX, premoveFromY,
4099                                         premoveToX, premoveToY,
4100                                         premovePromoChar);
4101                       }
4102                     }
4103
4104                     /* Usually suppress following prompt */
4105                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4106                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4107                         if (looking_at(buf, &i, "*% ")) {
4108                             savingComment = FALSE;
4109                             suppressKibitz = 0;
4110                         }
4111                     }
4112                     next_out = i;
4113                 } else if (started == STARTED_HOLDINGS) {
4114                     int gamenum;
4115                     char new_piece[MSG_SIZ];
4116                     started = STARTED_NONE;
4117                     parse[parse_pos] = NULLCHAR;
4118                     if (appData.debugMode)
4119                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4120                                                         parse, currentMove);
4121                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4122                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4123                         if (gameInfo.variant == VariantNormal) {
4124                           /* [HGM] We seem to switch variant during a game!
4125                            * Presumably no holdings were displayed, so we have
4126                            * to move the position two files to the right to
4127                            * create room for them!
4128                            */
4129                           VariantClass newVariant;
4130                           switch(gameInfo.boardWidth) { // base guess on board width
4131                                 case 9:  newVariant = VariantShogi; break;
4132                                 case 10: newVariant = VariantGreat; break;
4133                                 default: newVariant = VariantCrazyhouse; break;
4134                           }
4135                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4136                           /* Get a move list just to see the header, which
4137                              will tell us whether this is really bug or zh */
4138                           if (ics_getting_history == H_FALSE) {
4139                             ics_getting_history = H_REQUESTED;
4140                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4141                             SendToICS(str);
4142                           }
4143                         }
4144                         new_piece[0] = NULLCHAR;
4145                         sscanf(parse, "game %d white [%s black [%s <- %s",
4146                                &gamenum, white_holding, black_holding,
4147                                new_piece);
4148                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4149                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4150                         /* [HGM] copy holdings to board holdings area */
4151                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4152                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4153                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4154 #if ZIPPY
4155                         if (appData.zippyPlay && first.initDone) {
4156                             ZippyHoldings(white_holding, black_holding,
4157                                           new_piece);
4158                         }
4159 #endif /*ZIPPY*/
4160                         if (tinyLayout || smallLayout) {
4161                             char wh[16], bh[16];
4162                             PackHolding(wh, white_holding);
4163                             PackHolding(bh, black_holding);
4164                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4165                                     gameInfo.white, gameInfo.black);
4166                         } else {
4167                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4168                                     gameInfo.white, white_holding, _("vs."),
4169                                     gameInfo.black, black_holding);
4170                         }
4171                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4172                         DrawPosition(FALSE, boards[currentMove]);
4173                         DisplayTitle(str);
4174                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4175                         sscanf(parse, "game %d white [%s black [%s <- %s",
4176                                &gamenum, white_holding, black_holding,
4177                                new_piece);
4178                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4179                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4180                         /* [HGM] copy holdings to partner-board holdings area */
4181                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4182                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4183                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4184                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4185                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4186                       }
4187                     }
4188                     /* Suppress following prompt */
4189                     if (looking_at(buf, &i, "*% ")) {
4190                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4191                         savingComment = FALSE;
4192                         suppressKibitz = 0;
4193                     }
4194                     next_out = i;
4195                 }
4196                 continue;
4197             }
4198
4199             i++;                /* skip unparsed character and loop back */
4200         }
4201
4202         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4203 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4204 //          SendToPlayer(&buf[next_out], i - next_out);
4205             started != STARTED_HOLDINGS && leftover_start > next_out) {
4206             SendToPlayer(&buf[next_out], leftover_start - next_out);
4207             next_out = i;
4208         }
4209
4210         leftover_len = buf_len - leftover_start;
4211         /* if buffer ends with something we couldn't parse,
4212            reparse it after appending the next read */
4213
4214     } else if (count == 0) {
4215         RemoveInputSource(isr);
4216         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4217     } else {
4218         DisplayFatalError(_("Error reading from ICS"), error, 1);
4219     }
4220 }
4221
4222
4223 /* Board style 12 looks like this:
4224
4225    <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
4226
4227  * The "<12> " is stripped before it gets to this routine.  The two
4228  * trailing 0's (flip state and clock ticking) are later addition, and
4229  * some chess servers may not have them, or may have only the first.
4230  * Additional trailing fields may be added in the future.
4231  */
4232
4233 #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"
4234
4235 #define RELATION_OBSERVING_PLAYED    0
4236 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4237 #define RELATION_PLAYING_MYMOVE      1
4238 #define RELATION_PLAYING_NOTMYMOVE  -1
4239 #define RELATION_EXAMINING           2
4240 #define RELATION_ISOLATED_BOARD     -3
4241 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4242
4243 void
4244 ParseBoard12 (char *string)
4245 {
4246 #if ZIPPY
4247     int i, takeback;
4248     char *bookHit = NULL; // [HGM] book
4249 #endif
4250     GameMode newGameMode;
4251     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4252     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4253     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4254     char to_play, board_chars[200];
4255     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4256     char black[32], white[32];
4257     Board board;
4258     int prevMove = currentMove;
4259     int ticking = 2;
4260     ChessMove moveType;
4261     int fromX, fromY, toX, toY;
4262     char promoChar;
4263     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4264     Boolean weird = FALSE, reqFlag = FALSE;
4265
4266     fromX = fromY = toX = toY = -1;
4267
4268     newGame = FALSE;
4269
4270     if (appData.debugMode)
4271       fprintf(debugFP, "Parsing board: %s\n", string);
4272
4273     move_str[0] = NULLCHAR;
4274     elapsed_time[0] = NULLCHAR;
4275     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4276         int  i = 0, j;
4277         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4278             if(string[i] == ' ') { ranks++; files = 0; }
4279             else files++;
4280             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4281             i++;
4282         }
4283         for(j = 0; j <i; j++) board_chars[j] = string[j];
4284         board_chars[i] = '\0';
4285         string += i + 1;
4286     }
4287     n = sscanf(string, PATTERN, &to_play, &double_push,
4288                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4289                &gamenum, white, black, &relation, &basetime, &increment,
4290                &white_stren, &black_stren, &white_time, &black_time,
4291                &moveNum, str, elapsed_time, move_str, &ics_flip,
4292                &ticking);
4293
4294     if (n < 21) {
4295         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4296         DisplayError(str, 0);
4297         return;
4298     }
4299
4300     /* Convert the move number to internal form */
4301     moveNum = (moveNum - 1) * 2;
4302     if (to_play == 'B') moveNum++;
4303     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4304       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4305                         0, 1);
4306       return;
4307     }
4308
4309     switch (relation) {
4310       case RELATION_OBSERVING_PLAYED:
4311       case RELATION_OBSERVING_STATIC:
4312         if (gamenum == -1) {
4313             /* Old ICC buglet */
4314             relation = RELATION_OBSERVING_STATIC;
4315         }
4316         newGameMode = IcsObserving;
4317         break;
4318       case RELATION_PLAYING_MYMOVE:
4319       case RELATION_PLAYING_NOTMYMOVE:
4320         newGameMode =
4321           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4322             IcsPlayingWhite : IcsPlayingBlack;
4323         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4324         break;
4325       case RELATION_EXAMINING:
4326         newGameMode = IcsExamining;
4327         break;
4328       case RELATION_ISOLATED_BOARD:
4329       default:
4330         /* Just display this board.  If user was doing something else,
4331            we will forget about it until the next board comes. */
4332         newGameMode = IcsIdle;
4333         break;
4334       case RELATION_STARTING_POSITION:
4335         newGameMode = gameMode;
4336         break;
4337     }
4338
4339     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4340         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4341          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4342       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4343       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4344       static int lastBgGame = -1;
4345       char *toSqr;
4346       for (k = 0; k < ranks; k++) {
4347         for (j = 0; j < files; j++)
4348           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4349         if(gameInfo.holdingsWidth > 1) {
4350              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4351              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4352         }
4353       }
4354       CopyBoard(partnerBoard, board);
4355       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4356         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4357         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4358       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4359       if(toSqr = strchr(str, '-')) {
4360         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4361         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4362       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4363       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4364       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4365       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4366       if(twoBoards) {
4367           DisplayWhiteClock(white_time*fac, to_play == 'W');
4368           DisplayBlackClock(black_time*fac, to_play != 'W');
4369           activePartner = to_play;
4370           if(gamenum != lastBgGame) {
4371               char buf[MSG_SIZ];
4372               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4373               DisplayTitle(buf);
4374           }
4375           lastBgGame = gamenum;
4376           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4377                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4378       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4379                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4380       if(!twoBoards) DisplayMessage(partnerStatus, "");
4381         partnerBoardValid = TRUE;
4382       return;
4383     }
4384
4385     if(appData.dualBoard && appData.bgObserve) {
4386         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4387             SendToICS(ics_prefix), SendToICS("pobserve\n");
4388         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4389             char buf[MSG_SIZ];
4390             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4391             SendToICS(buf);
4392         }
4393     }
4394
4395     /* Modify behavior for initial board display on move listing
4396        of wild games.
4397        */
4398     switch (ics_getting_history) {
4399       case H_FALSE:
4400       case H_REQUESTED:
4401         break;
4402       case H_GOT_REQ_HEADER:
4403       case H_GOT_UNREQ_HEADER:
4404         /* This is the initial position of the current game */
4405         gamenum = ics_gamenum;
4406         moveNum = 0;            /* old ICS bug workaround */
4407         if (to_play == 'B') {
4408           startedFromSetupPosition = TRUE;
4409           blackPlaysFirst = TRUE;
4410           moveNum = 1;
4411           if (forwardMostMove == 0) forwardMostMove = 1;
4412           if (backwardMostMove == 0) backwardMostMove = 1;
4413           if (currentMove == 0) currentMove = 1;
4414         }
4415         newGameMode = gameMode;
4416         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4417         break;
4418       case H_GOT_UNWANTED_HEADER:
4419         /* This is an initial board that we don't want */
4420         return;
4421       case H_GETTING_MOVES:
4422         /* Should not happen */
4423         DisplayError(_("Error gathering move list: extra board"), 0);
4424         ics_getting_history = H_FALSE;
4425         return;
4426     }
4427
4428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4429                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4430                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4431      /* [HGM] We seem to have switched variant unexpectedly
4432       * Try to guess new variant from board size
4433       */
4434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4438           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4439           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4440           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4441           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4442           /* Get a move list just to see the header, which
4443              will tell us whether this is really bug or zh */
4444           if (ics_getting_history == H_FALSE) {
4445             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4446             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4447             SendToICS(str);
4448           }
4449     }
4450
4451     /* Take action if this is the first board of a new game, or of a
4452        different game than is currently being displayed.  */
4453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4454         relation == RELATION_ISOLATED_BOARD) {
4455
4456         /* Forget the old game and get the history (if any) of the new one */
4457         if (gameMode != BeginningOfGame) {
4458           Reset(TRUE, TRUE);
4459         }
4460         newGame = TRUE;
4461         if (appData.autoRaiseBoard) BoardToTop();
4462         prevMove = -3;
4463         if (gamenum == -1) {
4464             newGameMode = IcsIdle;
4465         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4466                    appData.getMoveList && !reqFlag) {
4467             /* Need to get game history */
4468             ics_getting_history = H_REQUESTED;
4469             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4470             SendToICS(str);
4471         }
4472
4473         /* Initially flip the board to have black on the bottom if playing
4474            black or if the ICS flip flag is set, but let the user change
4475            it with the Flip View button. */
4476         flipView = appData.autoFlipView ?
4477           (newGameMode == IcsPlayingBlack) || ics_flip :
4478           appData.flipView;
4479
4480         /* Done with values from previous mode; copy in new ones */
4481         gameMode = newGameMode;
4482         ModeHighlight();
4483         ics_gamenum = gamenum;
4484         if (gamenum == gs_gamenum) {
4485             int klen = strlen(gs_kind);
4486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4487             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4488             gameInfo.event = StrSave(str);
4489         } else {
4490             gameInfo.event = StrSave("ICS game");
4491         }
4492         gameInfo.site = StrSave(appData.icsHost);
4493         gameInfo.date = PGNDate();
4494         gameInfo.round = StrSave("-");
4495         gameInfo.white = StrSave(white);
4496         gameInfo.black = StrSave(black);
4497         timeControl = basetime * 60 * 1000;
4498         timeControl_2 = 0;
4499         timeIncrement = increment * 1000;
4500         movesPerSession = 0;
4501         gameInfo.timeControl = TimeControlTagValue();
4502         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4503   if (appData.debugMode) {
4504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4506     setbuf(debugFP, NULL);
4507   }
4508
4509         gameInfo.outOfBook = NULL;
4510
4511         /* Do we have the ratings? */
4512         if (strcmp(player1Name, white) == 0 &&
4513             strcmp(player2Name, black) == 0) {
4514             if (appData.debugMode)
4515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4516                       player1Rating, player2Rating);
4517             gameInfo.whiteRating = player1Rating;
4518             gameInfo.blackRating = player2Rating;
4519         } else if (strcmp(player2Name, white) == 0 &&
4520                    strcmp(player1Name, black) == 0) {
4521             if (appData.debugMode)
4522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4523                       player2Rating, player1Rating);
4524             gameInfo.whiteRating = player2Rating;
4525             gameInfo.blackRating = player1Rating;
4526         }
4527         player1Name[0] = player2Name[0] = NULLCHAR;
4528
4529         /* Silence shouts if requested */
4530         if (appData.quietPlay &&
4531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4532             SendToICS(ics_prefix);
4533             SendToICS("set shout 0\n");
4534         }
4535     }
4536
4537     /* Deal with midgame name changes */
4538     if (!newGame) {
4539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4540             if (gameInfo.white) free(gameInfo.white);
4541             gameInfo.white = StrSave(white);
4542         }
4543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4544             if (gameInfo.black) free(gameInfo.black);
4545             gameInfo.black = StrSave(black);
4546         }
4547     }
4548
4549     /* Throw away game result if anything actually changes in examine mode */
4550     if (gameMode == IcsExamining && !newGame) {
4551         gameInfo.result = GameUnfinished;
4552         if (gameInfo.resultDetails != NULL) {
4553             free(gameInfo.resultDetails);
4554             gameInfo.resultDetails = NULL;
4555         }
4556     }
4557
4558     /* In pausing && IcsExamining mode, we ignore boards coming
4559        in if they are in a different variation than we are. */
4560     if (pauseExamInvalid) return;
4561     if (pausing && gameMode == IcsExamining) {
4562         if (moveNum <= pauseExamForwardMostMove) {
4563             pauseExamInvalid = TRUE;
4564             forwardMostMove = pauseExamForwardMostMove;
4565             return;
4566         }
4567     }
4568
4569   if (appData.debugMode) {
4570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4571   }
4572     /* Parse the board */
4573     for (k = 0; k < ranks; k++) {
4574       for (j = 0; j < files; j++)
4575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4576       if(gameInfo.holdingsWidth > 1) {
4577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4579       }
4580     }
4581     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4582       board[5][BOARD_RGHT+1] = WhiteAngel;
4583       board[6][BOARD_RGHT+1] = WhiteMarshall;
4584       board[1][0] = BlackMarshall;
4585       board[2][0] = BlackAngel;
4586       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4587     }
4588     CopyBoard(boards[moveNum], board);
4589     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4590     if (moveNum == 0) {
4591         startedFromSetupPosition =
4592           !CompareBoards(board, initialPosition);
4593         if(startedFromSetupPosition)
4594             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4595     }
4596
4597     /* [HGM] Set castling rights. Take the outermost Rooks,
4598        to make it also work for FRC opening positions. Note that board12
4599        is really defective for later FRC positions, as it has no way to
4600        indicate which Rook can castle if they are on the same side of King.
4601        For the initial position we grant rights to the outermost Rooks,
4602        and remember thos rights, and we then copy them on positions
4603        later in an FRC game. This means WB might not recognize castlings with
4604        Rooks that have moved back to their original position as illegal,
4605        but in ICS mode that is not its job anyway.
4606     */
4607     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4608     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4609
4610         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4611             if(board[0][i] == WhiteRook) j = i;
4612         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4614             if(board[0][i] == WhiteRook) j = i;
4615         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4616         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4617             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4618         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4619         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4620             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4621         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4622
4623         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4624         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4625         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4626             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4627         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4628             if(board[BOARD_HEIGHT-1][k] == bKing)
4629                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4630         if(gameInfo.variant == VariantTwoKings) {
4631             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4632             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4633             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4634         }
4635     } else { int r;
4636         r = boards[moveNum][CASTLING][0] = initialRights[0];
4637         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4638         r = boards[moveNum][CASTLING][1] = initialRights[1];
4639         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4640         r = boards[moveNum][CASTLING][3] = initialRights[3];
4641         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4642         r = boards[moveNum][CASTLING][4] = initialRights[4];
4643         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4644         /* wildcastle kludge: always assume King has rights */
4645         r = boards[moveNum][CASTLING][2] = initialRights[2];
4646         r = boards[moveNum][CASTLING][5] = initialRights[5];
4647     }
4648     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4649     boards[moveNum][EP_STATUS] = EP_NONE;
4650     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4651     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4652     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4653
4654
4655     if (ics_getting_history == H_GOT_REQ_HEADER ||
4656         ics_getting_history == H_GOT_UNREQ_HEADER) {
4657         /* This was an initial position from a move list, not
4658            the current position */
4659         return;
4660     }
4661
4662     /* Update currentMove and known move number limits */
4663     newMove = newGame || moveNum > forwardMostMove;
4664
4665     if (newGame) {
4666         forwardMostMove = backwardMostMove = currentMove = moveNum;
4667         if (gameMode == IcsExamining && moveNum == 0) {
4668           /* Workaround for ICS limitation: we are not told the wild
4669              type when starting to examine a game.  But if we ask for
4670              the move list, the move list header will tell us */
4671             ics_getting_history = H_REQUESTED;
4672             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4673             SendToICS(str);
4674         }
4675     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4676                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4677 #if ZIPPY
4678         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4679         /* [HGM] applied this also to an engine that is silently watching        */
4680         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4681             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4682             gameInfo.variant == currentlyInitializedVariant) {
4683           takeback = forwardMostMove - moveNum;
4684           for (i = 0; i < takeback; i++) {
4685             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4686             SendToProgram("undo\n", &first);
4687           }
4688         }
4689 #endif
4690
4691         forwardMostMove = moveNum;
4692         if (!pausing || currentMove > forwardMostMove)
4693           currentMove = forwardMostMove;
4694     } else {
4695         /* New part of history that is not contiguous with old part */
4696         if (pausing && gameMode == IcsExamining) {
4697             pauseExamInvalid = TRUE;
4698             forwardMostMove = pauseExamForwardMostMove;
4699             return;
4700         }
4701         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4702 #if ZIPPY
4703             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4704                 // [HGM] when we will receive the move list we now request, it will be
4705                 // fed to the engine from the first move on. So if the engine is not
4706                 // in the initial position now, bring it there.
4707                 InitChessProgram(&first, 0);
4708             }
4709 #endif
4710             ics_getting_history = H_REQUESTED;
4711             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4712             SendToICS(str);
4713         }
4714         forwardMostMove = backwardMostMove = currentMove = moveNum;
4715     }
4716
4717     /* Update the clocks */
4718     if (strchr(elapsed_time, '.')) {
4719       /* Time is in ms */
4720       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4721       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4722     } else {
4723       /* Time is in seconds */
4724       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4725       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4726     }
4727
4728
4729 #if ZIPPY
4730     if (appData.zippyPlay && newGame &&
4731         gameMode != IcsObserving && gameMode != IcsIdle &&
4732         gameMode != IcsExamining)
4733       ZippyFirstBoard(moveNum, basetime, increment);
4734 #endif
4735
4736     /* Put the move on the move list, first converting
4737        to canonical algebraic form. */
4738     if (moveNum > 0) {
4739   if (appData.debugMode) {
4740     int f = forwardMostMove;
4741     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4742             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4743             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4744     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4745     fprintf(debugFP, "moveNum = %d\n", moveNum);
4746     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4747     setbuf(debugFP, NULL);
4748   }
4749         if (moveNum <= backwardMostMove) {
4750             /* We don't know what the board looked like before
4751                this move.  Punt. */
4752           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4753             strcat(parseList[moveNum - 1], " ");
4754             strcat(parseList[moveNum - 1], elapsed_time);
4755             moveList[moveNum - 1][0] = NULLCHAR;
4756         } else if (strcmp(move_str, "none") == 0) {
4757             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4758             /* Again, we don't know what the board looked like;
4759                this is really the start of the game. */
4760             parseList[moveNum - 1][0] = NULLCHAR;
4761             moveList[moveNum - 1][0] = NULLCHAR;
4762             backwardMostMove = moveNum;
4763             startedFromSetupPosition = TRUE;
4764             fromX = fromY = toX = toY = -1;
4765         } else {
4766           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4767           //                 So we parse the long-algebraic move string in stead of the SAN move
4768           int valid; char buf[MSG_SIZ], *prom;
4769
4770           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4771                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4772           // str looks something like "Q/a1-a2"; kill the slash
4773           if(str[1] == '/')
4774             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4775           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4776           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4777                 strcat(buf, prom); // long move lacks promo specification!
4778           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4779                 if(appData.debugMode)
4780                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4781                 safeStrCpy(move_str, buf, MSG_SIZ);
4782           }
4783           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4784                                 &fromX, &fromY, &toX, &toY, &promoChar)
4785                || ParseOneMove(buf, moveNum - 1, &moveType,
4786                                 &fromX, &fromY, &toX, &toY, &promoChar);
4787           // end of long SAN patch
4788           if (valid) {
4789             (void) CoordsToAlgebraic(boards[moveNum - 1],
4790                                      PosFlags(moveNum - 1),
4791                                      fromY, fromX, toY, toX, promoChar,
4792                                      parseList[moveNum-1]);
4793             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4794               case MT_NONE:
4795               case MT_STALEMATE:
4796               default:
4797                 break;
4798               case MT_CHECK:
4799                 if(gameInfo.variant != VariantShogi)
4800                     strcat(parseList[moveNum - 1], "+");
4801                 break;
4802               case MT_CHECKMATE:
4803               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4804                 strcat(parseList[moveNum - 1], "#");
4805                 break;
4806             }
4807             strcat(parseList[moveNum - 1], " ");
4808             strcat(parseList[moveNum - 1], elapsed_time);
4809             /* currentMoveString is set as a side-effect of ParseOneMove */
4810             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4811             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4812             strcat(moveList[moveNum - 1], "\n");
4813
4814             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4815                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4816               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4817                 ChessSquare old, new = boards[moveNum][k][j];
4818                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4819                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4820                   if(old == new) continue;
4821                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4822                   else if(new == WhiteWazir || new == BlackWazir) {
4823                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4824                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4825                       else boards[moveNum][k][j] = old; // preserve type of Gold
4826                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4827                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4828               }
4829           } else {
4830             /* Move from ICS was illegal!?  Punt. */
4831             if (appData.debugMode) {
4832               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4833               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4834             }
4835             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4836             strcat(parseList[moveNum - 1], " ");
4837             strcat(parseList[moveNum - 1], elapsed_time);
4838             moveList[moveNum - 1][0] = NULLCHAR;
4839             fromX = fromY = toX = toY = -1;
4840           }
4841         }
4842   if (appData.debugMode) {
4843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4844     setbuf(debugFP, NULL);
4845   }
4846
4847 #if ZIPPY
4848         /* Send move to chess program (BEFORE animating it). */
4849         if (appData.zippyPlay && !newGame && newMove &&
4850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4851
4852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4855                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4856                             move_str);
4857                     DisplayError(str, 0);
4858                 } else {
4859                     if (first.sendTime) {
4860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4861                     }
4862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4863                     if (firstMove && !bookHit) {
4864                         firstMove = FALSE;
4865                         if (first.useColors) {
4866                           SendToProgram(gameMode == IcsPlayingWhite ?
4867                                         "white\ngo\n" :
4868                                         "black\ngo\n", &first);
4869                         } else {
4870                           SendToProgram("go\n", &first);
4871                         }
4872                         first.maybeThinking = TRUE;
4873                     }
4874                 }
4875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4876               if (moveList[moveNum - 1][0] == NULLCHAR) {
4877                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4878                 DisplayError(str, 0);
4879               } else {
4880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4881                 SendMoveToProgram(moveNum - 1, &first);
4882               }
4883             }
4884         }
4885 #endif
4886     }
4887
4888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4889         /* If move comes from a remote source, animate it.  If it
4890            isn't remote, it will have already been animated. */
4891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4893         }
4894         if (!pausing && appData.highlightLastMove) {
4895             SetHighlights(fromX, fromY, toX, toY);
4896         }
4897     }
4898
4899     /* Start the clocks */
4900     whiteFlag = blackFlag = FALSE;
4901     appData.clockMode = !(basetime == 0 && increment == 0);
4902     if (ticking == 0) {
4903       ics_clock_paused = TRUE;
4904       StopClocks();
4905     } else if (ticking == 1) {
4906       ics_clock_paused = FALSE;
4907     }
4908     if (gameMode == IcsIdle ||
4909         relation == RELATION_OBSERVING_STATIC ||
4910         relation == RELATION_EXAMINING ||
4911         ics_clock_paused)
4912       DisplayBothClocks();
4913     else
4914       StartClocks();
4915
4916     /* Display opponents and material strengths */
4917     if (gameInfo.variant != VariantBughouse &&
4918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4919         if (tinyLayout || smallLayout) {
4920             if(gameInfo.variant == VariantNormal)
4921               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4923                     basetime, increment);
4924             else
4925               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4927                     basetime, increment, (int) gameInfo.variant);
4928         } else {
4929             if(gameInfo.variant == VariantNormal)
4930               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4931                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4932                     basetime, increment);
4933             else
4934               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4935                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4936                     basetime, increment, VariantName(gameInfo.variant));
4937         }
4938         DisplayTitle(str);
4939   if (appData.debugMode) {
4940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4941   }
4942     }
4943
4944
4945     /* Display the board */
4946     if (!pausing && !appData.noGUI) {
4947
4948       if (appData.premove)
4949           if (!gotPremove ||
4950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4952               ClearPremoveHighlights();
4953
4954       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4955         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4956       DrawPosition(j, boards[currentMove]);
4957
4958       DisplayMove(moveNum - 1);
4959       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4960             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4961               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4962         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4963       }
4964     }
4965
4966     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4967 #if ZIPPY
4968     if(bookHit) { // [HGM] book: simulate book reply
4969         static char bookMove[MSG_SIZ]; // a bit generous?
4970
4971         programStats.nodes = programStats.depth = programStats.time =
4972         programStats.score = programStats.got_only_move = 0;
4973         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4974
4975         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4976         strcat(bookMove, bookHit);
4977         HandleMachineMove(bookMove, &first);
4978     }
4979 #endif
4980 }
4981
4982 void
4983 GetMoveListEvent ()
4984 {
4985     char buf[MSG_SIZ];
4986     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4987         ics_getting_history = H_REQUESTED;
4988         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4989         SendToICS(buf);
4990     }
4991 }
4992
4993 void
4994 SendToBoth (char *msg)
4995 {   // to make it easy to keep two engines in step in dual analysis
4996     SendToProgram(msg, &first);
4997     if(second.analyzing) SendToProgram(msg, &second);
4998 }
4999
5000 void
5001 AnalysisPeriodicEvent (int force)
5002 {
5003     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5004          && !force) || !appData.periodicUpdates)
5005       return;
5006
5007     /* Send . command to Crafty to collect stats */
5008     SendToBoth(".\n");
5009
5010     /* Don't send another until we get a response (this makes
5011        us stop sending to old Crafty's which don't understand
5012        the "." command (sending illegal cmds resets node count & time,
5013        which looks bad)) */
5014     programStats.ok_to_send = 0;
5015 }
5016
5017 void
5018 ics_update_width (int new_width)
5019 {
5020         ics_printf("set width %d\n", new_width);
5021 }
5022
5023 void
5024 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5025 {
5026     char buf[MSG_SIZ];
5027
5028     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5029         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5030             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5031             SendToProgram(buf, cps);
5032             return;
5033         }
5034         // null move in variant where engine does not understand it (for analysis purposes)
5035         SendBoard(cps, moveNum + 1); // send position after move in stead.
5036         return;
5037     }
5038     if (cps->useUsermove) {
5039       SendToProgram("usermove ", cps);
5040     }
5041     if (cps->useSAN) {
5042       char *space;
5043       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5044         int len = space - parseList[moveNum];
5045         memcpy(buf, parseList[moveNum], len);
5046         buf[len++] = '\n';
5047         buf[len] = NULLCHAR;
5048       } else {
5049         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5050       }
5051       SendToProgram(buf, cps);
5052     } else {
5053       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5054         AlphaRank(moveList[moveNum], 4);
5055         SendToProgram(moveList[moveNum], cps);
5056         AlphaRank(moveList[moveNum], 4); // and back
5057       } else
5058       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5059        * the engine. It would be nice to have a better way to identify castle
5060        * moves here. */
5061       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5062                                                                          && cps->useOOCastle) {
5063         int fromX = moveList[moveNum][0] - AAA;
5064         int fromY = moveList[moveNum][1] - ONE;
5065         int toX = moveList[moveNum][2] - AAA;
5066         int toY = moveList[moveNum][3] - ONE;
5067         if((boards[moveNum][fromY][fromX] == WhiteKing
5068             && boards[moveNum][toY][toX] == WhiteRook)
5069            || (boards[moveNum][fromY][fromX] == BlackKing
5070                && boards[moveNum][toY][toX] == BlackRook)) {
5071           if(toX > fromX) SendToProgram("O-O\n", cps);
5072           else SendToProgram("O-O-O\n", cps);
5073         }
5074         else SendToProgram(moveList[moveNum], cps);
5075       } else
5076       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5077           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5078                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5079                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5080                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5081           SendToProgram(buf, cps);
5082       } else
5083       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5084         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5085           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5086           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5087                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5088         } else
5089           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5090                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5091         SendToProgram(buf, cps);
5092       }
5093       else SendToProgram(moveList[moveNum], cps);
5094       /* End of additions by Tord */
5095     }
5096
5097     /* [HGM] setting up the opening has brought engine in force mode! */
5098     /*       Send 'go' if we are in a mode where machine should play. */
5099     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5100         (gameMode == TwoMachinesPlay   ||
5101 #if ZIPPY
5102          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5103 #endif
5104          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5105         SendToProgram("go\n", cps);
5106   if (appData.debugMode) {
5107     fprintf(debugFP, "(extra)\n");
5108   }
5109     }
5110     setboardSpoiledMachineBlack = 0;
5111 }
5112
5113 void
5114 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5115 {
5116     char user_move[MSG_SIZ];
5117     char suffix[4];
5118
5119     if(gameInfo.variant == VariantSChess && promoChar) {
5120         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5121         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5122     } else suffix[0] = NULLCHAR;
5123
5124     switch (moveType) {
5125       default:
5126         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5127                 (int)moveType, fromX, fromY, toX, toY);
5128         DisplayError(user_move + strlen("say "), 0);
5129         break;
5130       case WhiteKingSideCastle:
5131       case BlackKingSideCastle:
5132       case WhiteQueenSideCastleWild:
5133       case BlackQueenSideCastleWild:
5134       /* PUSH Fabien */
5135       case WhiteHSideCastleFR:
5136       case BlackHSideCastleFR:
5137       /* POP Fabien */
5138         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5139         break;
5140       case WhiteQueenSideCastle:
5141       case BlackQueenSideCastle:
5142       case WhiteKingSideCastleWild:
5143       case BlackKingSideCastleWild:
5144       /* PUSH Fabien */
5145       case WhiteASideCastleFR:
5146       case BlackASideCastleFR:
5147       /* POP Fabien */
5148         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5149         break;
5150       case WhiteNonPromotion:
5151       case BlackNonPromotion:
5152         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5153         break;
5154       case WhitePromotion:
5155       case BlackPromotion:
5156         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5157            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5158           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5159                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5160                 PieceToChar(WhiteFerz));
5161         else if(gameInfo.variant == VariantGreat)
5162           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5163                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5164                 PieceToChar(WhiteMan));
5165         else
5166           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5167                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5168                 promoChar);
5169         break;
5170       case WhiteDrop:
5171       case BlackDrop:
5172       drop:
5173         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5174                  ToUpper(PieceToChar((ChessSquare) fromX)),
5175                  AAA + toX, ONE + toY);
5176         break;
5177       case IllegalMove:  /* could be a variant we don't quite understand */
5178         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5179       case NormalMove:
5180       case WhiteCapturesEnPassant:
5181       case BlackCapturesEnPassant:
5182         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5184         break;
5185     }
5186     SendToICS(user_move);
5187     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5188         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5189 }
5190
5191 void
5192 UploadGameEvent ()
5193 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5194     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5195     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5196     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5197       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5198       return;
5199     }
5200     if(gameMode != IcsExamining) { // is this ever not the case?
5201         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5202
5203         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5204           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5205         } else { // on FICS we must first go to general examine mode
5206           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5207         }
5208         if(gameInfo.variant != VariantNormal) {
5209             // try figure out wild number, as xboard names are not always valid on ICS
5210             for(i=1; i<=36; i++) {
5211               snprintf(buf, MSG_SIZ, "wild/%d", i);
5212                 if(StringToVariant(buf) == gameInfo.variant) break;
5213             }
5214             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5215             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5216             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5217         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5218         SendToICS(ics_prefix);
5219         SendToICS(buf);
5220         if(startedFromSetupPosition || backwardMostMove != 0) {
5221           fen = PositionToFEN(backwardMostMove, NULL, 1);
5222           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5223             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5224             SendToICS(buf);
5225           } else { // FICS: everything has to set by separate bsetup commands
5226             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5227             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5228             SendToICS(buf);
5229             if(!WhiteOnMove(backwardMostMove)) {
5230                 SendToICS("bsetup tomove black\n");
5231             }
5232             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5233             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5234             SendToICS(buf);
5235             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5236             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5237             SendToICS(buf);
5238             i = boards[backwardMostMove][EP_STATUS];
5239             if(i >= 0) { // set e.p.
5240               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5241                 SendToICS(buf);
5242             }
5243             bsetup++;
5244           }
5245         }
5246       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5247             SendToICS("bsetup done\n"); // switch to normal examining.
5248     }
5249     for(i = backwardMostMove; i<last; i++) {
5250         char buf[20];
5251         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5252         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5253             int len = strlen(moveList[i]);
5254             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5255             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5256         }
5257         SendToICS(buf);
5258     }
5259     SendToICS(ics_prefix);
5260     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5261 }
5262
5263 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5264
5265 void
5266 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5267 {
5268     if (rf == DROP_RANK) {
5269       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5270       sprintf(move, "%c@%c%c\n",
5271                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5272     } else {
5273         if (promoChar == 'x' || promoChar == NULLCHAR) {
5274           sprintf(move, "%c%c%c%c\n",
5275                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5276           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5277         } else {
5278             sprintf(move, "%c%c%c%c%c\n",
5279                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5280         }
5281     }
5282 }
5283
5284 void
5285 ProcessICSInitScript (FILE *f)
5286 {
5287     char buf[MSG_SIZ];
5288
5289     while (fgets(buf, MSG_SIZ, f)) {
5290         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5291     }
5292
5293     fclose(f);
5294 }
5295
5296
5297 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5298 int dragging;
5299 static ClickType lastClickType;
5300
5301 int
5302 Partner (ChessSquare *p)
5303 { // change piece into promotion partner if one shogi-promotes to the other
5304   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5305   ChessSquare partner;
5306   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5307   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5308   *p = partner;
5309   return 1;
5310 }
5311
5312 void
5313 Sweep (int step)
5314 {
5315     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5316     static int toggleFlag;
5317     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5318     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5319     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5320     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5321     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5322     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5323     do {
5324         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5325         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5326         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5327         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5328         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5329         if(!step) step = -1;
5330     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5331             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5332             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5333             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5334     if(toX >= 0) {
5335         int victim = boards[currentMove][toY][toX];
5336         boards[currentMove][toY][toX] = promoSweep;
5337         DrawPosition(FALSE, boards[currentMove]);
5338         boards[currentMove][toY][toX] = victim;
5339     } else
5340     ChangeDragPiece(promoSweep);
5341 }
5342
5343 int
5344 PromoScroll (int x, int y)
5345 {
5346   int step = 0;
5347
5348   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5349   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5350   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5351   if(!step) return FALSE;
5352   lastX = x; lastY = y;
5353   if((promoSweep < BlackPawn) == flipView) step = -step;
5354   if(step > 0) selectFlag = 1;
5355   if(!selectFlag) Sweep(step);
5356   return FALSE;
5357 }
5358
5359 void
5360 NextPiece (int step)
5361 {
5362     ChessSquare piece = boards[currentMove][toY][toX];
5363     do {
5364         pieceSweep -= step;
5365         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5366         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5367         if(!step) step = -1;
5368     } while(PieceToChar(pieceSweep) == '.');
5369     boards[currentMove][toY][toX] = pieceSweep;
5370     DrawPosition(FALSE, boards[currentMove]);
5371     boards[currentMove][toY][toX] = piece;
5372 }
5373 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5374 void
5375 AlphaRank (char *move, int n)
5376 {
5377 //    char *p = move, c; int x, y;
5378
5379     if (appData.debugMode) {
5380         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5381     }
5382
5383     if(move[1]=='*' &&
5384        move[2]>='0' && move[2]<='9' &&
5385        move[3]>='a' && move[3]<='x'    ) {
5386         move[1] = '@';
5387         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5388         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5389     } else
5390     if(move[0]>='0' && move[0]<='9' &&
5391        move[1]>='a' && move[1]<='x' &&
5392        move[2]>='0' && move[2]<='9' &&
5393        move[3]>='a' && move[3]<='x'    ) {
5394         /* input move, Shogi -> normal */
5395         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5396         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5397         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5398         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5399     } else
5400     if(move[1]=='@' &&
5401        move[3]>='0' && move[3]<='9' &&
5402        move[2]>='a' && move[2]<='x'    ) {
5403         move[1] = '*';
5404         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5405         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5406     } else
5407     if(
5408        move[0]>='a' && move[0]<='x' &&
5409        move[3]>='0' && move[3]<='9' &&
5410        move[2]>='a' && move[2]<='x'    ) {
5411          /* output move, normal -> Shogi */
5412         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5413         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5414         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5415         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5416         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5417     }
5418     if (appData.debugMode) {
5419         fprintf(debugFP, "   out = '%s'\n", move);
5420     }
5421 }
5422
5423 char yy_textstr[8000];
5424
5425 /* Parser for moves from gnuchess, ICS, or user typein box */
5426 Boolean
5427 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5428 {
5429     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5430
5431     switch (*moveType) {
5432       case WhitePromotion:
5433       case BlackPromotion:
5434       case WhiteNonPromotion:
5435       case BlackNonPromotion:
5436       case NormalMove:
5437       case FirstLeg:
5438       case WhiteCapturesEnPassant:
5439       case BlackCapturesEnPassant:
5440       case WhiteKingSideCastle:
5441       case WhiteQueenSideCastle:
5442       case BlackKingSideCastle:
5443       case BlackQueenSideCastle:
5444       case WhiteKingSideCastleWild:
5445       case WhiteQueenSideCastleWild:
5446       case BlackKingSideCastleWild:
5447       case BlackQueenSideCastleWild:
5448       /* Code added by Tord: */
5449       case WhiteHSideCastleFR:
5450       case WhiteASideCastleFR:
5451       case BlackHSideCastleFR:
5452       case BlackASideCastleFR:
5453       /* End of code added by Tord */
5454       case IllegalMove:         /* bug or odd chess variant */
5455         *fromX = currentMoveString[0] - AAA;
5456         *fromY = currentMoveString[1] - ONE;
5457         *toX = currentMoveString[2] - AAA;
5458         *toY = currentMoveString[3] - ONE;
5459         *promoChar = currentMoveString[4];
5460         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5461             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5462     if (appData.debugMode) {
5463         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5464     }
5465             *fromX = *fromY = *toX = *toY = 0;
5466             return FALSE;
5467         }
5468         if (appData.testLegality) {
5469           return (*moveType != IllegalMove);
5470         } else {
5471           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5472                          // [HGM] lion: if this is a double move we are less critical
5473                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5474         }
5475
5476       case WhiteDrop:
5477       case BlackDrop:
5478         *fromX = *moveType == WhiteDrop ?
5479           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5480           (int) CharToPiece(ToLower(currentMoveString[0]));
5481         *fromY = DROP_RANK;
5482         *toX = currentMoveString[2] - AAA;
5483         *toY = currentMoveString[3] - ONE;
5484         *promoChar = NULLCHAR;
5485         return TRUE;
5486
5487       case AmbiguousMove:
5488       case ImpossibleMove:
5489       case EndOfFile:
5490       case ElapsedTime:
5491       case Comment:
5492       case PGNTag:
5493       case NAG:
5494       case WhiteWins:
5495       case BlackWins:
5496       case GameIsDrawn:
5497       default:
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5500     }
5501         /* bug? */
5502         *fromX = *fromY = *toX = *toY = 0;
5503         *promoChar = NULLCHAR;
5504         return FALSE;
5505     }
5506 }
5507
5508 Boolean pushed = FALSE;
5509 char *lastParseAttempt;
5510
5511 void
5512 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5513 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5514   int fromX, fromY, toX, toY; char promoChar;
5515   ChessMove moveType;
5516   Boolean valid;
5517   int nr = 0;
5518
5519   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5520   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5521     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5522     pushed = TRUE;
5523   }
5524   endPV = forwardMostMove;
5525   do {
5526     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5527     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5528     lastParseAttempt = pv;
5529     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5530     if(!valid && nr == 0 &&
5531        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5532         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5533         // Hande case where played move is different from leading PV move
5534         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5535         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5536         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5537         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5538           endPV += 2; // if position different, keep this
5539           moveList[endPV-1][0] = fromX + AAA;
5540           moveList[endPV-1][1] = fromY + ONE;
5541           moveList[endPV-1][2] = toX + AAA;
5542           moveList[endPV-1][3] = toY + ONE;
5543           parseList[endPV-1][0] = NULLCHAR;
5544           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5545         }
5546       }
5547     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5548     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5549     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5550     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5551         valid++; // allow comments in PV
5552         continue;
5553     }
5554     nr++;
5555     if(endPV+1 > framePtr) break; // no space, truncate
5556     if(!valid) break;
5557     endPV++;
5558     CopyBoard(boards[endPV], boards[endPV-1]);
5559     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5560     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5561     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5562     CoordsToAlgebraic(boards[endPV - 1],
5563                              PosFlags(endPV - 1),
5564                              fromY, fromX, toY, toX, promoChar,
5565                              parseList[endPV - 1]);
5566   } while(valid);
5567   if(atEnd == 2) return; // used hidden, for PV conversion
5568   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5569   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5570   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5571                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5572   DrawPosition(TRUE, boards[currentMove]);
5573 }
5574
5575 int
5576 MultiPV (ChessProgramState *cps)
5577 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5578         int i;
5579         for(i=0; i<cps->nrOptions; i++)
5580             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5581                 return i;
5582         return -1;
5583 }
5584
5585 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5586
5587 Boolean
5588 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5589 {
5590         int startPV, multi, lineStart, origIndex = index;
5591         char *p, buf2[MSG_SIZ];
5592         ChessProgramState *cps = (pane ? &second : &first);
5593
5594         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5595         lastX = x; lastY = y;
5596         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5597         lineStart = startPV = index;
5598         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5599         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5600         index = startPV;
5601         do{ while(buf[index] && buf[index] != '\n') index++;
5602         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5603         buf[index] = 0;
5604         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5605                 int n = cps->option[multi].value;
5606                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5607                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5608                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5609                 cps->option[multi].value = n;
5610                 *start = *end = 0;
5611                 return FALSE;
5612         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5613                 ExcludeClick(origIndex - lineStart);
5614                 return FALSE;
5615         }
5616         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5617         *start = startPV; *end = index-1;
5618         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5619         return TRUE;
5620 }
5621
5622 char *
5623 PvToSAN (char *pv)
5624 {
5625         static char buf[10*MSG_SIZ];
5626         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5627         *buf = NULLCHAR;
5628         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5629         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5630         for(i = forwardMostMove; i<endPV; i++){
5631             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5632             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5633             k += strlen(buf+k);
5634         }
5635         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5636         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5637         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5638         endPV = savedEnd;
5639         return buf;
5640 }
5641
5642 Boolean
5643 LoadPV (int x, int y)
5644 { // called on right mouse click to load PV
5645   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5646   lastX = x; lastY = y;
5647   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5648   extendGame = FALSE;
5649   return TRUE;
5650 }
5651
5652 void
5653 UnLoadPV ()
5654 {
5655   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5656   if(endPV < 0) return;
5657   if(appData.autoCopyPV) CopyFENToClipboard();
5658   endPV = -1;
5659   if(extendGame && currentMove > forwardMostMove) {
5660         Boolean saveAnimate = appData.animate;
5661         if(pushed) {
5662             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5663                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5664             } else storedGames--; // abandon shelved tail of original game
5665         }
5666         pushed = FALSE;
5667         forwardMostMove = currentMove;
5668         currentMove = oldFMM;
5669         appData.animate = FALSE;
5670         ToNrEvent(forwardMostMove);
5671         appData.animate = saveAnimate;
5672   }
5673   currentMove = forwardMostMove;
5674   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5675   ClearPremoveHighlights();
5676   DrawPosition(TRUE, boards[currentMove]);
5677 }
5678
5679 void
5680 MovePV (int x, int y, int h)
5681 { // step through PV based on mouse coordinates (called on mouse move)
5682   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5683
5684   // we must somehow check if right button is still down (might be released off board!)
5685   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5686   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5687   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5688   if(!step) return;
5689   lastX = x; lastY = y;
5690
5691   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5692   if(endPV < 0) return;
5693   if(y < margin) step = 1; else
5694   if(y > h - margin) step = -1;
5695   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5696   currentMove += step;
5697   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5698   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5699                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5700   DrawPosition(FALSE, boards[currentMove]);
5701 }
5702
5703
5704 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5705 // All positions will have equal probability, but the current method will not provide a unique
5706 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5707 #define DARK 1
5708 #define LITE 2
5709 #define ANY 3
5710
5711 int squaresLeft[4];
5712 int piecesLeft[(int)BlackPawn];
5713 int seed, nrOfShuffles;
5714
5715 void
5716 GetPositionNumber ()
5717 {       // sets global variable seed
5718         int i;
5719
5720         seed = appData.defaultFrcPosition;
5721         if(seed < 0) { // randomize based on time for negative FRC position numbers
5722                 for(i=0; i<50; i++) seed += random();
5723                 seed = random() ^ random() >> 8 ^ random() << 8;
5724                 if(seed<0) seed = -seed;
5725         }
5726 }
5727
5728 int
5729 put (Board board, int pieceType, int rank, int n, int shade)
5730 // put the piece on the (n-1)-th empty squares of the given shade
5731 {
5732         int i;
5733
5734         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5735                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5736                         board[rank][i] = (ChessSquare) pieceType;
5737                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5738                         squaresLeft[ANY]--;
5739                         piecesLeft[pieceType]--;
5740                         return i;
5741                 }
5742         }
5743         return -1;
5744 }
5745
5746
5747 void
5748 AddOnePiece (Board board, int pieceType, int rank, int shade)
5749 // calculate where the next piece goes, (any empty square), and put it there
5750 {
5751         int i;
5752
5753         i = seed % squaresLeft[shade];
5754         nrOfShuffles *= squaresLeft[shade];
5755         seed /= squaresLeft[shade];
5756         put(board, pieceType, rank, i, shade);
5757 }
5758
5759 void
5760 AddTwoPieces (Board board, int pieceType, int rank)
5761 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5762 {
5763         int i, n=squaresLeft[ANY], j=n-1, k;
5764
5765         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5766         i = seed % k;  // pick one
5767         nrOfShuffles *= k;
5768         seed /= k;
5769         while(i >= j) i -= j--;
5770         j = n - 1 - j; i += j;
5771         put(board, pieceType, rank, j, ANY);
5772         put(board, pieceType, rank, i, ANY);
5773 }
5774
5775 void
5776 SetUpShuffle (Board board, int number)
5777 {
5778         int i, p, first=1;
5779
5780         GetPositionNumber(); nrOfShuffles = 1;
5781
5782         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5783         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5784         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5785
5786         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5787
5788         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5789             p = (int) board[0][i];
5790             if(p < (int) BlackPawn) piecesLeft[p] ++;
5791             board[0][i] = EmptySquare;
5792         }
5793
5794         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5795             // shuffles restricted to allow normal castling put KRR first
5796             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5797                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5798             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5799                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5800             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5801                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5802             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5803                 put(board, WhiteRook, 0, 0, ANY);
5804             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5805         }
5806
5807         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5808             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5809             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5810                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5811                 while(piecesLeft[p] >= 2) {
5812                     AddOnePiece(board, p, 0, LITE);
5813                     AddOnePiece(board, p, 0, DARK);
5814                 }
5815                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5816             }
5817
5818         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5819             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5820             // but we leave King and Rooks for last, to possibly obey FRC restriction
5821             if(p == (int)WhiteRook) continue;
5822             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5823             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5824         }
5825
5826         // now everything is placed, except perhaps King (Unicorn) and Rooks
5827
5828         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5829             // Last King gets castling rights
5830             while(piecesLeft[(int)WhiteUnicorn]) {
5831                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5832                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5833             }
5834
5835             while(piecesLeft[(int)WhiteKing]) {
5836                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5837                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5838             }
5839
5840
5841         } else {
5842             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5843             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5844         }
5845
5846         // Only Rooks can be left; simply place them all
5847         while(piecesLeft[(int)WhiteRook]) {
5848                 i = put(board, WhiteRook, 0, 0, ANY);
5849                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5850                         if(first) {
5851                                 first=0;
5852                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5853                         }
5854                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5855                 }
5856         }
5857         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5858             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5859         }
5860
5861         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5862 }
5863
5864 int
5865 SetCharTable (char *table, const char * map)
5866 /* [HGM] moved here from winboard.c because of its general usefulness */
5867 /*       Basically a safe strcpy that uses the last character as King */
5868 {
5869     int result = FALSE; int NrPieces;
5870
5871     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5872                     && NrPieces >= 12 && !(NrPieces&1)) {
5873         int i; /* [HGM] Accept even length from 12 to 34 */
5874
5875         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5876         for( i=0; i<NrPieces/2-1; i++ ) {
5877             table[i] = map[i];
5878             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5879         }
5880         table[(int) WhiteKing]  = map[NrPieces/2-1];
5881         table[(int) BlackKing]  = map[NrPieces-1];
5882
5883         result = TRUE;
5884     }
5885
5886     return result;
5887 }
5888
5889 void
5890 Prelude (Board board)
5891 {       // [HGM] superchess: random selection of exo-pieces
5892         int i, j, k; ChessSquare p;
5893         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5894
5895         GetPositionNumber(); // use FRC position number
5896
5897         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5898             SetCharTable(pieceToChar, appData.pieceToCharTable);
5899             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5900                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5901         }
5902
5903         j = seed%4;                 seed /= 4;
5904         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5905         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5906         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5907         j = seed%3 + (seed%3 >= j); seed /= 3;
5908         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5909         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5910         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5911         j = seed%3;                 seed /= 3;
5912         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5913         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5914         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5915         j = seed%2 + (seed%2 >= j); seed /= 2;
5916         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5917         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5918         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5919         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5920         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5921         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5922         put(board, exoPieces[0],    0, 0, ANY);
5923         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5924 }
5925
5926 void
5927 InitPosition (int redraw)
5928 {
5929     ChessSquare (* pieces)[BOARD_FILES];
5930     int i, j, pawnRow=1, pieceRows=1, overrule,
5931     oldx = gameInfo.boardWidth,
5932     oldy = gameInfo.boardHeight,
5933     oldh = gameInfo.holdingsWidth;
5934     static int oldv;
5935
5936     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5937
5938     /* [AS] Initialize pv info list [HGM] and game status */
5939     {
5940         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5941             pvInfoList[i].depth = 0;
5942             boards[i][EP_STATUS] = EP_NONE;
5943             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5944         }
5945
5946         initialRulePlies = 0; /* 50-move counter start */
5947
5948         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5949         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5950     }
5951
5952
5953     /* [HGM] logic here is completely changed. In stead of full positions */
5954     /* the initialized data only consist of the two backranks. The switch */
5955     /* selects which one we will use, which is than copied to the Board   */
5956     /* initialPosition, which for the rest is initialized by Pawns and    */
5957     /* empty squares. This initial position is then copied to boards[0],  */
5958     /* possibly after shuffling, so that it remains available.            */
5959
5960     gameInfo.holdingsWidth = 0; /* default board sizes */
5961     gameInfo.boardWidth    = 8;
5962     gameInfo.boardHeight   = 8;
5963     gameInfo.holdingsSize  = 0;
5964     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5965     for(i=0; i<BOARD_FILES-2; i++)
5966       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5967     initialPosition[EP_STATUS] = EP_NONE;
5968     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5969     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5970          SetCharTable(pieceNickName, appData.pieceNickNames);
5971     else SetCharTable(pieceNickName, "............");
5972     pieces = FIDEArray;
5973
5974     switch (gameInfo.variant) {
5975     case VariantFischeRandom:
5976       shuffleOpenings = TRUE;
5977     default:
5978       break;
5979     case VariantShatranj:
5980       pieces = ShatranjArray;
5981       nrCastlingRights = 0;
5982       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5983       break;
5984     case VariantMakruk:
5985       pieces = makrukArray;
5986       nrCastlingRights = 0;
5987       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5988       break;
5989     case VariantASEAN:
5990       pieces = aseanArray;
5991       nrCastlingRights = 0;
5992       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5993       break;
5994     case VariantTwoKings:
5995       pieces = twoKingsArray;
5996       break;
5997     case VariantGrand:
5998       pieces = GrandArray;
5999       nrCastlingRights = 0;
6000       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6001       gameInfo.boardWidth = 10;
6002       gameInfo.boardHeight = 10;
6003       gameInfo.holdingsSize = 7;
6004       break;
6005     case VariantCapaRandom:
6006       shuffleOpenings = TRUE;
6007     case VariantCapablanca:
6008       pieces = CapablancaArray;
6009       gameInfo.boardWidth = 10;
6010       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6011       break;
6012     case VariantGothic:
6013       pieces = GothicArray;
6014       gameInfo.boardWidth = 10;
6015       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6016       break;
6017     case VariantSChess:
6018       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6019       gameInfo.holdingsSize = 7;
6020       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6021       break;
6022     case VariantJanus:
6023       pieces = JanusArray;
6024       gameInfo.boardWidth = 10;
6025       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6026       nrCastlingRights = 6;
6027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6033       break;
6034     case VariantFalcon:
6035       pieces = FalconArray;
6036       gameInfo.boardWidth = 10;
6037       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6038       break;
6039     case VariantXiangqi:
6040       pieces = XiangqiArray;
6041       gameInfo.boardWidth  = 9;
6042       gameInfo.boardHeight = 10;
6043       nrCastlingRights = 0;
6044       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6045       break;
6046     case VariantShogi:
6047       pieces = ShogiArray;
6048       gameInfo.boardWidth  = 9;
6049       gameInfo.boardHeight = 9;
6050       gameInfo.holdingsSize = 7;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6053       break;
6054     case VariantChu:
6055       pieces = ChuArray; pieceRows = 3;
6056       gameInfo.boardWidth  = 12;
6057       gameInfo.boardHeight = 12;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6060                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6061       break;
6062     case VariantCourier:
6063       pieces = CourierArray;
6064       gameInfo.boardWidth  = 12;
6065       nrCastlingRights = 0;
6066       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6067       break;
6068     case VariantKnightmate:
6069       pieces = KnightmateArray;
6070       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6071       break;
6072     case VariantSpartan:
6073       pieces = SpartanArray;
6074       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6075       break;
6076     case VariantLion:
6077       pieces = lionArray;
6078       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6079       break;
6080     case VariantChuChess:
6081       pieces = ChuChessArray;
6082       gameInfo.boardWidth = 10;
6083       gameInfo.boardHeight = 10;
6084       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6085       break;
6086     case VariantFairy:
6087       pieces = fairyArray;
6088       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6089       break;
6090     case VariantGreat:
6091       pieces = GreatArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6094       gameInfo.holdingsSize = 8;
6095       break;
6096     case VariantSuper:
6097       pieces = FIDEArray;
6098       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6099       gameInfo.holdingsSize = 8;
6100       startedFromSetupPosition = TRUE;
6101       break;
6102     case VariantCrazyhouse:
6103     case VariantBughouse:
6104       pieces = FIDEArray;
6105       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6106       gameInfo.holdingsSize = 5;
6107       break;
6108     case VariantWildCastle:
6109       pieces = FIDEArray;
6110       /* !!?shuffle with kings guaranteed to be on d or e file */
6111       shuffleOpenings = 1;
6112       break;
6113     case VariantNoCastle:
6114       pieces = FIDEArray;
6115       nrCastlingRights = 0;
6116       /* !!?unconstrained back-rank shuffle */
6117       shuffleOpenings = 1;
6118       break;
6119     }
6120
6121     overrule = 0;
6122     if(appData.NrFiles >= 0) {
6123         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6124         gameInfo.boardWidth = appData.NrFiles;
6125     }
6126     if(appData.NrRanks >= 0) {
6127         gameInfo.boardHeight = appData.NrRanks;
6128     }
6129     if(appData.holdingsSize >= 0) {
6130         i = appData.holdingsSize;
6131         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6132         gameInfo.holdingsSize = i;
6133     }
6134     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6135     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6136         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6137
6138     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6139     if(pawnRow < 1) pawnRow = 1;
6140     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6141        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6142     if(gameInfo.variant == VariantChu) pawnRow = 3;
6143
6144     /* User pieceToChar list overrules defaults */
6145     if(appData.pieceToCharTable != NULL)
6146         SetCharTable(pieceToChar, appData.pieceToCharTable);
6147
6148     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6149
6150         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6151             s = (ChessSquare) 0; /* account holding counts in guard band */
6152         for( i=0; i<BOARD_HEIGHT; i++ )
6153             initialPosition[i][j] = s;
6154
6155         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6156         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6157         initialPosition[pawnRow][j] = WhitePawn;
6158         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6159         if(gameInfo.variant == VariantXiangqi) {
6160             if(j&1) {
6161                 initialPosition[pawnRow][j] =
6162                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6163                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6164                    initialPosition[2][j] = WhiteCannon;
6165                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6166                 }
6167             }
6168         }
6169         if(gameInfo.variant == VariantChu) {
6170              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6171                initialPosition[pawnRow+1][j] = WhiteCobra,
6172                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6173              for(i=1; i<pieceRows; i++) {
6174                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6175                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6176              }
6177         }
6178         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6179             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6180                initialPosition[0][j] = WhiteRook;
6181                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6182             }
6183         }
6184         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6185     }
6186     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6187     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6188
6189             j=BOARD_LEFT+1;
6190             initialPosition[1][j] = WhiteBishop;
6191             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6192             j=BOARD_RGHT-2;
6193             initialPosition[1][j] = WhiteRook;
6194             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6195     }
6196
6197     if( nrCastlingRights == -1) {
6198         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6199         /*       This sets default castling rights from none to normal corners   */
6200         /* Variants with other castling rights must set them themselves above    */
6201         nrCastlingRights = 6;
6202
6203         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6204         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6205         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6206         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6207         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6208         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6209      }
6210
6211      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6212      if(gameInfo.variant == VariantGreat) { // promotion commoners
6213         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6214         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6215         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6216         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6217      }
6218      if( gameInfo.variant == VariantSChess ) {
6219       initialPosition[1][0] = BlackMarshall;
6220       initialPosition[2][0] = BlackAngel;
6221       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6222       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6223       initialPosition[1][1] = initialPosition[2][1] =
6224       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6225      }
6226   if (appData.debugMode) {
6227     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6228   }
6229     if(shuffleOpenings) {
6230         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6231         startedFromSetupPosition = TRUE;
6232     }
6233     if(startedFromPositionFile) {
6234       /* [HGM] loadPos: use PositionFile for every new game */
6235       CopyBoard(initialPosition, filePosition);
6236       for(i=0; i<nrCastlingRights; i++)
6237           initialRights[i] = filePosition[CASTLING][i];
6238       startedFromSetupPosition = TRUE;
6239     }
6240
6241     CopyBoard(boards[0], initialPosition);
6242
6243     if(oldx != gameInfo.boardWidth ||
6244        oldy != gameInfo.boardHeight ||
6245        oldv != gameInfo.variant ||
6246        oldh != gameInfo.holdingsWidth
6247                                          )
6248             InitDrawingSizes(-2 ,0);
6249
6250     oldv = gameInfo.variant;
6251     if (redraw)
6252       DrawPosition(TRUE, boards[currentMove]);
6253 }
6254
6255 void
6256 SendBoard (ChessProgramState *cps, int moveNum)
6257 {
6258     char message[MSG_SIZ];
6259
6260     if (cps->useSetboard) {
6261       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6262       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6263       SendToProgram(message, cps);
6264       free(fen);
6265
6266     } else {
6267       ChessSquare *bp;
6268       int i, j, left=0, right=BOARD_WIDTH;
6269       /* Kludge to set black to move, avoiding the troublesome and now
6270        * deprecated "black" command.
6271        */
6272       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6273         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6274
6275       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6276
6277       SendToProgram("edit\n", cps);
6278       SendToProgram("#\n", cps);
6279       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6280         bp = &boards[moveNum][i][left];
6281         for (j = left; j < right; j++, bp++) {
6282           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6283           if ((int) *bp < (int) BlackPawn) {
6284             if(j == BOARD_RGHT+1)
6285                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6286             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6287             if(message[0] == '+' || message[0] == '~') {
6288               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6289                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6290                         AAA + j, ONE + i);
6291             }
6292             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6293                 message[1] = BOARD_RGHT   - 1 - j + '1';
6294                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6295             }
6296             SendToProgram(message, cps);
6297           }
6298         }
6299       }
6300
6301       SendToProgram("c\n", cps);
6302       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6303         bp = &boards[moveNum][i][left];
6304         for (j = left; j < right; j++, bp++) {
6305           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6306           if (((int) *bp != (int) EmptySquare)
6307               && ((int) *bp >= (int) BlackPawn)) {
6308             if(j == BOARD_LEFT-2)
6309                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6310             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6311                     AAA + j, ONE + i);
6312             if(message[0] == '+' || message[0] == '~') {
6313               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6314                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6315                         AAA + j, ONE + i);
6316             }
6317             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6318                 message[1] = BOARD_RGHT   - 1 - j + '1';
6319                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6320             }
6321             SendToProgram(message, cps);
6322           }
6323         }
6324       }
6325
6326       SendToProgram(".\n", cps);
6327     }
6328     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6329 }
6330
6331 char exclusionHeader[MSG_SIZ];
6332 int exCnt, excludePtr;
6333 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6334 static Exclusion excluTab[200];
6335 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6336
6337 static void
6338 WriteMap (int s)
6339 {
6340     int j;
6341     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6342     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6343 }
6344
6345 static void
6346 ClearMap ()
6347 {
6348     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6349     excludePtr = 24; exCnt = 0;
6350     WriteMap(0);
6351 }
6352
6353 static void
6354 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6355 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6356     char buf[2*MOVE_LEN], *p;
6357     Exclusion *e = excluTab;
6358     int i;
6359     for(i=0; i<exCnt; i++)
6360         if(e[i].ff == fromX && e[i].fr == fromY &&
6361            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6362     if(i == exCnt) { // was not in exclude list; add it
6363         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6364         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6365             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6366             return; // abort
6367         }
6368         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6369         excludePtr++; e[i].mark = excludePtr++;
6370         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6371         exCnt++;
6372     }
6373     exclusionHeader[e[i].mark] = state;
6374 }
6375
6376 static int
6377 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6378 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6379     char buf[MSG_SIZ];
6380     int j, k;
6381     ChessMove moveType;
6382     if((signed char)promoChar == -1) { // kludge to indicate best move
6383         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6384             return 1; // if unparsable, abort
6385     }
6386     // update exclusion map (resolving toggle by consulting existing state)
6387     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6388     j = k%8; k >>= 3;
6389     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6390     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6391          excludeMap[k] |=   1<<j;
6392     else excludeMap[k] &= ~(1<<j);
6393     // update header
6394     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6395     // inform engine
6396     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6397     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6398     SendToBoth(buf);
6399     return (state == '+');
6400 }
6401
6402 static void
6403 ExcludeClick (int index)
6404 {
6405     int i, j;
6406     Exclusion *e = excluTab;
6407     if(index < 25) { // none, best or tail clicked
6408         if(index < 13) { // none: include all
6409             WriteMap(0); // clear map
6410             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6411             SendToBoth("include all\n"); // and inform engine
6412         } else if(index > 18) { // tail
6413             if(exclusionHeader[19] == '-') { // tail was excluded
6414                 SendToBoth("include all\n");
6415                 WriteMap(0); // clear map completely
6416                 // now re-exclude selected moves
6417                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6418                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6419             } else { // tail was included or in mixed state
6420                 SendToBoth("exclude all\n");
6421                 WriteMap(0xFF); // fill map completely
6422                 // now re-include selected moves
6423                 j = 0; // count them
6424                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6425                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6426                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6427             }
6428         } else { // best
6429             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6430         }
6431     } else {
6432         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6433             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6434             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6435             break;
6436         }
6437     }
6438 }
6439
6440 ChessSquare
6441 DefaultPromoChoice (int white)
6442 {
6443     ChessSquare result;
6444     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6445        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6446         result = WhiteFerz; // no choice
6447     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6448         result= WhiteKing; // in Suicide Q is the last thing we want
6449     else if(gameInfo.variant == VariantSpartan)
6450         result = white ? WhiteQueen : WhiteAngel;
6451     else result = WhiteQueen;
6452     if(!white) result = WHITE_TO_BLACK result;
6453     return result;
6454 }
6455
6456 static int autoQueen; // [HGM] oneclick
6457
6458 int
6459 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6460 {
6461     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6462     /* [HGM] add Shogi promotions */
6463     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6464     ChessSquare piece, partner;
6465     ChessMove moveType;
6466     Boolean premove;
6467
6468     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6469     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6470
6471     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6472       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6473         return FALSE;
6474
6475     piece = boards[currentMove][fromY][fromX];
6476     if(gameInfo.variant == VariantChu) {
6477         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6478         promotionZoneSize = BOARD_HEIGHT/3;
6479         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6480     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6481         promotionZoneSize = BOARD_HEIGHT/3;
6482         highestPromotingPiece = (int)WhiteAlfil;
6483     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6484         promotionZoneSize = 3;
6485     }
6486
6487     // Treat Lance as Pawn when it is not representing Amazon
6488     if(gameInfo.variant != VariantSuper) {
6489         if(piece == WhiteLance) piece = WhitePawn; else
6490         if(piece == BlackLance) piece = BlackPawn;
6491     }
6492
6493     // next weed out all moves that do not touch the promotion zone at all
6494     if((int)piece >= BlackPawn) {
6495         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6496              return FALSE;
6497         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6498         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6499     } else {
6500         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6501            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6502         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6503              return FALSE;
6504     }
6505
6506     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6507
6508     // weed out mandatory Shogi promotions
6509     if(gameInfo.variant == VariantShogi) {
6510         if(piece >= BlackPawn) {
6511             if(toY == 0 && piece == BlackPawn ||
6512                toY == 0 && piece == BlackQueen ||
6513                toY <= 1 && piece == BlackKnight) {
6514                 *promoChoice = '+';
6515                 return FALSE;
6516             }
6517         } else {
6518             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6519                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6520                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6521                 *promoChoice = '+';
6522                 return FALSE;
6523             }
6524         }
6525     }
6526
6527     // weed out obviously illegal Pawn moves
6528     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6529         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6530         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6531         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6532         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6533         // note we are not allowed to test for valid (non-)capture, due to premove
6534     }
6535
6536     // we either have a choice what to promote to, or (in Shogi) whether to promote
6537     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6538        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6539         *promoChoice = PieceToChar(BlackFerz);  // no choice
6540         return FALSE;
6541     }
6542     // no sense asking what we must promote to if it is going to explode...
6543     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6544         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6545         return FALSE;
6546     }
6547     // give caller the default choice even if we will not make it
6548     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6549     partner = piece; // pieces can promote if the pieceToCharTable says so
6550     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6551     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6552     if(        sweepSelect && gameInfo.variant != VariantGreat
6553                            && gameInfo.variant != VariantGrand
6554                            && gameInfo.variant != VariantSuper) return FALSE;
6555     if(autoQueen) return FALSE; // predetermined
6556
6557     // suppress promotion popup on illegal moves that are not premoves
6558     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6559               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6560     if(appData.testLegality && !premove) {
6561         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6562                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6563         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6564         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6565             return FALSE;
6566     }
6567
6568     return TRUE;
6569 }
6570
6571 int
6572 InPalace (int row, int column)
6573 {   /* [HGM] for Xiangqi */
6574     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6575          column < (BOARD_WIDTH + 4)/2 &&
6576          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6577     return FALSE;
6578 }
6579
6580 int
6581 PieceForSquare (int x, int y)
6582 {
6583   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6584      return -1;
6585   else
6586      return boards[currentMove][y][x];
6587 }
6588
6589 int
6590 OKToStartUserMove (int x, int y)
6591 {
6592     ChessSquare from_piece;
6593     int white_piece;
6594
6595     if (matchMode) return FALSE;
6596     if (gameMode == EditPosition) return TRUE;
6597
6598     if (x >= 0 && y >= 0)
6599       from_piece = boards[currentMove][y][x];
6600     else
6601       from_piece = EmptySquare;
6602
6603     if (from_piece == EmptySquare) return FALSE;
6604
6605     white_piece = (int)from_piece >= (int)WhitePawn &&
6606       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6607
6608     switch (gameMode) {
6609       case AnalyzeFile:
6610       case TwoMachinesPlay:
6611       case EndOfGame:
6612         return FALSE;
6613
6614       case IcsObserving:
6615       case IcsIdle:
6616         return FALSE;
6617
6618       case MachinePlaysWhite:
6619       case IcsPlayingBlack:
6620         if (appData.zippyPlay) return FALSE;
6621         if (white_piece) {
6622             DisplayMoveError(_("You are playing Black"));
6623             return FALSE;
6624         }
6625         break;
6626
6627       case MachinePlaysBlack:
6628       case IcsPlayingWhite:
6629         if (appData.zippyPlay) return FALSE;
6630         if (!white_piece) {
6631             DisplayMoveError(_("You are playing White"));
6632             return FALSE;
6633         }
6634         break;
6635
6636       case PlayFromGameFile:
6637             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6638       case EditGame:
6639         if (!white_piece && WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return FALSE;
6642         }
6643         if (white_piece && !WhiteOnMove(currentMove)) {
6644             DisplayMoveError(_("It is Black's turn"));
6645             return FALSE;
6646         }
6647         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6648             /* Editing correspondence game history */
6649             /* Could disallow this or prompt for confirmation */
6650             cmailOldMove = -1;
6651         }
6652         break;
6653
6654       case BeginningOfGame:
6655         if (appData.icsActive) return FALSE;
6656         if (!appData.noChessProgram) {
6657             if (!white_piece) {
6658                 DisplayMoveError(_("You are playing White"));
6659                 return FALSE;
6660             }
6661         }
6662         break;
6663
6664       case Training:
6665         if (!white_piece && WhiteOnMove(currentMove)) {
6666             DisplayMoveError(_("It is White's turn"));
6667             return FALSE;
6668         }
6669         if (white_piece && !WhiteOnMove(currentMove)) {
6670             DisplayMoveError(_("It is Black's turn"));
6671             return FALSE;
6672         }
6673         break;
6674
6675       default:
6676       case IcsExamining:
6677         break;
6678     }
6679     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6680         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6681         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6682         && gameMode != AnalyzeFile && gameMode != Training) {
6683         DisplayMoveError(_("Displayed position is not current"));
6684         return FALSE;
6685     }
6686     return TRUE;
6687 }
6688
6689 Boolean
6690 OnlyMove (int *x, int *y, Boolean captures)
6691 {
6692     DisambiguateClosure cl;
6693     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6694     switch(gameMode) {
6695       case MachinePlaysBlack:
6696       case IcsPlayingWhite:
6697       case BeginningOfGame:
6698         if(!WhiteOnMove(currentMove)) return FALSE;
6699         break;
6700       case MachinePlaysWhite:
6701       case IcsPlayingBlack:
6702         if(WhiteOnMove(currentMove)) return FALSE;
6703         break;
6704       case EditGame:
6705         break;
6706       default:
6707         return FALSE;
6708     }
6709     cl.pieceIn = EmptySquare;
6710     cl.rfIn = *y;
6711     cl.ffIn = *x;
6712     cl.rtIn = -1;
6713     cl.ftIn = -1;
6714     cl.promoCharIn = NULLCHAR;
6715     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6716     if( cl.kind == NormalMove ||
6717         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6718         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6719         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6720       fromX = cl.ff;
6721       fromY = cl.rf;
6722       *x = cl.ft;
6723       *y = cl.rt;
6724       return TRUE;
6725     }
6726     if(cl.kind != ImpossibleMove) return FALSE;
6727     cl.pieceIn = EmptySquare;
6728     cl.rfIn = -1;
6729     cl.ffIn = -1;
6730     cl.rtIn = *y;
6731     cl.ftIn = *x;
6732     cl.promoCharIn = NULLCHAR;
6733     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6734     if( cl.kind == NormalMove ||
6735         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6736         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6737         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6738       fromX = cl.ff;
6739       fromY = cl.rf;
6740       *x = cl.ft;
6741       *y = cl.rt;
6742       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6743       return TRUE;
6744     }
6745     return FALSE;
6746 }
6747
6748 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6749 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6750 int lastLoadGameUseList = FALSE;
6751 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6752 ChessMove lastLoadGameStart = EndOfFile;
6753 int doubleClick;
6754
6755 void
6756 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6757 {
6758     ChessMove moveType;
6759     ChessSquare pup;
6760     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6761
6762     /* Check if the user is playing in turn.  This is complicated because we
6763        let the user "pick up" a piece before it is his turn.  So the piece he
6764        tried to pick up may have been captured by the time he puts it down!
6765        Therefore we use the color the user is supposed to be playing in this
6766        test, not the color of the piece that is currently on the starting
6767        square---except in EditGame mode, where the user is playing both
6768        sides; fortunately there the capture race can't happen.  (It can
6769        now happen in IcsExamining mode, but that's just too bad.  The user
6770        will get a somewhat confusing message in that case.)
6771        */
6772
6773     switch (gameMode) {
6774       case AnalyzeFile:
6775       case TwoMachinesPlay:
6776       case EndOfGame:
6777       case IcsObserving:
6778       case IcsIdle:
6779         /* We switched into a game mode where moves are not accepted,
6780            perhaps while the mouse button was down. */
6781         return;
6782
6783       case MachinePlaysWhite:
6784         /* User is moving for Black */
6785         if (WhiteOnMove(currentMove)) {
6786             DisplayMoveError(_("It is White's turn"));
6787             return;
6788         }
6789         break;
6790
6791       case MachinePlaysBlack:
6792         /* User is moving for White */
6793         if (!WhiteOnMove(currentMove)) {
6794             DisplayMoveError(_("It is Black's turn"));
6795             return;
6796         }
6797         break;
6798
6799       case PlayFromGameFile:
6800             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6801       case EditGame:
6802       case IcsExamining:
6803       case BeginningOfGame:
6804       case AnalyzeMode:
6805       case Training:
6806         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6807         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6808             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6809             /* User is moving for Black */
6810             if (WhiteOnMove(currentMove)) {
6811                 DisplayMoveError(_("It is White's turn"));
6812                 return;
6813             }
6814         } else {
6815             /* User is moving for White */
6816             if (!WhiteOnMove(currentMove)) {
6817                 DisplayMoveError(_("It is Black's turn"));
6818                 return;
6819             }
6820         }
6821         break;
6822
6823       case IcsPlayingBlack:
6824         /* User is moving for Black */
6825         if (WhiteOnMove(currentMove)) {
6826             if (!appData.premove) {
6827                 DisplayMoveError(_("It is White's turn"));
6828             } else if (toX >= 0 && toY >= 0) {
6829                 premoveToX = toX;
6830                 premoveToY = toY;
6831                 premoveFromX = fromX;
6832                 premoveFromY = fromY;
6833                 premovePromoChar = promoChar;
6834                 gotPremove = 1;
6835                 if (appData.debugMode)
6836                     fprintf(debugFP, "Got premove: fromX %d,"
6837                             "fromY %d, toX %d, toY %d\n",
6838                             fromX, fromY, toX, toY);
6839             }
6840             return;
6841         }
6842         break;
6843
6844       case IcsPlayingWhite:
6845         /* User is moving for White */
6846         if (!WhiteOnMove(currentMove)) {
6847             if (!appData.premove) {
6848                 DisplayMoveError(_("It is Black's turn"));
6849             } else if (toX >= 0 && toY >= 0) {
6850                 premoveToX = toX;
6851                 premoveToY = toY;
6852                 premoveFromX = fromX;
6853                 premoveFromY = fromY;
6854                 premovePromoChar = promoChar;
6855                 gotPremove = 1;
6856                 if (appData.debugMode)
6857                     fprintf(debugFP, "Got premove: fromX %d,"
6858                             "fromY %d, toX %d, toY %d\n",
6859                             fromX, fromY, toX, toY);
6860             }
6861             return;
6862         }
6863         break;
6864
6865       default:
6866         break;
6867
6868       case EditPosition:
6869         /* EditPosition, empty square, or different color piece;
6870            click-click move is possible */
6871         if (toX == -2 || toY == -2) {
6872             boards[0][fromY][fromX] = EmptySquare;
6873             DrawPosition(FALSE, boards[currentMove]);
6874             return;
6875         } else if (toX >= 0 && toY >= 0) {
6876             boards[0][toY][toX] = boards[0][fromY][fromX];
6877             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6878                 if(boards[0][fromY][0] != EmptySquare) {
6879                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6880                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6881                 }
6882             } else
6883             if(fromX == BOARD_RGHT+1) {
6884                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6885                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6886                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6887                 }
6888             } else
6889             boards[0][fromY][fromX] = gatingPiece;
6890             DrawPosition(FALSE, boards[currentMove]);
6891             return;
6892         }
6893         return;
6894     }
6895
6896     if(toX < 0 || toY < 0) return;
6897     pup = boards[currentMove][toY][toX];
6898
6899     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6900     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6901          if( pup != EmptySquare ) return;
6902          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6903            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6904                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6905            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6906            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6907            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6908            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6909          fromY = DROP_RANK;
6910     }
6911
6912     /* [HGM] always test for legality, to get promotion info */
6913     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6914                                          fromY, fromX, toY, toX, promoChar);
6915
6916     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6917
6918     /* [HGM] but possibly ignore an IllegalMove result */
6919     if (appData.testLegality) {
6920         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6921             DisplayMoveError(_("Illegal move"));
6922             return;
6923         }
6924     }
6925
6926     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6927         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6928              ClearPremoveHighlights(); // was included
6929         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6930         return;
6931     }
6932
6933     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6934 }
6935
6936 /* Common tail of UserMoveEvent and DropMenuEvent */
6937 int
6938 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6939 {
6940     char *bookHit = 0;
6941
6942     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6943         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6944         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6945         if(WhiteOnMove(currentMove)) {
6946             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6947         } else {
6948             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6949         }
6950     }
6951
6952     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6953        move type in caller when we know the move is a legal promotion */
6954     if(moveType == NormalMove && promoChar)
6955         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6956
6957     /* [HGM] <popupFix> The following if has been moved here from
6958        UserMoveEvent(). Because it seemed to belong here (why not allow
6959        piece drops in training games?), and because it can only be
6960        performed after it is known to what we promote. */
6961     if (gameMode == Training) {
6962       /* compare the move played on the board to the next move in the
6963        * game. If they match, display the move and the opponent's response.
6964        * If they don't match, display an error message.
6965        */
6966       int saveAnimate;
6967       Board testBoard;
6968       CopyBoard(testBoard, boards[currentMove]);
6969       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6970
6971       if (CompareBoards(testBoard, boards[currentMove+1])) {
6972         ForwardInner(currentMove+1);
6973
6974         /* Autoplay the opponent's response.
6975          * if appData.animate was TRUE when Training mode was entered,
6976          * the response will be animated.
6977          */
6978         saveAnimate = appData.animate;
6979         appData.animate = animateTraining;
6980         ForwardInner(currentMove+1);
6981         appData.animate = saveAnimate;
6982
6983         /* check for the end of the game */
6984         if (currentMove >= forwardMostMove) {
6985           gameMode = PlayFromGameFile;
6986           ModeHighlight();
6987           SetTrainingModeOff();
6988           DisplayInformation(_("End of game"));
6989         }
6990       } else {
6991         DisplayError(_("Incorrect move"), 0);
6992       }
6993       return 1;
6994     }
6995
6996   /* Ok, now we know that the move is good, so we can kill
6997      the previous line in Analysis Mode */
6998   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6999                                 && currentMove < forwardMostMove) {
7000     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7001     else forwardMostMove = currentMove;
7002   }
7003
7004   ClearMap();
7005
7006   /* If we need the chess program but it's dead, restart it */
7007   ResurrectChessProgram();
7008
7009   /* A user move restarts a paused game*/
7010   if (pausing)
7011     PauseEvent();
7012
7013   thinkOutput[0] = NULLCHAR;
7014
7015   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7016
7017   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7018     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7019     return 1;
7020   }
7021
7022   if (gameMode == BeginningOfGame) {
7023     if (appData.noChessProgram) {
7024       gameMode = EditGame;
7025       SetGameInfo();
7026     } else {
7027       char buf[MSG_SIZ];
7028       gameMode = MachinePlaysBlack;
7029       StartClocks();
7030       SetGameInfo();
7031       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7032       DisplayTitle(buf);
7033       if (first.sendName) {
7034         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7035         SendToProgram(buf, &first);
7036       }
7037       StartClocks();
7038     }
7039     ModeHighlight();
7040   }
7041
7042   /* Relay move to ICS or chess engine */
7043   if (appData.icsActive) {
7044     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7045         gameMode == IcsExamining) {
7046       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7047         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7048         SendToICS("draw ");
7049         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7050       }
7051       // also send plain move, in case ICS does not understand atomic claims
7052       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7053       ics_user_moved = 1;
7054     }
7055   } else {
7056     if (first.sendTime && (gameMode == BeginningOfGame ||
7057                            gameMode == MachinePlaysWhite ||
7058                            gameMode == MachinePlaysBlack)) {
7059       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7060     }
7061     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7062          // [HGM] book: if program might be playing, let it use book
7063         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7064         first.maybeThinking = TRUE;
7065     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7066         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7067         SendBoard(&first, currentMove+1);
7068         if(second.analyzing) {
7069             if(!second.useSetboard) SendToProgram("undo\n", &second);
7070             SendBoard(&second, currentMove+1);
7071         }
7072     } else {
7073         SendMoveToProgram(forwardMostMove-1, &first);
7074         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7075     }
7076     if (currentMove == cmailOldMove + 1) {
7077       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7078     }
7079   }
7080
7081   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7082
7083   switch (gameMode) {
7084   case EditGame:
7085     if(appData.testLegality)
7086     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7087     case MT_NONE:
7088     case MT_CHECK:
7089       break;
7090     case MT_CHECKMATE:
7091     case MT_STAINMATE:
7092       if (WhiteOnMove(currentMove)) {
7093         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7094       } else {
7095         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7096       }
7097       break;
7098     case MT_STALEMATE:
7099       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7100       break;
7101     }
7102     break;
7103
7104   case MachinePlaysBlack:
7105   case MachinePlaysWhite:
7106     /* disable certain menu options while machine is thinking */
7107     SetMachineThinkingEnables();
7108     break;
7109
7110   default:
7111     break;
7112   }
7113
7114   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7115   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7116
7117   if(bookHit) { // [HGM] book: simulate book reply
7118         static char bookMove[MSG_SIZ]; // a bit generous?
7119
7120         programStats.nodes = programStats.depth = programStats.time =
7121         programStats.score = programStats.got_only_move = 0;
7122         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7123
7124         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7125         strcat(bookMove, bookHit);
7126         HandleMachineMove(bookMove, &first);
7127   }
7128   return 1;
7129 }
7130
7131 void
7132 MarkByFEN(char *fen)
7133 {
7134         int r, f;
7135         if(!appData.markers || !appData.highlightDragging) return;
7136         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7137         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7138         while(*fen) {
7139             int s = 0;
7140             marker[r][f] = 0;
7141             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7142             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7143             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7144             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7145             if(*fen == 'T') marker[r][f++] = 0; else
7146             if(*fen == 'Y') marker[r][f++] = 1; else
7147             if(*fen == 'G') marker[r][f++] = 3; else
7148             if(*fen == 'B') marker[r][f++] = 4; else
7149             if(*fen == 'C') marker[r][f++] = 5; else
7150             if(*fen == 'M') marker[r][f++] = 6; else
7151             if(*fen == 'W') marker[r][f++] = 7; else
7152             if(*fen == 'D') marker[r][f++] = 8; else
7153             if(*fen == 'R') marker[r][f++] = 2; else {
7154                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7155               f += s; fen -= s>0;
7156             }
7157             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7158             if(r < 0) break;
7159             fen++;
7160         }
7161         DrawPosition(TRUE, NULL);
7162 }
7163
7164 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7165
7166 void
7167 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7168 {
7169     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7170     Markers *m = (Markers *) closure;
7171     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7172         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7173                          || kind == WhiteCapturesEnPassant
7174                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7175     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7176 }
7177
7178 static int hoverSavedValid;
7179
7180 void
7181 MarkTargetSquares (int clear)
7182 {
7183   int x, y, sum=0;
7184   if(clear) { // no reason to ever suppress clearing
7185     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7186     hoverSavedValid = 0;
7187     if(!sum) return; // nothing was cleared,no redraw needed
7188   } else {
7189     int capt = 0;
7190     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7191        !appData.testLegality || gameMode == EditPosition) return;
7192     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7193     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7194       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7195       if(capt)
7196       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7197     }
7198   }
7199   DrawPosition(FALSE, NULL);
7200 }
7201
7202 int
7203 Explode (Board board, int fromX, int fromY, int toX, int toY)
7204 {
7205     if(gameInfo.variant == VariantAtomic &&
7206        (board[toY][toX] != EmptySquare ||                     // capture?
7207         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7208                          board[fromY][fromX] == BlackPawn   )
7209       )) {
7210         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7211         return TRUE;
7212     }
7213     return FALSE;
7214 }
7215
7216 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7217
7218 int
7219 CanPromote (ChessSquare piece, int y)
7220 {
7221         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7222         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7223         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7224         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7225            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7226            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7227          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7228         return (piece == BlackPawn && y <= zone ||
7229                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7230                 piece == BlackLance && y == 1 ||
7231                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7232 }
7233
7234 void
7235 HoverEvent (int xPix, int yPix, int x, int y)
7236 {
7237         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7238         int r, f;
7239         if(!first.highlight) return;
7240         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7241         if(x == oldX && y == oldY) return; // only do something if we enter new square
7242         oldFromX = fromX; oldFromY = fromY;
7243         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7244           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7245             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7246           hoverSavedValid = 1;
7247         } else if(oldX != x || oldY != y) {
7248           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7249           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7250           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7251             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7252           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7253             char buf[MSG_SIZ];
7254             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7255             SendToProgram(buf, &first);
7256           }
7257           oldX = x; oldY = y;
7258 //        SetHighlights(fromX, fromY, x, y);
7259         }
7260 }
7261
7262 void ReportClick(char *action, int x, int y)
7263 {
7264         char buf[MSG_SIZ]; // Inform engine of what user does
7265         int r, f;
7266         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7267           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7268         if(!first.highlight || gameMode == EditPosition) return;
7269         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7270         SendToProgram(buf, &first);
7271 }
7272
7273 void
7274 LeftClick (ClickType clickType, int xPix, int yPix)
7275 {
7276     int x, y;
7277     Boolean saveAnimate;
7278     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7279     char promoChoice = NULLCHAR;
7280     ChessSquare piece;
7281     static TimeMark lastClickTime, prevClickTime;
7282
7283     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7284
7285     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7286
7287     if (clickType == Press) ErrorPopDown();
7288     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7289
7290     x = EventToSquare(xPix, BOARD_WIDTH);
7291     y = EventToSquare(yPix, BOARD_HEIGHT);
7292     if (!flipView && y >= 0) {
7293         y = BOARD_HEIGHT - 1 - y;
7294     }
7295     if (flipView && x >= 0) {
7296         x = BOARD_WIDTH - 1 - x;
7297     }
7298
7299     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7300         defaultPromoChoice = promoSweep;
7301         promoSweep = EmptySquare;   // terminate sweep
7302         promoDefaultAltered = TRUE;
7303         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7304     }
7305
7306     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7307         if(clickType == Release) return; // ignore upclick of click-click destination
7308         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7309         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7310         if(gameInfo.holdingsWidth &&
7311                 (WhiteOnMove(currentMove)
7312                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7313                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7314             // click in right holdings, for determining promotion piece
7315             ChessSquare p = boards[currentMove][y][x];
7316             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7317             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7318             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7319                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7320                 fromX = fromY = -1;
7321                 return;
7322             }
7323         }
7324         DrawPosition(FALSE, boards[currentMove]);
7325         return;
7326     }
7327
7328     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7329     if(clickType == Press
7330             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7331               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7332               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7333         return;
7334
7335     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7336         // could be static click on premove from-square: abort premove
7337         gotPremove = 0;
7338         ClearPremoveHighlights();
7339     }
7340
7341     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7342         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7343
7344     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7345         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7346                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7347         defaultPromoChoice = DefaultPromoChoice(side);
7348     }
7349
7350     autoQueen = appData.alwaysPromoteToQueen;
7351
7352     if (fromX == -1) {
7353       int originalY = y;
7354       gatingPiece = EmptySquare;
7355       if (clickType != Press) {
7356         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7357             DragPieceEnd(xPix, yPix); dragging = 0;
7358             DrawPosition(FALSE, NULL);
7359         }
7360         return;
7361       }
7362       doubleClick = FALSE;
7363       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7364         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7365       }
7366       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7367       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7368          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7369          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7370             /* First square */
7371             if (OKToStartUserMove(fromX, fromY)) {
7372                 second = 0;
7373                 ReportClick("lift", x, y);
7374                 MarkTargetSquares(0);
7375                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7376                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7377                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7378                     promoSweep = defaultPromoChoice;
7379                     selectFlag = 0; lastX = xPix; lastY = yPix;
7380                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7381                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7382                 }
7383                 if (appData.highlightDragging) {
7384                     SetHighlights(fromX, fromY, -1, -1);
7385                 } else {
7386                     ClearHighlights();
7387                 }
7388             } else fromX = fromY = -1;
7389             return;
7390         }
7391     }
7392
7393     /* fromX != -1 */
7394     if (clickType == Press && gameMode != EditPosition) {
7395         ChessSquare fromP;
7396         ChessSquare toP;
7397         int frc;
7398
7399         // ignore off-board to clicks
7400         if(y < 0 || x < 0) return;
7401
7402         /* Check if clicking again on the same color piece */
7403         fromP = boards[currentMove][fromY][fromX];
7404         toP = boards[currentMove][y][x];
7405         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7406         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7407            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7408              WhitePawn <= toP && toP <= WhiteKing &&
7409              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7410              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7411             (BlackPawn <= fromP && fromP <= BlackKing &&
7412              BlackPawn <= toP && toP <= BlackKing &&
7413              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7414              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7415             /* Clicked again on same color piece -- changed his mind */
7416             second = (x == fromX && y == fromY);
7417             killX = killY = -1;
7418             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7419                 second = FALSE; // first double-click rather than scond click
7420                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7421             }
7422             promoDefaultAltered = FALSE;
7423             MarkTargetSquares(1);
7424            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7425             if (appData.highlightDragging) {
7426                 SetHighlights(x, y, -1, -1);
7427             } else {
7428                 ClearHighlights();
7429             }
7430             if (OKToStartUserMove(x, y)) {
7431                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7432                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7433                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7434                  gatingPiece = boards[currentMove][fromY][fromX];
7435                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7436                 fromX = x;
7437                 fromY = y; dragging = 1;
7438                 ReportClick("lift", x, y);
7439                 MarkTargetSquares(0);
7440                 DragPieceBegin(xPix, yPix, FALSE);
7441                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7442                     promoSweep = defaultPromoChoice;
7443                     selectFlag = 0; lastX = xPix; lastY = yPix;
7444                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7445                 }
7446             }
7447            }
7448            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7449            second = FALSE;
7450         }
7451         // ignore clicks on holdings
7452         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7453     }
7454
7455     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7456         DragPieceEnd(xPix, yPix); dragging = 0;
7457         if(clearFlag) {
7458             // a deferred attempt to click-click move an empty square on top of a piece
7459             boards[currentMove][y][x] = EmptySquare;
7460             ClearHighlights();
7461             DrawPosition(FALSE, boards[currentMove]);
7462             fromX = fromY = -1; clearFlag = 0;
7463             return;
7464         }
7465         if (appData.animateDragging) {
7466             /* Undo animation damage if any */
7467             DrawPosition(FALSE, NULL);
7468         }
7469         if (second || sweepSelecting) {
7470             /* Second up/down in same square; just abort move */
7471             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7472             second = sweepSelecting = 0;
7473             fromX = fromY = -1;
7474             gatingPiece = EmptySquare;
7475             MarkTargetSquares(1);
7476             ClearHighlights();
7477             gotPremove = 0;
7478             ClearPremoveHighlights();
7479         } else {
7480             /* First upclick in same square; start click-click mode */
7481             SetHighlights(x, y, -1, -1);
7482         }
7483         return;
7484     }
7485
7486     clearFlag = 0;
7487
7488     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7489         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7490         DisplayMessage(_("only marked squares are legal"),"");
7491         DrawPosition(TRUE, NULL);
7492         return; // ignore to-click
7493     }
7494
7495     /* we now have a different from- and (possibly off-board) to-square */
7496     /* Completed move */
7497     if(!sweepSelecting) {
7498         toX = x;
7499         toY = y;
7500     }
7501
7502     piece = boards[currentMove][fromY][fromX];
7503
7504     saveAnimate = appData.animate;
7505     if (clickType == Press) {
7506         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7507         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7508             // must be Edit Position mode with empty-square selected
7509             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7510             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7511             return;
7512         }
7513         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7514             return;
7515         }
7516         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7517             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7518         } else
7519         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7520         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7521           if(appData.sweepSelect) {
7522             promoSweep = defaultPromoChoice;
7523             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7524             selectFlag = 0; lastX = xPix; lastY = yPix;
7525             Sweep(0); // Pawn that is going to promote: preview promotion piece
7526             sweepSelecting = 1;
7527             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7528             MarkTargetSquares(1);
7529           }
7530           return; // promo popup appears on up-click
7531         }
7532         /* Finish clickclick move */
7533         if (appData.animate || appData.highlightLastMove) {
7534             SetHighlights(fromX, fromY, toX, toY);
7535         } else {
7536             ClearHighlights();
7537         }
7538     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7539         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7540         if (appData.animate || appData.highlightLastMove) {
7541             SetHighlights(fromX, fromY, toX, toY);
7542         } else {
7543             ClearHighlights();
7544         }
7545     } else {
7546 #if 0
7547 // [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
7548         /* Finish drag move */
7549         if (appData.highlightLastMove) {
7550             SetHighlights(fromX, fromY, toX, toY);
7551         } else {
7552             ClearHighlights();
7553         }
7554 #endif
7555         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7556         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7557           dragging *= 2;            // flag button-less dragging if we are dragging
7558           MarkTargetSquares(1);
7559           if(x == killX && y == killY) killX = killY = -1; else {
7560             killX = x; killY = y;     //remeber this square as intermediate
7561             ReportClick("put", x, y); // and inform engine
7562             ReportClick("lift", x, y);
7563             MarkTargetSquares(0);
7564             return;
7565           }
7566         }
7567         DragPieceEnd(xPix, yPix); dragging = 0;
7568         /* Don't animate move and drag both */
7569         appData.animate = FALSE;
7570     }
7571
7572     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7573     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7574         ChessSquare piece = boards[currentMove][fromY][fromX];
7575         if(gameMode == EditPosition && piece != EmptySquare &&
7576            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7577             int n;
7578
7579             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7580                 n = PieceToNumber(piece - (int)BlackPawn);
7581                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7582                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7583                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7584             } else
7585             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7586                 n = PieceToNumber(piece);
7587                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7588                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7589                 boards[currentMove][n][BOARD_WIDTH-2]++;
7590             }
7591             boards[currentMove][fromY][fromX] = EmptySquare;
7592         }
7593         ClearHighlights();
7594         fromX = fromY = -1;
7595         MarkTargetSquares(1);
7596         DrawPosition(TRUE, boards[currentMove]);
7597         return;
7598     }
7599
7600     // off-board moves should not be highlighted
7601     if(x < 0 || y < 0) ClearHighlights();
7602     else ReportClick("put", x, y);
7603
7604     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7605
7606     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7607         SetHighlights(fromX, fromY, toX, toY);
7608         MarkTargetSquares(1);
7609         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7610             // [HGM] super: promotion to captured piece selected from holdings
7611             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7612             promotionChoice = TRUE;
7613             // kludge follows to temporarily execute move on display, without promoting yet
7614             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7615             boards[currentMove][toY][toX] = p;
7616             DrawPosition(FALSE, boards[currentMove]);
7617             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7618             boards[currentMove][toY][toX] = q;
7619             DisplayMessage("Click in holdings to choose piece", "");
7620             return;
7621         }
7622         PromotionPopUp(promoChoice);
7623     } else {
7624         int oldMove = currentMove;
7625         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7626         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7627         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7628         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7629            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7630             DrawPosition(TRUE, boards[currentMove]);
7631         MarkTargetSquares(1);
7632         fromX = fromY = -1;
7633     }
7634     appData.animate = saveAnimate;
7635     if (appData.animate || appData.animateDragging) {
7636         /* Undo animation damage if needed */
7637         DrawPosition(FALSE, NULL);
7638     }
7639 }
7640
7641 int
7642 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7643 {   // front-end-free part taken out of PieceMenuPopup
7644     int whichMenu; int xSqr, ySqr;
7645
7646     if(seekGraphUp) { // [HGM] seekgraph
7647         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7648         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7649         return -2;
7650     }
7651
7652     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7653          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7654         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7655         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7656         if(action == Press)   {
7657             originalFlip = flipView;
7658             flipView = !flipView; // temporarily flip board to see game from partners perspective
7659             DrawPosition(TRUE, partnerBoard);
7660             DisplayMessage(partnerStatus, "");
7661             partnerUp = TRUE;
7662         } else if(action == Release) {
7663             flipView = originalFlip;
7664             DrawPosition(TRUE, boards[currentMove]);
7665             partnerUp = FALSE;
7666         }
7667         return -2;
7668     }
7669
7670     xSqr = EventToSquare(x, BOARD_WIDTH);
7671     ySqr = EventToSquare(y, BOARD_HEIGHT);
7672     if (action == Release) {
7673         if(pieceSweep != EmptySquare) {
7674             EditPositionMenuEvent(pieceSweep, toX, toY);
7675             pieceSweep = EmptySquare;
7676         } else UnLoadPV(); // [HGM] pv
7677     }
7678     if (action != Press) return -2; // return code to be ignored
7679     switch (gameMode) {
7680       case IcsExamining:
7681         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7682       case EditPosition:
7683         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7684         if (xSqr < 0 || ySqr < 0) return -1;
7685         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7686         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7687         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7688         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7689         NextPiece(0);
7690         return 2; // grab
7691       case IcsObserving:
7692         if(!appData.icsEngineAnalyze) return -1;
7693       case IcsPlayingWhite:
7694       case IcsPlayingBlack:
7695         if(!appData.zippyPlay) goto noZip;
7696       case AnalyzeMode:
7697       case AnalyzeFile:
7698       case MachinePlaysWhite:
7699       case MachinePlaysBlack:
7700       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7701         if (!appData.dropMenu) {
7702           LoadPV(x, y);
7703           return 2; // flag front-end to grab mouse events
7704         }
7705         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7706            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7707       case EditGame:
7708       noZip:
7709         if (xSqr < 0 || ySqr < 0) return -1;
7710         if (!appData.dropMenu || appData.testLegality &&
7711             gameInfo.variant != VariantBughouse &&
7712             gameInfo.variant != VariantCrazyhouse) return -1;
7713         whichMenu = 1; // drop menu
7714         break;
7715       default:
7716         return -1;
7717     }
7718
7719     if (((*fromX = xSqr) < 0) ||
7720         ((*fromY = ySqr) < 0)) {
7721         *fromX = *fromY = -1;
7722         return -1;
7723     }
7724     if (flipView)
7725       *fromX = BOARD_WIDTH - 1 - *fromX;
7726     else
7727       *fromY = BOARD_HEIGHT - 1 - *fromY;
7728
7729     return whichMenu;
7730 }
7731
7732 void
7733 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7734 {
7735 //    char * hint = lastHint;
7736     FrontEndProgramStats stats;
7737
7738     stats.which = cps == &first ? 0 : 1;
7739     stats.depth = cpstats->depth;
7740     stats.nodes = cpstats->nodes;
7741     stats.score = cpstats->score;
7742     stats.time = cpstats->time;
7743     stats.pv = cpstats->movelist;
7744     stats.hint = lastHint;
7745     stats.an_move_index = 0;
7746     stats.an_move_count = 0;
7747
7748     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7749         stats.hint = cpstats->move_name;
7750         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7751         stats.an_move_count = cpstats->nr_moves;
7752     }
7753
7754     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
7755
7756     SetProgramStats( &stats );
7757 }
7758
7759 void
7760 ClearEngineOutputPane (int which)
7761 {
7762     static FrontEndProgramStats dummyStats;
7763     dummyStats.which = which;
7764     dummyStats.pv = "#";
7765     SetProgramStats( &dummyStats );
7766 }
7767
7768 #define MAXPLAYERS 500
7769
7770 char *
7771 TourneyStandings (int display)
7772 {
7773     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7774     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7775     char result, *p, *names[MAXPLAYERS];
7776
7777     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7778         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7779     names[0] = p = strdup(appData.participants);
7780     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7781
7782     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7783
7784     while(result = appData.results[nr]) {
7785         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7786         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7787         wScore = bScore = 0;
7788         switch(result) {
7789           case '+': wScore = 2; break;
7790           case '-': bScore = 2; break;
7791           case '=': wScore = bScore = 1; break;
7792           case ' ':
7793           case '*': return strdup("busy"); // tourney not finished
7794         }
7795         score[w] += wScore;
7796         score[b] += bScore;
7797         games[w]++;
7798         games[b]++;
7799         nr++;
7800     }
7801     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7802     for(w=0; w<nPlayers; w++) {
7803         bScore = -1;
7804         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7805         ranking[w] = b; points[w] = bScore; score[b] = -2;
7806     }
7807     p = malloc(nPlayers*34+1);
7808     for(w=0; w<nPlayers && w<display; w++)
7809         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7810     free(names[0]);
7811     return p;
7812 }
7813
7814 void
7815 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7816 {       // count all piece types
7817         int p, f, r;
7818         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7819         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7820         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7821                 p = board[r][f];
7822                 pCnt[p]++;
7823                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7824                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7825                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7826                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7827                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7828                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7829         }
7830 }
7831
7832 int
7833 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7834 {
7835         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7836         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7837
7838         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7839         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7840         if(myPawns == 2 && nMine == 3) // KPP
7841             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7842         if(myPawns == 1 && nMine == 2) // KP
7843             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7844         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7845             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7846         if(myPawns) return FALSE;
7847         if(pCnt[WhiteRook+side])
7848             return pCnt[BlackRook-side] ||
7849                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7850                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7851                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7852         if(pCnt[WhiteCannon+side]) {
7853             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7854             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7855         }
7856         if(pCnt[WhiteKnight+side])
7857             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7858         return FALSE;
7859 }
7860
7861 int
7862 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7863 {
7864         VariantClass v = gameInfo.variant;
7865
7866         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7867         if(v == VariantShatranj) return TRUE; // always winnable through baring
7868         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7869         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7870
7871         if(v == VariantXiangqi) {
7872                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7873
7874                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7875                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7876                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7877                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7878                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7879                 if(stale) // we have at least one last-rank P plus perhaps C
7880                     return majors // KPKX
7881                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7882                 else // KCA*E*
7883                     return pCnt[WhiteFerz+side] // KCAK
7884                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7885                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7886                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7887
7888         } else if(v == VariantKnightmate) {
7889                 if(nMine == 1) return FALSE;
7890                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7891         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7892                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7893
7894                 if(nMine == 1) return FALSE; // bare King
7895                 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
7896                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7897                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7898                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7899                 if(pCnt[WhiteKnight+side])
7900                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7901                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7902                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7903                 if(nBishops)
7904                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7905                 if(pCnt[WhiteAlfil+side])
7906                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7907                 if(pCnt[WhiteWazir+side])
7908                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7909         }
7910
7911         return TRUE;
7912 }
7913
7914 int
7915 CompareWithRights (Board b1, Board b2)
7916 {
7917     int rights = 0;
7918     if(!CompareBoards(b1, b2)) return FALSE;
7919     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7920     /* compare castling rights */
7921     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7922            rights++; /* King lost rights, while rook still had them */
7923     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7924         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7925            rights++; /* but at least one rook lost them */
7926     }
7927     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7928            rights++;
7929     if( b1[CASTLING][5] != NoRights ) {
7930         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7931            rights++;
7932     }
7933     return rights == 0;
7934 }
7935
7936 int
7937 Adjudicate (ChessProgramState *cps)
7938 {       // [HGM] some adjudications useful with buggy engines
7939         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7940         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7941         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7942         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7943         int k, drop, count = 0; static int bare = 1;
7944         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7945         Boolean canAdjudicate = !appData.icsActive;
7946
7947         // most tests only when we understand the game, i.e. legality-checking on
7948             if( appData.testLegality )
7949             {   /* [HGM] Some more adjudications for obstinate engines */
7950                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7951                 static int moveCount = 6;
7952                 ChessMove result;
7953                 char *reason = NULL;
7954
7955                 /* Count what is on board. */
7956                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7957
7958                 /* Some material-based adjudications that have to be made before stalemate test */
7959                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7960                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7961                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7962                      if(canAdjudicate && appData.checkMates) {
7963                          if(engineOpponent)
7964                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7965                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7966                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7967                          return 1;
7968                      }
7969                 }
7970
7971                 /* Bare King in Shatranj (loses) or Losers (wins) */
7972                 if( nrW == 1 || nrB == 1) {
7973                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7974                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7975                      if(canAdjudicate && appData.checkMates) {
7976                          if(engineOpponent)
7977                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7978                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7979                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7980                          return 1;
7981                      }
7982                   } else
7983                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7984                   {    /* bare King */
7985                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7986                         if(canAdjudicate && appData.checkMates) {
7987                             /* but only adjudicate if adjudication enabled */
7988                             if(engineOpponent)
7989                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7990                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7991                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7992                             return 1;
7993                         }
7994                   }
7995                 } else bare = 1;
7996
7997
7998             // don't wait for engine to announce game end if we can judge ourselves
7999             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8000               case MT_CHECK:
8001                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8002                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8003                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8004                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8005                             checkCnt++;
8006                         if(checkCnt >= 2) {
8007                             reason = "Xboard adjudication: 3rd check";
8008                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8009                             break;
8010                         }
8011                     }
8012                 }
8013               case MT_NONE:
8014               default:
8015                 break;
8016               case MT_STALEMATE:
8017               case MT_STAINMATE:
8018                 reason = "Xboard adjudication: Stalemate";
8019                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8020                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8021                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8022                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8023                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8024                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8025                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8026                                                                         EP_CHECKMATE : EP_WINS);
8027                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8028                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8029                 }
8030                 break;
8031               case MT_CHECKMATE:
8032                 reason = "Xboard adjudication: Checkmate";
8033                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8034                 if(gameInfo.variant == VariantShogi) {
8035                     if(forwardMostMove > backwardMostMove
8036                        && moveList[forwardMostMove-1][1] == '@'
8037                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8038                         reason = "XBoard adjudication: pawn-drop mate";
8039                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8040                     }
8041                 }
8042                 break;
8043             }
8044
8045                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8046                     case EP_STALEMATE:
8047                         result = GameIsDrawn; break;
8048                     case EP_CHECKMATE:
8049                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8050                     case EP_WINS:
8051                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8052                     default:
8053                         result = EndOfFile;
8054                 }
8055                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8056                     if(engineOpponent)
8057                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8058                     GameEnds( result, reason, GE_XBOARD );
8059                     return 1;
8060                 }
8061
8062                 /* Next absolutely insufficient mating material. */
8063                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8064                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8065                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8066
8067                      /* always flag draws, for judging claims */
8068                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8069
8070                      if(canAdjudicate && appData.materialDraws) {
8071                          /* but only adjudicate them if adjudication enabled */
8072                          if(engineOpponent) {
8073                            SendToProgram("force\n", engineOpponent); // suppress reply
8074                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8075                          }
8076                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8077                          return 1;
8078                      }
8079                 }
8080
8081                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8082                 if(gameInfo.variant == VariantXiangqi ?
8083                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8084                  : nrW + nrB == 4 &&
8085                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8086                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8087                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8088                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8089                    ) ) {
8090                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8091                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8092                           if(engineOpponent) {
8093                             SendToProgram("force\n", engineOpponent); // suppress reply
8094                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8095                           }
8096                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8097                           return 1;
8098                      }
8099                 } else moveCount = 6;
8100             }
8101
8102         // Repetition draws and 50-move rule can be applied independently of legality testing
8103
8104                 /* Check for rep-draws */
8105                 count = 0;
8106                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8107                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8108                 for(k = forwardMostMove-2;
8109                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8110                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8111                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8112                     k-=2)
8113                 {   int rights=0;
8114                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8115                         /* compare castling rights */
8116                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8117                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8118                                 rights++; /* King lost rights, while rook still had them */
8119                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8120                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8121                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8122                                    rights++; /* but at least one rook lost them */
8123                         }
8124                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8125                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8126                                 rights++;
8127                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8128                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8129                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8130                                    rights++;
8131                         }
8132                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8133                             && appData.drawRepeats > 1) {
8134                              /* adjudicate after user-specified nr of repeats */
8135                              int result = GameIsDrawn;
8136                              char *details = "XBoard adjudication: repetition draw";
8137                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8138                                 // [HGM] xiangqi: check for forbidden perpetuals
8139                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8140                                 for(m=forwardMostMove; m>k; m-=2) {
8141                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8142                                         ourPerpetual = 0; // the current mover did not always check
8143                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8144                                         hisPerpetual = 0; // the opponent did not always check
8145                                 }
8146                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8147                                                                         ourPerpetual, hisPerpetual);
8148                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8149                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8150                                     details = "Xboard adjudication: perpetual checking";
8151                                 } else
8152                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8153                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8154                                 } else
8155                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8156                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8157                                         result = BlackWins;
8158                                         details = "Xboard adjudication: repetition";
8159                                     }
8160                                 } else // it must be XQ
8161                                 // Now check for perpetual chases
8162                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8163                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8164                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8165                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8166                                         static char resdet[MSG_SIZ];
8167                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8168                                         details = resdet;
8169                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8170                                     } else
8171                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8172                                         break; // Abort repetition-checking loop.
8173                                 }
8174                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8175                              }
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( result, details, GE_XBOARD );
8181                              return 1;
8182                         }
8183                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8184                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8185                     }
8186                 }
8187
8188                 /* Now we test for 50-move draws. Determine ply count */
8189                 count = forwardMostMove;
8190                 /* look for last irreversble move */
8191                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8192                     count--;
8193                 /* if we hit starting position, add initial plies */
8194                 if( count == backwardMostMove )
8195                     count -= initialRulePlies;
8196                 count = forwardMostMove - count;
8197                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8198                         // adjust reversible move counter for checks in Xiangqi
8199                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8200                         if(i < backwardMostMove) i = backwardMostMove;
8201                         while(i <= forwardMostMove) {
8202                                 lastCheck = inCheck; // check evasion does not count
8203                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8204                                 if(inCheck || lastCheck) count--; // check does not count
8205                                 i++;
8206                         }
8207                 }
8208                 if( count >= 100)
8209                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8210                          /* this is used to judge if draw claims are legal */
8211                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8212                          if(engineOpponent) {
8213                            SendToProgram("force\n", engineOpponent); // suppress reply
8214                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8215                          }
8216                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8217                          return 1;
8218                 }
8219
8220                 /* if draw offer is pending, treat it as a draw claim
8221                  * when draw condition present, to allow engines a way to
8222                  * claim draws before making their move to avoid a race
8223                  * condition occurring after their move
8224                  */
8225                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8226                          char *p = NULL;
8227                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8228                              p = "Draw claim: 50-move rule";
8229                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8230                              p = "Draw claim: 3-fold repetition";
8231                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8232                              p = "Draw claim: insufficient mating material";
8233                          if( p != NULL && canAdjudicate) {
8234                              if(engineOpponent) {
8235                                SendToProgram("force\n", engineOpponent); // suppress reply
8236                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8237                              }
8238                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8239                              return 1;
8240                          }
8241                 }
8242
8243                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8244                     if(engineOpponent) {
8245                       SendToProgram("force\n", engineOpponent); // suppress reply
8246                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8247                     }
8248                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8249                     return 1;
8250                 }
8251         return 0;
8252 }
8253
8254 char *
8255 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8256 {   // [HGM] book: this routine intercepts moves to simulate book replies
8257     char *bookHit = NULL;
8258
8259     //first determine if the incoming move brings opponent into his book
8260     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8261         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8262     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8263     if(bookHit != NULL && !cps->bookSuspend) {
8264         // make sure opponent is not going to reply after receiving move to book position
8265         SendToProgram("force\n", cps);
8266         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8267     }
8268     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8269     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8270     // now arrange restart after book miss
8271     if(bookHit) {
8272         // after a book hit we never send 'go', and the code after the call to this routine
8273         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8274         char buf[MSG_SIZ], *move = bookHit;
8275         if(cps->useSAN) {
8276             int fromX, fromY, toX, toY;
8277             char promoChar;
8278             ChessMove moveType;
8279             move = buf + 30;
8280             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8281                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8282                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8283                                     PosFlags(forwardMostMove),
8284                                     fromY, fromX, toY, toX, promoChar, move);
8285             } else {
8286                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8287                 bookHit = NULL;
8288             }
8289         }
8290         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8291         SendToProgram(buf, cps);
8292         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8293     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8294         SendToProgram("go\n", cps);
8295         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8296     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8297         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8298             SendToProgram("go\n", cps);
8299         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8300     }
8301     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8302 }
8303
8304 int
8305 LoadError (char *errmess, ChessProgramState *cps)
8306 {   // unloads engine and switches back to -ncp mode if it was first
8307     if(cps->initDone) return FALSE;
8308     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8309     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8310     cps->pr = NoProc;
8311     if(cps == &first) {
8312         appData.noChessProgram = TRUE;
8313         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8314         gameMode = BeginningOfGame; ModeHighlight();
8315         SetNCPMode();
8316     }
8317     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8318     DisplayMessage("", ""); // erase waiting message
8319     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8320     return TRUE;
8321 }
8322
8323 char *savedMessage;
8324 ChessProgramState *savedState;
8325 void
8326 DeferredBookMove (void)
8327 {
8328         if(savedState->lastPing != savedState->lastPong)
8329                     ScheduleDelayedEvent(DeferredBookMove, 10);
8330         else
8331         HandleMachineMove(savedMessage, savedState);
8332 }
8333
8334 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8335 static ChessProgramState *stalledEngine;
8336 static char stashedInputMove[MSG_SIZ];
8337
8338 void
8339 HandleMachineMove (char *message, ChessProgramState *cps)
8340 {
8341     static char firstLeg[20];
8342     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8343     char realname[MSG_SIZ];
8344     int fromX, fromY, toX, toY;
8345     ChessMove moveType;
8346     char promoChar, roar;
8347     char *p, *pv=buf1;
8348     int machineWhite, oldError;
8349     char *bookHit;
8350
8351     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8352         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8353         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8354             DisplayError(_("Invalid pairing from pairing engine"), 0);
8355             return;
8356         }
8357         pairingReceived = 1;
8358         NextMatchGame();
8359         return; // Skim the pairing messages here.
8360     }
8361
8362     oldError = cps->userError; cps->userError = 0;
8363
8364 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8365     /*
8366      * Kludge to ignore BEL characters
8367      */
8368     while (*message == '\007') message++;
8369
8370     /*
8371      * [HGM] engine debug message: ignore lines starting with '#' character
8372      */
8373     if(cps->debug && *message == '#') return;
8374
8375     /*
8376      * Look for book output
8377      */
8378     if (cps == &first && bookRequested) {
8379         if (message[0] == '\t' || message[0] == ' ') {
8380             /* Part of the book output is here; append it */
8381             strcat(bookOutput, message);
8382             strcat(bookOutput, "  \n");
8383             return;
8384         } else if (bookOutput[0] != NULLCHAR) {
8385             /* All of book output has arrived; display it */
8386             char *p = bookOutput;
8387             while (*p != NULLCHAR) {
8388                 if (*p == '\t') *p = ' ';
8389                 p++;
8390             }
8391             DisplayInformation(bookOutput);
8392             bookRequested = FALSE;
8393             /* Fall through to parse the current output */
8394         }
8395     }
8396
8397     /*
8398      * Look for machine move.
8399      */
8400     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8401         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8402     {
8403         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8404             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8405             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8406             stalledEngine = cps;
8407             if(appData.ponderNextMove) { // bring opponent out of ponder
8408                 if(gameMode == TwoMachinesPlay) {
8409                     if(cps->other->pause)
8410                         PauseEngine(cps->other);
8411                     else
8412                         SendToProgram("easy\n", cps->other);
8413                 }
8414             }
8415             StopClocks();
8416             return;
8417         }
8418
8419         /* This method is only useful on engines that support ping */
8420         if (cps->lastPing != cps->lastPong) {
8421           if (gameMode == BeginningOfGame) {
8422             /* Extra move from before last new; ignore */
8423             if (appData.debugMode) {
8424                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8425             }
8426           } else {
8427             if (appData.debugMode) {
8428                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8429                         cps->which, gameMode);
8430             }
8431
8432             SendToProgram("undo\n", cps);
8433           }
8434           return;
8435         }
8436
8437         switch (gameMode) {
8438           case BeginningOfGame:
8439             /* Extra move from before last reset; ignore */
8440             if (appData.debugMode) {
8441                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8442             }
8443             return;
8444
8445           case EndOfGame:
8446           case IcsIdle:
8447           default:
8448             /* Extra move after we tried to stop.  The mode test is
8449                not a reliable way of detecting this problem, but it's
8450                the best we can do on engines that don't support ping.
8451             */
8452             if (appData.debugMode) {
8453                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8454                         cps->which, gameMode);
8455             }
8456             SendToProgram("undo\n", cps);
8457             return;
8458
8459           case MachinePlaysWhite:
8460           case IcsPlayingWhite:
8461             machineWhite = TRUE;
8462             break;
8463
8464           case MachinePlaysBlack:
8465           case IcsPlayingBlack:
8466             machineWhite = FALSE;
8467             break;
8468
8469           case TwoMachinesPlay:
8470             machineWhite = (cps->twoMachinesColor[0] == 'w');
8471             break;
8472         }
8473         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8474             if (appData.debugMode) {
8475                 fprintf(debugFP,
8476                         "Ignoring move out of turn by %s, gameMode %d"
8477                         ", forwardMost %d\n",
8478                         cps->which, gameMode, forwardMostMove);
8479             }
8480             return;
8481         }
8482
8483         if(cps->alphaRank) AlphaRank(machineMove, 4);
8484
8485         // [HGM] lion: (some very limited) support for Alien protocol
8486         killX = killY = -1;
8487         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8488             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8489             return;
8490         } else if(firstLeg[0]) { // there was a previous leg;
8491             // only support case where same piece makes two step (and don't even test that!)
8492             char buf[20], *p = machineMove+1, *q = buf+1, f;
8493             safeStrCpy(buf, machineMove, 20);
8494             while(isdigit(*q)) q++; // find start of to-square
8495             safeStrCpy(machineMove, firstLeg, 20);
8496             while(isdigit(*p)) p++;
8497             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8498             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8499             firstLeg[0] = NULLCHAR;
8500         }
8501
8502         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8503                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8504             /* Machine move could not be parsed; ignore it. */
8505           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8506                     machineMove, _(cps->which));
8507             DisplayMoveError(buf1);
8508             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8509                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8510             if (gameMode == TwoMachinesPlay) {
8511               GameEnds(machineWhite ? BlackWins : WhiteWins,
8512                        buf1, GE_XBOARD);
8513             }
8514             return;
8515         }
8516
8517         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8518         /* So we have to redo legality test with true e.p. status here,  */
8519         /* to make sure an illegal e.p. capture does not slip through,   */
8520         /* to cause a forfeit on a justified illegal-move complaint      */
8521         /* of the opponent.                                              */
8522         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8523            ChessMove moveType;
8524            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8525                              fromY, fromX, toY, toX, promoChar);
8526             if(moveType == IllegalMove) {
8527               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8528                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8529                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8530                            buf1, GE_XBOARD);
8531                 return;
8532            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8533            /* [HGM] Kludge to handle engines that send FRC-style castling
8534               when they shouldn't (like TSCP-Gothic) */
8535            switch(moveType) {
8536              case WhiteASideCastleFR:
8537              case BlackASideCastleFR:
8538                toX+=2;
8539                currentMoveString[2]++;
8540                break;
8541              case WhiteHSideCastleFR:
8542              case BlackHSideCastleFR:
8543                toX--;
8544                currentMoveString[2]--;
8545                break;
8546              default: ; // nothing to do, but suppresses warning of pedantic compilers
8547            }
8548         }
8549         hintRequested = FALSE;
8550         lastHint[0] = NULLCHAR;
8551         bookRequested = FALSE;
8552         /* Program may be pondering now */
8553         cps->maybeThinking = TRUE;
8554         if (cps->sendTime == 2) cps->sendTime = 1;
8555         if (cps->offeredDraw) cps->offeredDraw--;
8556
8557         /* [AS] Save move info*/
8558         pvInfoList[ forwardMostMove ].score = programStats.score;
8559         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8560         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8561
8562         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8563
8564         /* Test suites abort the 'game' after one move */
8565         if(*appData.finger) {
8566            static FILE *f;
8567            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8568            if(!f) f = fopen(appData.finger, "w");
8569            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8570            else { DisplayFatalError("Bad output file", errno, 0); return; }
8571            free(fen);
8572            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8573         }
8574
8575         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8576         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8577             int count = 0;
8578
8579             while( count < adjudicateLossPlies ) {
8580                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8581
8582                 if( count & 1 ) {
8583                     score = -score; /* Flip score for winning side */
8584                 }
8585
8586                 if( score > adjudicateLossThreshold ) {
8587                     break;
8588                 }
8589
8590                 count++;
8591             }
8592
8593             if( count >= adjudicateLossPlies ) {
8594                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8595
8596                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8597                     "Xboard adjudication",
8598                     GE_XBOARD );
8599
8600                 return;
8601             }
8602         }
8603
8604         if(Adjudicate(cps)) {
8605             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8606             return; // [HGM] adjudicate: for all automatic game ends
8607         }
8608
8609 #if ZIPPY
8610         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8611             first.initDone) {
8612           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8613                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8614                 SendToICS("draw ");
8615                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8616           }
8617           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8618           ics_user_moved = 1;
8619           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8620                 char buf[3*MSG_SIZ];
8621
8622                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8623                         programStats.score / 100.,
8624                         programStats.depth,
8625                         programStats.time / 100.,
8626                         (unsigned int)programStats.nodes,
8627                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8628                         programStats.movelist);
8629                 SendToICS(buf);
8630 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8631           }
8632         }
8633 #endif
8634
8635         /* [AS] Clear stats for next move */
8636         ClearProgramStats();
8637         thinkOutput[0] = NULLCHAR;
8638         hiddenThinkOutputState = 0;
8639
8640         bookHit = NULL;
8641         if (gameMode == TwoMachinesPlay) {
8642             /* [HGM] relaying draw offers moved to after reception of move */
8643             /* and interpreting offer as claim if it brings draw condition */
8644             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8645                 SendToProgram("draw\n", cps->other);
8646             }
8647             if (cps->other->sendTime) {
8648                 SendTimeRemaining(cps->other,
8649                                   cps->other->twoMachinesColor[0] == 'w');
8650             }
8651             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8652             if (firstMove && !bookHit) {
8653                 firstMove = FALSE;
8654                 if (cps->other->useColors) {
8655                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8656                 }
8657                 SendToProgram("go\n", cps->other);
8658             }
8659             cps->other->maybeThinking = TRUE;
8660         }
8661
8662         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8663
8664         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8665
8666         if (!pausing && appData.ringBellAfterMoves) {
8667             if(!roar) RingBell();
8668         }
8669
8670         /*
8671          * Reenable menu items that were disabled while
8672          * machine was thinking
8673          */
8674         if (gameMode != TwoMachinesPlay)
8675             SetUserThinkingEnables();
8676
8677         // [HGM] book: after book hit opponent has received move and is now in force mode
8678         // force the book reply into it, and then fake that it outputted this move by jumping
8679         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8680         if(bookHit) {
8681                 static char bookMove[MSG_SIZ]; // a bit generous?
8682
8683                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8684                 strcat(bookMove, bookHit);
8685                 message = bookMove;
8686                 cps = cps->other;
8687                 programStats.nodes = programStats.depth = programStats.time =
8688                 programStats.score = programStats.got_only_move = 0;
8689                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8690
8691                 if(cps->lastPing != cps->lastPong) {
8692                     savedMessage = message; // args for deferred call
8693                     savedState = cps;
8694                     ScheduleDelayedEvent(DeferredBookMove, 10);
8695                     return;
8696                 }
8697                 goto FakeBookMove;
8698         }
8699
8700         return;
8701     }
8702
8703     /* Set special modes for chess engines.  Later something general
8704      *  could be added here; for now there is just one kludge feature,
8705      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8706      *  when "xboard" is given as an interactive command.
8707      */
8708     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8709         cps->useSigint = FALSE;
8710         cps->useSigterm = FALSE;
8711     }
8712     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8713       ParseFeatures(message+8, cps);
8714       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8715     }
8716
8717     if (!strncmp(message, "setup ", 6) && 
8718         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8719           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8720                                         ) { // [HGM] allow first engine to define opening position
8721       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8722       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8723       *buf = NULLCHAR;
8724       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8725       if(startedFromSetupPosition) return;
8726       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8727       if(dummy >= 3) {
8728         while(message[s] && message[s++] != ' ');
8729         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8730            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8731             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8732             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8733           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8734           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8735         }
8736       }
8737       ParseFEN(boards[0], &dummy, message+s, FALSE);
8738       DrawPosition(TRUE, boards[0]);
8739       startedFromSetupPosition = TRUE;
8740       return;
8741     }
8742     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8743      * want this, I was asked to put it in, and obliged.
8744      */
8745     if (!strncmp(message, "setboard ", 9)) {
8746         Board initial_position;
8747
8748         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8749
8750         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8751             DisplayError(_("Bad FEN received from engine"), 0);
8752             return ;
8753         } else {
8754            Reset(TRUE, FALSE);
8755            CopyBoard(boards[0], initial_position);
8756            initialRulePlies = FENrulePlies;
8757            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8758            else gameMode = MachinePlaysBlack;
8759            DrawPosition(FALSE, boards[currentMove]);
8760         }
8761         return;
8762     }
8763
8764     /*
8765      * Look for communication commands
8766      */
8767     if (!strncmp(message, "telluser ", 9)) {
8768         if(message[9] == '\\' && message[10] == '\\')
8769             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8770         PlayTellSound();
8771         DisplayNote(message + 9);
8772         return;
8773     }
8774     if (!strncmp(message, "tellusererror ", 14)) {
8775         cps->userError = 1;
8776         if(message[14] == '\\' && message[15] == '\\')
8777             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8778         PlayTellSound();
8779         DisplayError(message + 14, 0);
8780         return;
8781     }
8782     if (!strncmp(message, "tellopponent ", 13)) {
8783       if (appData.icsActive) {
8784         if (loggedOn) {
8785           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8786           SendToICS(buf1);
8787         }
8788       } else {
8789         DisplayNote(message + 13);
8790       }
8791       return;
8792     }
8793     if (!strncmp(message, "tellothers ", 11)) {
8794       if (appData.icsActive) {
8795         if (loggedOn) {
8796           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8797           SendToICS(buf1);
8798         }
8799       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8800       return;
8801     }
8802     if (!strncmp(message, "tellall ", 8)) {
8803       if (appData.icsActive) {
8804         if (loggedOn) {
8805           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8806           SendToICS(buf1);
8807         }
8808       } else {
8809         DisplayNote(message + 8);
8810       }
8811       return;
8812     }
8813     if (strncmp(message, "warning", 7) == 0) {
8814         /* Undocumented feature, use tellusererror in new code */
8815         DisplayError(message, 0);
8816         return;
8817     }
8818     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8819         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8820         strcat(realname, " query");
8821         AskQuestion(realname, buf2, buf1, cps->pr);
8822         return;
8823     }
8824     /* Commands from the engine directly to ICS.  We don't allow these to be
8825      *  sent until we are logged on. Crafty kibitzes have been known to
8826      *  interfere with the login process.
8827      */
8828     if (loggedOn) {
8829         if (!strncmp(message, "tellics ", 8)) {
8830             SendToICS(message + 8);
8831             SendToICS("\n");
8832             return;
8833         }
8834         if (!strncmp(message, "tellicsnoalias ", 15)) {
8835             SendToICS(ics_prefix);
8836             SendToICS(message + 15);
8837             SendToICS("\n");
8838             return;
8839         }
8840         /* The following are for backward compatibility only */
8841         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8842             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8843             SendToICS(ics_prefix);
8844             SendToICS(message);
8845             SendToICS("\n");
8846             return;
8847         }
8848     }
8849     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8850         if(initPing == cps->lastPong) {
8851             if(gameInfo.variant == VariantUnknown) {
8852                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8853                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8854                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8855             }
8856             initPing = -1;
8857         }
8858         return;
8859     }
8860     if(!strncmp(message, "highlight ", 10)) {
8861         if(appData.testLegality && appData.markers) return;
8862         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8863         return;
8864     }
8865     if(!strncmp(message, "click ", 6)) {
8866         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8867         if(appData.testLegality || !appData.oneClick) return;
8868         sscanf(message+6, "%c%d%c", &f, &y, &c);
8869         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8870         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8871         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8872         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8873         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8874         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8875             LeftClick(Release, lastLeftX, lastLeftY);
8876         controlKey  = (c == ',');
8877         LeftClick(Press, x, y);
8878         LeftClick(Release, x, y);
8879         first.highlight = f;
8880         return;
8881     }
8882     /*
8883      * If the move is illegal, cancel it and redraw the board.
8884      * Also deal with other error cases.  Matching is rather loose
8885      * here to accommodate engines written before the spec.
8886      */
8887     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8888         strncmp(message, "Error", 5) == 0) {
8889         if (StrStr(message, "name") ||
8890             StrStr(message, "rating") || StrStr(message, "?") ||
8891             StrStr(message, "result") || StrStr(message, "board") ||
8892             StrStr(message, "bk") || StrStr(message, "computer") ||
8893             StrStr(message, "variant") || StrStr(message, "hint") ||
8894             StrStr(message, "random") || StrStr(message, "depth") ||
8895             StrStr(message, "accepted")) {
8896             return;
8897         }
8898         if (StrStr(message, "protover")) {
8899           /* Program is responding to input, so it's apparently done
8900              initializing, and this error message indicates it is
8901              protocol version 1.  So we don't need to wait any longer
8902              for it to initialize and send feature commands. */
8903           FeatureDone(cps, 1);
8904           cps->protocolVersion = 1;
8905           return;
8906         }
8907         cps->maybeThinking = FALSE;
8908
8909         if (StrStr(message, "draw")) {
8910             /* Program doesn't have "draw" command */
8911             cps->sendDrawOffers = 0;
8912             return;
8913         }
8914         if (cps->sendTime != 1 &&
8915             (StrStr(message, "time") || StrStr(message, "otim"))) {
8916           /* Program apparently doesn't have "time" or "otim" command */
8917           cps->sendTime = 0;
8918           return;
8919         }
8920         if (StrStr(message, "analyze")) {
8921             cps->analysisSupport = FALSE;
8922             cps->analyzing = FALSE;
8923 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8924             EditGameEvent(); // [HGM] try to preserve loaded game
8925             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8926             DisplayError(buf2, 0);
8927             return;
8928         }
8929         if (StrStr(message, "(no matching move)st")) {
8930           /* Special kludge for GNU Chess 4 only */
8931           cps->stKludge = TRUE;
8932           SendTimeControl(cps, movesPerSession, timeControl,
8933                           timeIncrement, appData.searchDepth,
8934                           searchTime);
8935           return;
8936         }
8937         if (StrStr(message, "(no matching move)sd")) {
8938           /* Special kludge for GNU Chess 4 only */
8939           cps->sdKludge = TRUE;
8940           SendTimeControl(cps, movesPerSession, timeControl,
8941                           timeIncrement, appData.searchDepth,
8942                           searchTime);
8943           return;
8944         }
8945         if (!StrStr(message, "llegal")) {
8946             return;
8947         }
8948         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8949             gameMode == IcsIdle) return;
8950         if (forwardMostMove <= backwardMostMove) return;
8951         if (pausing) PauseEvent();
8952       if(appData.forceIllegal) {
8953             // [HGM] illegal: machine refused move; force position after move into it
8954           SendToProgram("force\n", cps);
8955           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8956                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8957                 // when black is to move, while there might be nothing on a2 or black
8958                 // might already have the move. So send the board as if white has the move.
8959                 // But first we must change the stm of the engine, as it refused the last move
8960                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8961                 if(WhiteOnMove(forwardMostMove)) {
8962                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8963                     SendBoard(cps, forwardMostMove); // kludgeless board
8964                 } else {
8965                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8966                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8967                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8968                 }
8969           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8970             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8971                  gameMode == TwoMachinesPlay)
8972               SendToProgram("go\n", cps);
8973             return;
8974       } else
8975         if (gameMode == PlayFromGameFile) {
8976             /* Stop reading this game file */
8977             gameMode = EditGame;
8978             ModeHighlight();
8979         }
8980         /* [HGM] illegal-move claim should forfeit game when Xboard */
8981         /* only passes fully legal moves                            */
8982         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8983             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8984                                 "False illegal-move claim", GE_XBOARD );
8985             return; // do not take back move we tested as valid
8986         }
8987         currentMove = forwardMostMove-1;
8988         DisplayMove(currentMove-1); /* before DisplayMoveError */
8989         SwitchClocks(forwardMostMove-1); // [HGM] race
8990         DisplayBothClocks();
8991         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8992                 parseList[currentMove], _(cps->which));
8993         DisplayMoveError(buf1);
8994         DrawPosition(FALSE, boards[currentMove]);
8995
8996         SetUserThinkingEnables();
8997         return;
8998     }
8999     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9000         /* Program has a broken "time" command that
9001            outputs a string not ending in newline.
9002            Don't use it. */
9003         cps->sendTime = 0;
9004     }
9005
9006     /*
9007      * If chess program startup fails, exit with an error message.
9008      * Attempts to recover here are futile. [HGM] Well, we try anyway
9009      */
9010     if ((StrStr(message, "unknown host") != NULL)
9011         || (StrStr(message, "No remote directory") != NULL)
9012         || (StrStr(message, "not found") != NULL)
9013         || (StrStr(message, "No such file") != NULL)
9014         || (StrStr(message, "can't alloc") != NULL)
9015         || (StrStr(message, "Permission denied") != NULL)) {
9016
9017         cps->maybeThinking = FALSE;
9018         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9019                 _(cps->which), cps->program, cps->host, message);
9020         RemoveInputSource(cps->isr);
9021         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9022             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9023             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9024         }
9025         return;
9026     }
9027
9028     /*
9029      * Look for hint output
9030      */
9031     if (sscanf(message, "Hint: %s", buf1) == 1) {
9032         if (cps == &first && hintRequested) {
9033             hintRequested = FALSE;
9034             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9035                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9036                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9037                                     PosFlags(forwardMostMove),
9038                                     fromY, fromX, toY, toX, promoChar, buf1);
9039                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9040                 DisplayInformation(buf2);
9041             } else {
9042                 /* Hint move could not be parsed!? */
9043               snprintf(buf2, sizeof(buf2),
9044                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9045                         buf1, _(cps->which));
9046                 DisplayError(buf2, 0);
9047             }
9048         } else {
9049           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9050         }
9051         return;
9052     }
9053
9054     /*
9055      * Ignore other messages if game is not in progress
9056      */
9057     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9058         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9059
9060     /*
9061      * look for win, lose, draw, or draw offer
9062      */
9063     if (strncmp(message, "1-0", 3) == 0) {
9064         char *p, *q, *r = "";
9065         p = strchr(message, '{');
9066         if (p) {
9067             q = strchr(p, '}');
9068             if (q) {
9069                 *q = NULLCHAR;
9070                 r = p + 1;
9071             }
9072         }
9073         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9074         return;
9075     } else if (strncmp(message, "0-1", 3) == 0) {
9076         char *p, *q, *r = "";
9077         p = strchr(message, '{');
9078         if (p) {
9079             q = strchr(p, '}');
9080             if (q) {
9081                 *q = NULLCHAR;
9082                 r = p + 1;
9083             }
9084         }
9085         /* Kludge for Arasan 4.1 bug */
9086         if (strcmp(r, "Black resigns") == 0) {
9087             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9088             return;
9089         }
9090         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9091         return;
9092     } else if (strncmp(message, "1/2", 3) == 0) {
9093         char *p, *q, *r = "";
9094         p = strchr(message, '{');
9095         if (p) {
9096             q = strchr(p, '}');
9097             if (q) {
9098                 *q = NULLCHAR;
9099                 r = p + 1;
9100             }
9101         }
9102
9103         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9104         return;
9105
9106     } else if (strncmp(message, "White resign", 12) == 0) {
9107         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9108         return;
9109     } else if (strncmp(message, "Black resign", 12) == 0) {
9110         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9111         return;
9112     } else if (strncmp(message, "White matches", 13) == 0 ||
9113                strncmp(message, "Black matches", 13) == 0   ) {
9114         /* [HGM] ignore GNUShogi noises */
9115         return;
9116     } else if (strncmp(message, "White", 5) == 0 &&
9117                message[5] != '(' &&
9118                StrStr(message, "Black") == NULL) {
9119         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9120         return;
9121     } else if (strncmp(message, "Black", 5) == 0 &&
9122                message[5] != '(') {
9123         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9124         return;
9125     } else if (strcmp(message, "resign") == 0 ||
9126                strcmp(message, "computer resigns") == 0) {
9127         switch (gameMode) {
9128           case MachinePlaysBlack:
9129           case IcsPlayingBlack:
9130             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9131             break;
9132           case MachinePlaysWhite:
9133           case IcsPlayingWhite:
9134             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9135             break;
9136           case TwoMachinesPlay:
9137             if (cps->twoMachinesColor[0] == 'w')
9138               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9139             else
9140               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9141             break;
9142           default:
9143             /* can't happen */
9144             break;
9145         }
9146         return;
9147     } else if (strncmp(message, "opponent mates", 14) == 0) {
9148         switch (gameMode) {
9149           case MachinePlaysBlack:
9150           case IcsPlayingBlack:
9151             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9152             break;
9153           case MachinePlaysWhite:
9154           case IcsPlayingWhite:
9155             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9156             break;
9157           case TwoMachinesPlay:
9158             if (cps->twoMachinesColor[0] == 'w')
9159               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9160             else
9161               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9162             break;
9163           default:
9164             /* can't happen */
9165             break;
9166         }
9167         return;
9168     } else if (strncmp(message, "computer mates", 14) == 0) {
9169         switch (gameMode) {
9170           case MachinePlaysBlack:
9171           case IcsPlayingBlack:
9172             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9173             break;
9174           case MachinePlaysWhite:
9175           case IcsPlayingWhite:
9176             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9177             break;
9178           case TwoMachinesPlay:
9179             if (cps->twoMachinesColor[0] == 'w')
9180               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9181             else
9182               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9183             break;
9184           default:
9185             /* can't happen */
9186             break;
9187         }
9188         return;
9189     } else if (strncmp(message, "checkmate", 9) == 0) {
9190         if (WhiteOnMove(forwardMostMove)) {
9191             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9192         } else {
9193             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9194         }
9195         return;
9196     } else if (strstr(message, "Draw") != NULL ||
9197                strstr(message, "game is a draw") != NULL) {
9198         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9199         return;
9200     } else if (strstr(message, "offer") != NULL &&
9201                strstr(message, "draw") != NULL) {
9202 #if ZIPPY
9203         if (appData.zippyPlay && first.initDone) {
9204             /* Relay offer to ICS */
9205             SendToICS(ics_prefix);
9206             SendToICS("draw\n");
9207         }
9208 #endif
9209         cps->offeredDraw = 2; /* valid until this engine moves twice */
9210         if (gameMode == TwoMachinesPlay) {
9211             if (cps->other->offeredDraw) {
9212                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9213             /* [HGM] in two-machine mode we delay relaying draw offer      */
9214             /* until after we also have move, to see if it is really claim */
9215             }
9216         } else if (gameMode == MachinePlaysWhite ||
9217                    gameMode == MachinePlaysBlack) {
9218           if (userOfferedDraw) {
9219             DisplayInformation(_("Machine accepts your draw offer"));
9220             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9221           } else {
9222             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9223           }
9224         }
9225     }
9226
9227
9228     /*
9229      * Look for thinking output
9230      */
9231     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9232           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9233                                 ) {
9234         int plylev, mvleft, mvtot, curscore, time;
9235         char mvname[MOVE_LEN];
9236         u64 nodes; // [DM]
9237         char plyext;
9238         int ignore = FALSE;
9239         int prefixHint = FALSE;
9240         mvname[0] = NULLCHAR;
9241
9242         switch (gameMode) {
9243           case MachinePlaysBlack:
9244           case IcsPlayingBlack:
9245             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9246             break;
9247           case MachinePlaysWhite:
9248           case IcsPlayingWhite:
9249             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9250             break;
9251           case AnalyzeMode:
9252           case AnalyzeFile:
9253             break;
9254           case IcsObserving: /* [DM] icsEngineAnalyze */
9255             if (!appData.icsEngineAnalyze) ignore = TRUE;
9256             break;
9257           case TwoMachinesPlay:
9258             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9259                 ignore = TRUE;
9260             }
9261             break;
9262           default:
9263             ignore = TRUE;
9264             break;
9265         }
9266
9267         if (!ignore) {
9268             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9269             buf1[0] = NULLCHAR;
9270             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9271                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9272
9273                 if (plyext != ' ' && plyext != '\t') {
9274                     time *= 100;
9275                 }
9276
9277                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9278                 if( cps->scoreIsAbsolute &&
9279                     ( gameMode == MachinePlaysBlack ||
9280                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9281                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9282                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9283                      !WhiteOnMove(currentMove)
9284                     ) )
9285                 {
9286                     curscore = -curscore;
9287                 }
9288
9289                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9290
9291                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9292                         char buf[MSG_SIZ];
9293                         FILE *f;
9294                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9295                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9296                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9297                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9298                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9299                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9300                                 fclose(f);
9301                         }
9302                         else
9303                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9304                           DisplayError(_("failed writing PV"), 0);
9305                 }
9306
9307                 tempStats.depth = plylev;
9308                 tempStats.nodes = nodes;
9309                 tempStats.time = time;
9310                 tempStats.score = curscore;
9311                 tempStats.got_only_move = 0;
9312
9313                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9314                         int ticklen;
9315
9316                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9317                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9318                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9319                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9320                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9321                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9322                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9323                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9324                 }
9325
9326                 /* Buffer overflow protection */
9327                 if (pv[0] != NULLCHAR) {
9328                     if (strlen(pv) >= sizeof(tempStats.movelist)
9329                         && appData.debugMode) {
9330                         fprintf(debugFP,
9331                                 "PV is too long; using the first %u bytes.\n",
9332                                 (unsigned) sizeof(tempStats.movelist) - 1);
9333                     }
9334
9335                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9336                 } else {
9337                     sprintf(tempStats.movelist, " no PV\n");
9338                 }
9339
9340                 if (tempStats.seen_stat) {
9341                     tempStats.ok_to_send = 1;
9342                 }
9343
9344                 if (strchr(tempStats.movelist, '(') != NULL) {
9345                     tempStats.line_is_book = 1;
9346                     tempStats.nr_moves = 0;
9347                     tempStats.moves_left = 0;
9348                 } else {
9349                     tempStats.line_is_book = 0;
9350                 }
9351
9352                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9353                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9354
9355                 SendProgramStatsToFrontend( cps, &tempStats );
9356
9357                 /*
9358                     [AS] Protect the thinkOutput buffer from overflow... this
9359                     is only useful if buf1 hasn't overflowed first!
9360                 */
9361                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9362                          plylev,
9363                          (gameMode == TwoMachinesPlay ?
9364                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9365                          ((double) curscore) / 100.0,
9366                          prefixHint ? lastHint : "",
9367                          prefixHint ? " " : "" );
9368
9369                 if( buf1[0] != NULLCHAR ) {
9370                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9371
9372                     if( strlen(pv) > max_len ) {
9373                         if( appData.debugMode) {
9374                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9375                         }
9376                         pv[max_len+1] = '\0';
9377                     }
9378
9379                     strcat( thinkOutput, pv);
9380                 }
9381
9382                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9383                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9384                     DisplayMove(currentMove - 1);
9385                 }
9386                 return;
9387
9388             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9389                 /* crafty (9.25+) says "(only move) <move>"
9390                  * if there is only 1 legal move
9391                  */
9392                 sscanf(p, "(only move) %s", buf1);
9393                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9394                 sprintf(programStats.movelist, "%s (only move)", buf1);
9395                 programStats.depth = 1;
9396                 programStats.nr_moves = 1;
9397                 programStats.moves_left = 1;
9398                 programStats.nodes = 1;
9399                 programStats.time = 1;
9400                 programStats.got_only_move = 1;
9401
9402                 /* Not really, but we also use this member to
9403                    mean "line isn't going to change" (Crafty
9404                    isn't searching, so stats won't change) */
9405                 programStats.line_is_book = 1;
9406
9407                 SendProgramStatsToFrontend( cps, &programStats );
9408
9409                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9410                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9411                     DisplayMove(currentMove - 1);
9412                 }
9413                 return;
9414             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9415                               &time, &nodes, &plylev, &mvleft,
9416                               &mvtot, mvname) >= 5) {
9417                 /* The stat01: line is from Crafty (9.29+) in response
9418                    to the "." command */
9419                 programStats.seen_stat = 1;
9420                 cps->maybeThinking = TRUE;
9421
9422                 if (programStats.got_only_move || !appData.periodicUpdates)
9423                   return;
9424
9425                 programStats.depth = plylev;
9426                 programStats.time = time;
9427                 programStats.nodes = nodes;
9428                 programStats.moves_left = mvleft;
9429                 programStats.nr_moves = mvtot;
9430                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9431                 programStats.ok_to_send = 1;
9432                 programStats.movelist[0] = '\0';
9433
9434                 SendProgramStatsToFrontend( cps, &programStats );
9435
9436                 return;
9437
9438             } else if (strncmp(message,"++",2) == 0) {
9439                 /* Crafty 9.29+ outputs this */
9440                 programStats.got_fail = 2;
9441                 return;
9442
9443             } else if (strncmp(message,"--",2) == 0) {
9444                 /* Crafty 9.29+ outputs this */
9445                 programStats.got_fail = 1;
9446                 return;
9447
9448             } else if (thinkOutput[0] != NULLCHAR &&
9449                        strncmp(message, "    ", 4) == 0) {
9450                 unsigned message_len;
9451
9452                 p = message;
9453                 while (*p && *p == ' ') p++;
9454
9455                 message_len = strlen( p );
9456
9457                 /* [AS] Avoid buffer overflow */
9458                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9459                     strcat(thinkOutput, " ");
9460                     strcat(thinkOutput, p);
9461                 }
9462
9463                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9464                     strcat(programStats.movelist, " ");
9465                     strcat(programStats.movelist, p);
9466                 }
9467
9468                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9469                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9470                     DisplayMove(currentMove - 1);
9471                 }
9472                 return;
9473             }
9474         }
9475         else {
9476             buf1[0] = NULLCHAR;
9477
9478             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9479                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9480             {
9481                 ChessProgramStats cpstats;
9482
9483                 if (plyext != ' ' && plyext != '\t') {
9484                     time *= 100;
9485                 }
9486
9487                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9488                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9489                     curscore = -curscore;
9490                 }
9491
9492                 cpstats.depth = plylev;
9493                 cpstats.nodes = nodes;
9494                 cpstats.time = time;
9495                 cpstats.score = curscore;
9496                 cpstats.got_only_move = 0;
9497                 cpstats.movelist[0] = '\0';
9498
9499                 if (buf1[0] != NULLCHAR) {
9500                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9501                 }
9502
9503                 cpstats.ok_to_send = 0;
9504                 cpstats.line_is_book = 0;
9505                 cpstats.nr_moves = 0;
9506                 cpstats.moves_left = 0;
9507
9508                 SendProgramStatsToFrontend( cps, &cpstats );
9509             }
9510         }
9511     }
9512 }
9513
9514
9515 /* Parse a game score from the character string "game", and
9516    record it as the history of the current game.  The game
9517    score is NOT assumed to start from the standard position.
9518    The display is not updated in any way.
9519    */
9520 void
9521 ParseGameHistory (char *game)
9522 {
9523     ChessMove moveType;
9524     int fromX, fromY, toX, toY, boardIndex;
9525     char promoChar;
9526     char *p, *q;
9527     char buf[MSG_SIZ];
9528
9529     if (appData.debugMode)
9530       fprintf(debugFP, "Parsing game history: %s\n", game);
9531
9532     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9533     gameInfo.site = StrSave(appData.icsHost);
9534     gameInfo.date = PGNDate();
9535     gameInfo.round = StrSave("-");
9536
9537     /* Parse out names of players */
9538     while (*game == ' ') game++;
9539     p = buf;
9540     while (*game != ' ') *p++ = *game++;
9541     *p = NULLCHAR;
9542     gameInfo.white = StrSave(buf);
9543     while (*game == ' ') game++;
9544     p = buf;
9545     while (*game != ' ' && *game != '\n') *p++ = *game++;
9546     *p = NULLCHAR;
9547     gameInfo.black = StrSave(buf);
9548
9549     /* Parse moves */
9550     boardIndex = blackPlaysFirst ? 1 : 0;
9551     yynewstr(game);
9552     for (;;) {
9553         yyboardindex = boardIndex;
9554         moveType = (ChessMove) Myylex();
9555         switch (moveType) {
9556           case IllegalMove:             /* maybe suicide chess, etc. */
9557   if (appData.debugMode) {
9558     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9559     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9560     setbuf(debugFP, NULL);
9561   }
9562           case WhitePromotion:
9563           case BlackPromotion:
9564           case WhiteNonPromotion:
9565           case BlackNonPromotion:
9566           case NormalMove:
9567           case FirstLeg:
9568           case WhiteCapturesEnPassant:
9569           case BlackCapturesEnPassant:
9570           case WhiteKingSideCastle:
9571           case WhiteQueenSideCastle:
9572           case BlackKingSideCastle:
9573           case BlackQueenSideCastle:
9574           case WhiteKingSideCastleWild:
9575           case WhiteQueenSideCastleWild:
9576           case BlackKingSideCastleWild:
9577           case BlackQueenSideCastleWild:
9578           /* PUSH Fabien */
9579           case WhiteHSideCastleFR:
9580           case WhiteASideCastleFR:
9581           case BlackHSideCastleFR:
9582           case BlackASideCastleFR:
9583           /* POP Fabien */
9584             fromX = currentMoveString[0] - AAA;
9585             fromY = currentMoveString[1] - ONE;
9586             toX = currentMoveString[2] - AAA;
9587             toY = currentMoveString[3] - ONE;
9588             promoChar = currentMoveString[4];
9589             break;
9590           case WhiteDrop:
9591           case BlackDrop:
9592             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9593             fromX = moveType == WhiteDrop ?
9594               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9595             (int) CharToPiece(ToLower(currentMoveString[0]));
9596             fromY = DROP_RANK;
9597             toX = currentMoveString[2] - AAA;
9598             toY = currentMoveString[3] - ONE;
9599             promoChar = NULLCHAR;
9600             break;
9601           case AmbiguousMove:
9602             /* bug? */
9603             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9604   if (appData.debugMode) {
9605     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9606     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9607     setbuf(debugFP, NULL);
9608   }
9609             DisplayError(buf, 0);
9610             return;
9611           case ImpossibleMove:
9612             /* bug? */
9613             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9614   if (appData.debugMode) {
9615     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9616     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9617     setbuf(debugFP, NULL);
9618   }
9619             DisplayError(buf, 0);
9620             return;
9621           case EndOfFile:
9622             if (boardIndex < backwardMostMove) {
9623                 /* Oops, gap.  How did that happen? */
9624                 DisplayError(_("Gap in move list"), 0);
9625                 return;
9626             }
9627             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9628             if (boardIndex > forwardMostMove) {
9629                 forwardMostMove = boardIndex;
9630             }
9631             return;
9632           case ElapsedTime:
9633             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9634                 strcat(parseList[boardIndex-1], " ");
9635                 strcat(parseList[boardIndex-1], yy_text);
9636             }
9637             continue;
9638           case Comment:
9639           case PGNTag:
9640           case NAG:
9641           default:
9642             /* ignore */
9643             continue;
9644           case WhiteWins:
9645           case BlackWins:
9646           case GameIsDrawn:
9647           case GameUnfinished:
9648             if (gameMode == IcsExamining) {
9649                 if (boardIndex < backwardMostMove) {
9650                     /* Oops, gap.  How did that happen? */
9651                     return;
9652                 }
9653                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9654                 return;
9655             }
9656             gameInfo.result = moveType;
9657             p = strchr(yy_text, '{');
9658             if (p == NULL) p = strchr(yy_text, '(');
9659             if (p == NULL) {
9660                 p = yy_text;
9661                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9662             } else {
9663                 q = strchr(p, *p == '{' ? '}' : ')');
9664                 if (q != NULL) *q = NULLCHAR;
9665                 p++;
9666             }
9667             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9668             gameInfo.resultDetails = StrSave(p);
9669             continue;
9670         }
9671         if (boardIndex >= forwardMostMove &&
9672             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9673             backwardMostMove = blackPlaysFirst ? 1 : 0;
9674             return;
9675         }
9676         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9677                                  fromY, fromX, toY, toX, promoChar,
9678                                  parseList[boardIndex]);
9679         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9680         /* currentMoveString is set as a side-effect of yylex */
9681         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9682         strcat(moveList[boardIndex], "\n");
9683         boardIndex++;
9684         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9685         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9686           case MT_NONE:
9687           case MT_STALEMATE:
9688           default:
9689             break;
9690           case MT_CHECK:
9691             if(gameInfo.variant != VariantShogi)
9692                 strcat(parseList[boardIndex - 1], "+");
9693             break;
9694           case MT_CHECKMATE:
9695           case MT_STAINMATE:
9696             strcat(parseList[boardIndex - 1], "#");
9697             break;
9698         }
9699     }
9700 }
9701
9702
9703 /* Apply a move to the given board  */
9704 void
9705 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9706 {
9707   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9708   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9709
9710     /* [HGM] compute & store e.p. status and castling rights for new position */
9711     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9712
9713       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9714       oldEP = (signed char)board[EP_STATUS];
9715       board[EP_STATUS] = EP_NONE;
9716
9717   if (fromY == DROP_RANK) {
9718         /* must be first */
9719         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9720             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9721             return;
9722         }
9723         piece = board[toY][toX] = (ChessSquare) fromX;
9724   } else {
9725       ChessSquare victim;
9726       int i;
9727
9728       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9729            victim = board[killY][killX],
9730            board[killY][killX] = EmptySquare,
9731            board[EP_STATUS] = EP_CAPTURE;
9732
9733       if( board[toY][toX] != EmptySquare ) {
9734            board[EP_STATUS] = EP_CAPTURE;
9735            if( (fromX != toX || fromY != toY) && // not igui!
9736                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9737                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9738                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9739            }
9740       }
9741
9742       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9743            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9744                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9745       } else
9746       if( board[fromY][fromX] == WhitePawn ) {
9747            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9748                board[EP_STATUS] = EP_PAWN_MOVE;
9749            if( toY-fromY==2) {
9750                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9751                         gameInfo.variant != VariantBerolina || toX < fromX)
9752                       board[EP_STATUS] = toX | berolina;
9753                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9754                         gameInfo.variant != VariantBerolina || toX > fromX)
9755                       board[EP_STATUS] = toX;
9756            }
9757       } else
9758       if( board[fromY][fromX] == BlackPawn ) {
9759            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9760                board[EP_STATUS] = EP_PAWN_MOVE;
9761            if( toY-fromY== -2) {
9762                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9763                         gameInfo.variant != VariantBerolina || toX < fromX)
9764                       board[EP_STATUS] = toX | berolina;
9765                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9766                         gameInfo.variant != VariantBerolina || toX > fromX)
9767                       board[EP_STATUS] = toX;
9768            }
9769        }
9770
9771        for(i=0; i<nrCastlingRights; i++) {
9772            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9773               board[CASTLING][i] == toX   && castlingRank[i] == toY
9774              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9775        }
9776
9777        if(gameInfo.variant == VariantSChess) { // update virginity
9778            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9779            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9780            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9781            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9782        }
9783
9784      if (fromX == toX && fromY == toY) return;
9785
9786      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9787      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9788      if(gameInfo.variant == VariantKnightmate)
9789          king += (int) WhiteUnicorn - (int) WhiteKing;
9790
9791     /* Code added by Tord: */
9792     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9793     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9794         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9795       board[fromY][fromX] = EmptySquare;
9796       board[toY][toX] = EmptySquare;
9797       if((toX > fromX) != (piece == WhiteRook)) {
9798         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9799       } else {
9800         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9801       }
9802     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9803                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9804       board[fromY][fromX] = EmptySquare;
9805       board[toY][toX] = EmptySquare;
9806       if((toX > fromX) != (piece == BlackRook)) {
9807         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9808       } else {
9809         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9810       }
9811     /* End of code added by Tord */
9812
9813     } else if (board[fromY][fromX] == king
9814         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9815         && toY == fromY && toX > fromX+1) {
9816         board[fromY][fromX] = EmptySquare;
9817         board[toY][toX] = king;
9818         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9819         board[fromY][BOARD_RGHT-1] = EmptySquare;
9820     } else if (board[fromY][fromX] == king
9821         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9822                && toY == fromY && toX < fromX-1) {
9823         board[fromY][fromX] = EmptySquare;
9824         board[toY][toX] = king;
9825         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9826         board[fromY][BOARD_LEFT] = EmptySquare;
9827     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9828                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9829                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9830                ) {
9831         /* white pawn promotion */
9832         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9833         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9834             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9835         board[fromY][fromX] = EmptySquare;
9836     } else if ((fromY >= BOARD_HEIGHT>>1)
9837                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9838                && (toX != fromX)
9839                && gameInfo.variant != VariantXiangqi
9840                && gameInfo.variant != VariantBerolina
9841                && (board[fromY][fromX] == WhitePawn)
9842                && (board[toY][toX] == EmptySquare)) {
9843         board[fromY][fromX] = EmptySquare;
9844         board[toY][toX] = WhitePawn;
9845         captured = board[toY - 1][toX];
9846         board[toY - 1][toX] = EmptySquare;
9847     } else if ((fromY == BOARD_HEIGHT-4)
9848                && (toX == fromX)
9849                && gameInfo.variant == VariantBerolina
9850                && (board[fromY][fromX] == WhitePawn)
9851                && (board[toY][toX] == EmptySquare)) {
9852         board[fromY][fromX] = EmptySquare;
9853         board[toY][toX] = WhitePawn;
9854         if(oldEP & EP_BEROLIN_A) {
9855                 captured = board[fromY][fromX-1];
9856                 board[fromY][fromX-1] = EmptySquare;
9857         }else{  captured = board[fromY][fromX+1];
9858                 board[fromY][fromX+1] = EmptySquare;
9859         }
9860     } else if (board[fromY][fromX] == king
9861         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9862                && toY == fromY && toX > fromX+1) {
9863         board[fromY][fromX] = EmptySquare;
9864         board[toY][toX] = king;
9865         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9866         board[fromY][BOARD_RGHT-1] = EmptySquare;
9867     } else if (board[fromY][fromX] == king
9868         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9869                && toY == fromY && toX < fromX-1) {
9870         board[fromY][fromX] = EmptySquare;
9871         board[toY][toX] = king;
9872         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9873         board[fromY][BOARD_LEFT] = EmptySquare;
9874     } else if (fromY == 7 && fromX == 3
9875                && board[fromY][fromX] == BlackKing
9876                && toY == 7 && toX == 5) {
9877         board[fromY][fromX] = EmptySquare;
9878         board[toY][toX] = BlackKing;
9879         board[fromY][7] = EmptySquare;
9880         board[toY][4] = BlackRook;
9881     } else if (fromY == 7 && fromX == 3
9882                && board[fromY][fromX] == BlackKing
9883                && toY == 7 && toX == 1) {
9884         board[fromY][fromX] = EmptySquare;
9885         board[toY][toX] = BlackKing;
9886         board[fromY][0] = EmptySquare;
9887         board[toY][2] = BlackRook;
9888     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9889                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9890                && toY < promoRank && promoChar
9891                ) {
9892         /* black pawn promotion */
9893         board[toY][toX] = CharToPiece(ToLower(promoChar));
9894         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9895             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9896         board[fromY][fromX] = EmptySquare;
9897     } else if ((fromY < BOARD_HEIGHT>>1)
9898                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9899                && (toX != fromX)
9900                && gameInfo.variant != VariantXiangqi
9901                && gameInfo.variant != VariantBerolina
9902                && (board[fromY][fromX] == BlackPawn)
9903                && (board[toY][toX] == EmptySquare)) {
9904         board[fromY][fromX] = EmptySquare;
9905         board[toY][toX] = BlackPawn;
9906         captured = board[toY + 1][toX];
9907         board[toY + 1][toX] = EmptySquare;
9908     } else if ((fromY == 3)
9909                && (toX == fromX)
9910                && gameInfo.variant == VariantBerolina
9911                && (board[fromY][fromX] == BlackPawn)
9912                && (board[toY][toX] == EmptySquare)) {
9913         board[fromY][fromX] = EmptySquare;
9914         board[toY][toX] = BlackPawn;
9915         if(oldEP & EP_BEROLIN_A) {
9916                 captured = board[fromY][fromX-1];
9917                 board[fromY][fromX-1] = EmptySquare;
9918         }else{  captured = board[fromY][fromX+1];
9919                 board[fromY][fromX+1] = EmptySquare;
9920         }
9921     } else {
9922         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9923         board[fromY][fromX] = EmptySquare;
9924         board[toY][toX] = piece;
9925     }
9926   }
9927
9928     if (gameInfo.holdingsWidth != 0) {
9929
9930       /* !!A lot more code needs to be written to support holdings  */
9931       /* [HGM] OK, so I have written it. Holdings are stored in the */
9932       /* penultimate board files, so they are automaticlly stored   */
9933       /* in the game history.                                       */
9934       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9935                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9936         /* Delete from holdings, by decreasing count */
9937         /* and erasing image if necessary            */
9938         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9939         if(p < (int) BlackPawn) { /* white drop */
9940              p -= (int)WhitePawn;
9941                  p = PieceToNumber((ChessSquare)p);
9942              if(p >= gameInfo.holdingsSize) p = 0;
9943              if(--board[p][BOARD_WIDTH-2] <= 0)
9944                   board[p][BOARD_WIDTH-1] = EmptySquare;
9945              if((int)board[p][BOARD_WIDTH-2] < 0)
9946                         board[p][BOARD_WIDTH-2] = 0;
9947         } else {                  /* black drop */
9948              p -= (int)BlackPawn;
9949                  p = PieceToNumber((ChessSquare)p);
9950              if(p >= gameInfo.holdingsSize) p = 0;
9951              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9952                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9953              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9954                         board[BOARD_HEIGHT-1-p][1] = 0;
9955         }
9956       }
9957       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9958           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9959         /* [HGM] holdings: Add to holdings, if holdings exist */
9960         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9961                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9962                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9963         }
9964         p = (int) captured;
9965         if (p >= (int) BlackPawn) {
9966           p -= (int)BlackPawn;
9967           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9968                   /* in Shogi restore piece to its original  first */
9969                   captured = (ChessSquare) (DEMOTED captured);
9970                   p = DEMOTED p;
9971           }
9972           p = PieceToNumber((ChessSquare)p);
9973           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9974           board[p][BOARD_WIDTH-2]++;
9975           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9976         } else {
9977           p -= (int)WhitePawn;
9978           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9979                   captured = (ChessSquare) (DEMOTED captured);
9980                   p = DEMOTED p;
9981           }
9982           p = PieceToNumber((ChessSquare)p);
9983           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9984           board[BOARD_HEIGHT-1-p][1]++;
9985           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9986         }
9987       }
9988     } else if (gameInfo.variant == VariantAtomic) {
9989       if (captured != EmptySquare) {
9990         int y, x;
9991         for (y = toY-1; y <= toY+1; y++) {
9992           for (x = toX-1; x <= toX+1; x++) {
9993             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9994                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9995               board[y][x] = EmptySquare;
9996             }
9997           }
9998         }
9999         board[toY][toX] = EmptySquare;
10000       }
10001     }
10002     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10003         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10004     } else
10005     if(promoChar == '+') {
10006         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10007         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10008         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10009           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10010     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10011         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10012         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10013            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10014         board[toY][toX] = newPiece;
10015     }
10016     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10017                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10018         // [HGM] superchess: take promotion piece out of holdings
10019         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10020         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10021             if(!--board[k][BOARD_WIDTH-2])
10022                 board[k][BOARD_WIDTH-1] = EmptySquare;
10023         } else {
10024             if(!--board[BOARD_HEIGHT-1-k][1])
10025                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10026         }
10027     }
10028 }
10029
10030 /* Updates forwardMostMove */
10031 void
10032 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10033 {
10034     int x = toX, y = toY;
10035     char *s = parseList[forwardMostMove];
10036     ChessSquare p = boards[forwardMostMove][toY][toX];
10037 //    forwardMostMove++; // [HGM] bare: moved downstream
10038
10039     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10040     (void) CoordsToAlgebraic(boards[forwardMostMove],
10041                              PosFlags(forwardMostMove),
10042                              fromY, fromX, y, x, promoChar,
10043                              s);
10044     if(killX >= 0 && killY >= 0)
10045         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10046
10047     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10048         int timeLeft; static int lastLoadFlag=0; int king, piece;
10049         piece = boards[forwardMostMove][fromY][fromX];
10050         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10051         if(gameInfo.variant == VariantKnightmate)
10052             king += (int) WhiteUnicorn - (int) WhiteKing;
10053         if(forwardMostMove == 0) {
10054             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10055                 fprintf(serverMoves, "%s;", UserName());
10056             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10057                 fprintf(serverMoves, "%s;", second.tidy);
10058             fprintf(serverMoves, "%s;", first.tidy);
10059             if(gameMode == MachinePlaysWhite)
10060                 fprintf(serverMoves, "%s;", UserName());
10061             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10062                 fprintf(serverMoves, "%s;", second.tidy);
10063         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10064         lastLoadFlag = loadFlag;
10065         // print base move
10066         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10067         // print castling suffix
10068         if( toY == fromY && piece == king ) {
10069             if(toX-fromX > 1)
10070                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10071             if(fromX-toX >1)
10072                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10073         }
10074         // e.p. suffix
10075         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10076              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10077              boards[forwardMostMove][toY][toX] == EmptySquare
10078              && fromX != toX && fromY != toY)
10079                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10080         // promotion suffix
10081         if(promoChar != NULLCHAR) {
10082             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10083                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10084                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10085             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10086         }
10087         if(!loadFlag) {
10088                 char buf[MOVE_LEN*2], *p; int len;
10089             fprintf(serverMoves, "/%d/%d",
10090                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10091             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10092             else                      timeLeft = blackTimeRemaining/1000;
10093             fprintf(serverMoves, "/%d", timeLeft);
10094                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10095                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10096                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10097                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10098             fprintf(serverMoves, "/%s", buf);
10099         }
10100         fflush(serverMoves);
10101     }
10102
10103     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10104         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10105       return;
10106     }
10107     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10108     if (commentList[forwardMostMove+1] != NULL) {
10109         free(commentList[forwardMostMove+1]);
10110         commentList[forwardMostMove+1] = NULL;
10111     }
10112     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10113     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10114     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10115     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10116     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10117     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10118     adjustedClock = FALSE;
10119     gameInfo.result = GameUnfinished;
10120     if (gameInfo.resultDetails != NULL) {
10121         free(gameInfo.resultDetails);
10122         gameInfo.resultDetails = NULL;
10123     }
10124     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10125                               moveList[forwardMostMove - 1]);
10126     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10127       case MT_NONE:
10128       case MT_STALEMATE:
10129       default:
10130         break;
10131       case MT_CHECK:
10132         if(gameInfo.variant != VariantShogi)
10133             strcat(parseList[forwardMostMove - 1], "+");
10134         break;
10135       case MT_CHECKMATE:
10136       case MT_STAINMATE:
10137         strcat(parseList[forwardMostMove - 1], "#");
10138         break;
10139     }
10140 }
10141
10142 /* Updates currentMove if not pausing */
10143 void
10144 ShowMove (int fromX, int fromY, int toX, int toY)
10145 {
10146     int instant = (gameMode == PlayFromGameFile) ?
10147         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10148     if(appData.noGUI) return;
10149     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10150         if (!instant) {
10151             if (forwardMostMove == currentMove + 1) {
10152                 AnimateMove(boards[forwardMostMove - 1],
10153                             fromX, fromY, toX, toY);
10154             }
10155         }
10156         currentMove = forwardMostMove;
10157     }
10158
10159     killX = killY = -1; // [HGM] lion: used up
10160
10161     if (instant) return;
10162
10163     DisplayMove(currentMove - 1);
10164     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10165             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10166                 SetHighlights(fromX, fromY, toX, toY);
10167             }
10168     }
10169     DrawPosition(FALSE, boards[currentMove]);
10170     DisplayBothClocks();
10171     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10172 }
10173
10174 void
10175 SendEgtPath (ChessProgramState *cps)
10176 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10177         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10178
10179         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10180
10181         while(*p) {
10182             char c, *q = name+1, *r, *s;
10183
10184             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10185             while(*p && *p != ',') *q++ = *p++;
10186             *q++ = ':'; *q = 0;
10187             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10188                 strcmp(name, ",nalimov:") == 0 ) {
10189                 // take nalimov path from the menu-changeable option first, if it is defined
10190               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10191                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10192             } else
10193             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10194                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10195                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10196                 s = r = StrStr(s, ":") + 1; // beginning of path info
10197                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10198                 c = *r; *r = 0;             // temporarily null-terminate path info
10199                     *--q = 0;               // strip of trailig ':' from name
10200                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10201                 *r = c;
10202                 SendToProgram(buf,cps);     // send egtbpath command for this format
10203             }
10204             if(*p == ',') p++; // read away comma to position for next format name
10205         }
10206 }
10207
10208 static int
10209 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10210 {
10211       int width = 8, height = 8, holdings = 0;             // most common sizes
10212       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10213       // correct the deviations default for each variant
10214       if( v == VariantXiangqi ) width = 9,  height = 10;
10215       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10216       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10217       if( v == VariantCapablanca || v == VariantCapaRandom ||
10218           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10219                                 width = 10;
10220       if( v == VariantCourier ) width = 12;
10221       if( v == VariantSuper )                            holdings = 8;
10222       if( v == VariantGreat )   width = 10,              holdings = 8;
10223       if( v == VariantSChess )                           holdings = 7;
10224       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10225       if( v == VariantChuChess) width = 10, height = 10;
10226       if( v == VariantChu )     width = 12, height = 12;
10227       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10228              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10229              holdingsSize >= 0 && holdingsSize != holdings;
10230 }
10231
10232 char variantError[MSG_SIZ];
10233
10234 char *
10235 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10236 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10237       char *p, *variant = VariantName(v);
10238       static char b[MSG_SIZ];
10239       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10240            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10241                                                holdingsSize, variant); // cook up sized variant name
10242            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10243            if(StrStr(list, b) == NULL) {
10244                // specific sized variant not known, check if general sizing allowed
10245                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10246                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10247                             boardWidth, boardHeight, holdingsSize, engine);
10248                    return NULL;
10249                }
10250                /* [HGM] here we really should compare with the maximum supported board size */
10251            }
10252       } else snprintf(b, MSG_SIZ,"%s", variant);
10253       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10254       p = StrStr(list, b);
10255       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10256       if(p == NULL) {
10257           // occurs not at all in list, or only as sub-string
10258           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10259           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10260               int l = strlen(variantError);
10261               char *q;
10262               while(p != list && p[-1] != ',') p--;
10263               q = strchr(p, ',');
10264               if(q) *q = NULLCHAR;
10265               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10266               if(q) *q= ',';
10267           }
10268           return NULL;
10269       }
10270       return b;
10271 }
10272
10273 void
10274 InitChessProgram (ChessProgramState *cps, int setup)
10275 /* setup needed to setup FRC opening position */
10276 {
10277     char buf[MSG_SIZ], *b;
10278     if (appData.noChessProgram) return;
10279     hintRequested = FALSE;
10280     bookRequested = FALSE;
10281
10282     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10283     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10284     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10285     if(cps->memSize) { /* [HGM] memory */
10286       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10287         SendToProgram(buf, cps);
10288     }
10289     SendEgtPath(cps); /* [HGM] EGT */
10290     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10291       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10292         SendToProgram(buf, cps);
10293     }
10294
10295     SendToProgram(cps->initString, cps);
10296     if (gameInfo.variant != VariantNormal &&
10297         gameInfo.variant != VariantLoadable
10298         /* [HGM] also send variant if board size non-standard */
10299         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10300
10301       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10302                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10303       if (b == NULL) {
10304         DisplayFatalError(variantError, 0, 1);
10305         return;
10306       }
10307
10308       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10309       SendToProgram(buf, cps);
10310     }
10311     currentlyInitializedVariant = gameInfo.variant;
10312
10313     /* [HGM] send opening position in FRC to first engine */
10314     if(setup) {
10315           SendToProgram("force\n", cps);
10316           SendBoard(cps, 0);
10317           /* engine is now in force mode! Set flag to wake it up after first move. */
10318           setboardSpoiledMachineBlack = 1;
10319     }
10320
10321     if (cps->sendICS) {
10322       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10323       SendToProgram(buf, cps);
10324     }
10325     cps->maybeThinking = FALSE;
10326     cps->offeredDraw = 0;
10327     if (!appData.icsActive) {
10328         SendTimeControl(cps, movesPerSession, timeControl,
10329                         timeIncrement, appData.searchDepth,
10330                         searchTime);
10331     }
10332     if (appData.showThinking
10333         // [HGM] thinking: four options require thinking output to be sent
10334         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10335                                 ) {
10336         SendToProgram("post\n", cps);
10337     }
10338     SendToProgram("hard\n", cps);
10339     if (!appData.ponderNextMove) {
10340         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10341            it without being sure what state we are in first.  "hard"
10342            is not a toggle, so that one is OK.
10343          */
10344         SendToProgram("easy\n", cps);
10345     }
10346     if (cps->usePing) {
10347       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10348       SendToProgram(buf, cps);
10349     }
10350     cps->initDone = TRUE;
10351     ClearEngineOutputPane(cps == &second);
10352 }
10353
10354
10355 void
10356 ResendOptions (ChessProgramState *cps)
10357 { // send the stored value of the options
10358   int i;
10359   char buf[MSG_SIZ];
10360   Option *opt = cps->option;
10361   for(i=0; i<cps->nrOptions; i++, opt++) {
10362       switch(opt->type) {
10363         case Spin:
10364         case Slider:
10365         case CheckBox:
10366             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10367           break;
10368         case ComboBox:
10369           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10370           break;
10371         default:
10372             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10373           break;
10374         case Button:
10375         case SaveButton:
10376           continue;
10377       }
10378       SendToProgram(buf, cps);
10379   }
10380 }
10381
10382 void
10383 StartChessProgram (ChessProgramState *cps)
10384 {
10385     char buf[MSG_SIZ];
10386     int err;
10387
10388     if (appData.noChessProgram) return;
10389     cps->initDone = FALSE;
10390
10391     if (strcmp(cps->host, "localhost") == 0) {
10392         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10393     } else if (*appData.remoteShell == NULLCHAR) {
10394         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10395     } else {
10396         if (*appData.remoteUser == NULLCHAR) {
10397           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10398                     cps->program);
10399         } else {
10400           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10401                     cps->host, appData.remoteUser, cps->program);
10402         }
10403         err = StartChildProcess(buf, "", &cps->pr);
10404     }
10405
10406     if (err != 0) {
10407       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10408         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10409         if(cps != &first) return;
10410         appData.noChessProgram = TRUE;
10411         ThawUI();
10412         SetNCPMode();
10413 //      DisplayFatalError(buf, err, 1);
10414 //      cps->pr = NoProc;
10415 //      cps->isr = NULL;
10416         return;
10417     }
10418
10419     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10420     if (cps->protocolVersion > 1) {
10421       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10422       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10423         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10424         cps->comboCnt = 0;  //                and values of combo boxes
10425       }
10426       SendToProgram(buf, cps);
10427       if(cps->reload) ResendOptions(cps);
10428     } else {
10429       SendToProgram("xboard\n", cps);
10430     }
10431 }
10432
10433 void
10434 TwoMachinesEventIfReady P((void))
10435 {
10436   static int curMess = 0;
10437   if (first.lastPing != first.lastPong) {
10438     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10439     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10440     return;
10441   }
10442   if (second.lastPing != second.lastPong) {
10443     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10444     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10445     return;
10446   }
10447   DisplayMessage("", ""); curMess = 0;
10448   TwoMachinesEvent();
10449 }
10450
10451 char *
10452 MakeName (char *template)
10453 {
10454     time_t clock;
10455     struct tm *tm;
10456     static char buf[MSG_SIZ];
10457     char *p = buf;
10458     int i;
10459
10460     clock = time((time_t *)NULL);
10461     tm = localtime(&clock);
10462
10463     while(*p++ = *template++) if(p[-1] == '%') {
10464         switch(*template++) {
10465           case 0:   *p = 0; return buf;
10466           case 'Y': i = tm->tm_year+1900; break;
10467           case 'y': i = tm->tm_year-100; break;
10468           case 'M': i = tm->tm_mon+1; break;
10469           case 'd': i = tm->tm_mday; break;
10470           case 'h': i = tm->tm_hour; break;
10471           case 'm': i = tm->tm_min; break;
10472           case 's': i = tm->tm_sec; break;
10473           default:  i = 0;
10474         }
10475         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10476     }
10477     return buf;
10478 }
10479
10480 int
10481 CountPlayers (char *p)
10482 {
10483     int n = 0;
10484     while(p = strchr(p, '\n')) p++, n++; // count participants
10485     return n;
10486 }
10487
10488 FILE *
10489 WriteTourneyFile (char *results, FILE *f)
10490 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10491     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10492     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10493         // create a file with tournament description
10494         fprintf(f, "-participants {%s}\n", appData.participants);
10495         fprintf(f, "-seedBase %d\n", appData.seedBase);
10496         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10497         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10498         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10499         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10500         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10501         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10502         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10503         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10504         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10505         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10506         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10507         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10508         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10509         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10510         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10511         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10512         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10513         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10514         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10515         fprintf(f, "-smpCores %d\n", appData.smpCores);
10516         if(searchTime > 0)
10517                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10518         else {
10519                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10520                 fprintf(f, "-tc %s\n", appData.timeControl);
10521                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10522         }
10523         fprintf(f, "-results \"%s\"\n", results);
10524     }
10525     return f;
10526 }
10527
10528 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10529
10530 void
10531 Substitute (char *participants, int expunge)
10532 {
10533     int i, changed, changes=0, nPlayers=0;
10534     char *p, *q, *r, buf[MSG_SIZ];
10535     if(participants == NULL) return;
10536     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10537     r = p = participants; q = appData.participants;
10538     while(*p && *p == *q) {
10539         if(*p == '\n') r = p+1, nPlayers++;
10540         p++; q++;
10541     }
10542     if(*p) { // difference
10543         while(*p && *p++ != '\n');
10544         while(*q && *q++ != '\n');
10545       changed = nPlayers;
10546         changes = 1 + (strcmp(p, q) != 0);
10547     }
10548     if(changes == 1) { // a single engine mnemonic was changed
10549         q = r; while(*q) nPlayers += (*q++ == '\n');
10550         p = buf; while(*r && (*p = *r++) != '\n') p++;
10551         *p = NULLCHAR;
10552         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10553         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10554         if(mnemonic[i]) { // The substitute is valid
10555             FILE *f;
10556             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10557                 flock(fileno(f), LOCK_EX);
10558                 ParseArgsFromFile(f);
10559                 fseek(f, 0, SEEK_SET);
10560                 FREE(appData.participants); appData.participants = participants;
10561                 if(expunge) { // erase results of replaced engine
10562                     int len = strlen(appData.results), w, b, dummy;
10563                     for(i=0; i<len; i++) {
10564                         Pairing(i, nPlayers, &w, &b, &dummy);
10565                         if((w == changed || b == changed) && appData.results[i] == '*') {
10566                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10567                             fclose(f);
10568                             return;
10569                         }
10570                     }
10571                     for(i=0; i<len; i++) {
10572                         Pairing(i, nPlayers, &w, &b, &dummy);
10573                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10574                     }
10575                 }
10576                 WriteTourneyFile(appData.results, f);
10577                 fclose(f); // release lock
10578                 return;
10579             }
10580         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10581     }
10582     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10583     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10584     free(participants);
10585     return;
10586 }
10587
10588 int
10589 CheckPlayers (char *participants)
10590 {
10591         int i;
10592         char buf[MSG_SIZ], *p;
10593         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10594         while(p = strchr(participants, '\n')) {
10595             *p = NULLCHAR;
10596             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10597             if(!mnemonic[i]) {
10598                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10599                 *p = '\n';
10600                 DisplayError(buf, 0);
10601                 return 1;
10602             }
10603             *p = '\n';
10604             participants = p + 1;
10605         }
10606         return 0;
10607 }
10608
10609 int
10610 CreateTourney (char *name)
10611 {
10612         FILE *f;
10613         if(matchMode && strcmp(name, appData.tourneyFile)) {
10614              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10615         }
10616         if(name[0] == NULLCHAR) {
10617             if(appData.participants[0])
10618                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10619             return 0;
10620         }
10621         f = fopen(name, "r");
10622         if(f) { // file exists
10623             ASSIGN(appData.tourneyFile, name);
10624             ParseArgsFromFile(f); // parse it
10625         } else {
10626             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10627             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10628                 DisplayError(_("Not enough participants"), 0);
10629                 return 0;
10630             }
10631             if(CheckPlayers(appData.participants)) return 0;
10632             ASSIGN(appData.tourneyFile, name);
10633             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10634             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10635         }
10636         fclose(f);
10637         appData.noChessProgram = FALSE;
10638         appData.clockMode = TRUE;
10639         SetGNUMode();
10640         return 1;
10641 }
10642
10643 int
10644 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10645 {
10646     char buf[MSG_SIZ], *p, *q;
10647     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10648     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10649     skip = !all && group[0]; // if group requested, we start in skip mode
10650     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10651         p = names; q = buf; header = 0;
10652         while(*p && *p != '\n') *q++ = *p++;
10653         *q = 0;
10654         if(*p == '\n') p++;
10655         if(buf[0] == '#') {
10656             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10657             depth++; // we must be entering a new group
10658             if(all) continue; // suppress printing group headers when complete list requested
10659             header = 1;
10660             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10661         }
10662         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10663         if(engineList[i]) free(engineList[i]);
10664         engineList[i] = strdup(buf);
10665         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10666         if(engineMnemonic[i]) free(engineMnemonic[i]);
10667         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10668             strcat(buf, " (");
10669             sscanf(q + 8, "%s", buf + strlen(buf));
10670             strcat(buf, ")");
10671         }
10672         engineMnemonic[i] = strdup(buf);
10673         i++;
10674     }
10675     engineList[i] = engineMnemonic[i] = NULL;
10676     return i;
10677 }
10678
10679 // following implemented as macro to avoid type limitations
10680 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10681
10682 void
10683 SwapEngines (int n)
10684 {   // swap settings for first engine and other engine (so far only some selected options)
10685     int h;
10686     char *p;
10687     if(n == 0) return;
10688     SWAP(directory, p)
10689     SWAP(chessProgram, p)
10690     SWAP(isUCI, h)
10691     SWAP(hasOwnBookUCI, h)
10692     SWAP(protocolVersion, h)
10693     SWAP(reuse, h)
10694     SWAP(scoreIsAbsolute, h)
10695     SWAP(timeOdds, h)
10696     SWAP(logo, p)
10697     SWAP(pgnName, p)
10698     SWAP(pvSAN, h)
10699     SWAP(engOptions, p)
10700     SWAP(engInitString, p)
10701     SWAP(computerString, p)
10702     SWAP(features, p)
10703     SWAP(fenOverride, p)
10704     SWAP(NPS, h)
10705     SWAP(accumulateTC, h)
10706     SWAP(host, p)
10707 }
10708
10709 int
10710 GetEngineLine (char *s, int n)
10711 {
10712     int i;
10713     char buf[MSG_SIZ];
10714     extern char *icsNames;
10715     if(!s || !*s) return 0;
10716     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10717     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10718     if(!mnemonic[i]) return 0;
10719     if(n == 11) return 1; // just testing if there was a match
10720     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10721     if(n == 1) SwapEngines(n);
10722     ParseArgsFromString(buf);
10723     if(n == 1) SwapEngines(n);
10724     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10725         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10726         ParseArgsFromString(buf);
10727     }
10728     return 1;
10729 }
10730
10731 int
10732 SetPlayer (int player, char *p)
10733 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10734     int i;
10735     char buf[MSG_SIZ], *engineName;
10736     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10737     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10738     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10739     if(mnemonic[i]) {
10740         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10741         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10742         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10743         ParseArgsFromString(buf);
10744     } else { // no engine with this nickname is installed!
10745         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10746         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10747         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10748         ModeHighlight();
10749         DisplayError(buf, 0);
10750         return 0;
10751     }
10752     free(engineName);
10753     return i;
10754 }
10755
10756 char *recentEngines;
10757
10758 void
10759 RecentEngineEvent (int nr)
10760 {
10761     int n;
10762 //    SwapEngines(1); // bump first to second
10763 //    ReplaceEngine(&second, 1); // and load it there
10764     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10765     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10766     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10767         ReplaceEngine(&first, 0);
10768         FloatToFront(&appData.recentEngineList, command[n]);
10769     }
10770 }
10771
10772 int
10773 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10774 {   // determine players from game number
10775     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10776
10777     if(appData.tourneyType == 0) {
10778         roundsPerCycle = (nPlayers - 1) | 1;
10779         pairingsPerRound = nPlayers / 2;
10780     } else if(appData.tourneyType > 0) {
10781         roundsPerCycle = nPlayers - appData.tourneyType;
10782         pairingsPerRound = appData.tourneyType;
10783     }
10784     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10785     gamesPerCycle = gamesPerRound * roundsPerCycle;
10786     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10787     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10788     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10789     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10790     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10791     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10792
10793     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10794     if(appData.roundSync) *syncInterval = gamesPerRound;
10795
10796     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10797
10798     if(appData.tourneyType == 0) {
10799         if(curPairing == (nPlayers-1)/2 ) {
10800             *whitePlayer = curRound;
10801             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10802         } else {
10803             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10804             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10805             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10806             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10807         }
10808     } else if(appData.tourneyType > 1) {
10809         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10810         *whitePlayer = curRound + appData.tourneyType;
10811     } else if(appData.tourneyType > 0) {
10812         *whitePlayer = curPairing;
10813         *blackPlayer = curRound + appData.tourneyType;
10814     }
10815
10816     // take care of white/black alternation per round.
10817     // For cycles and games this is already taken care of by default, derived from matchGame!
10818     return curRound & 1;
10819 }
10820
10821 int
10822 NextTourneyGame (int nr, int *swapColors)
10823 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10824     char *p, *q;
10825     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10826     FILE *tf;
10827     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10828     tf = fopen(appData.tourneyFile, "r");
10829     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10830     ParseArgsFromFile(tf); fclose(tf);
10831     InitTimeControls(); // TC might be altered from tourney file
10832
10833     nPlayers = CountPlayers(appData.participants); // count participants
10834     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10835     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10836
10837     if(syncInterval) {
10838         p = q = appData.results;
10839         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10840         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10841             DisplayMessage(_("Waiting for other game(s)"),"");
10842             waitingForGame = TRUE;
10843             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10844             return 0;
10845         }
10846         waitingForGame = FALSE;
10847     }
10848
10849     if(appData.tourneyType < 0) {
10850         if(nr>=0 && !pairingReceived) {
10851             char buf[1<<16];
10852             if(pairing.pr == NoProc) {
10853                 if(!appData.pairingEngine[0]) {
10854                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10855                     return 0;
10856                 }
10857                 StartChessProgram(&pairing); // starts the pairing engine
10858             }
10859             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10860             SendToProgram(buf, &pairing);
10861             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10862             SendToProgram(buf, &pairing);
10863             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10864         }
10865         pairingReceived = 0;                              // ... so we continue here
10866         *swapColors = 0;
10867         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10868         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10869         matchGame = 1; roundNr = nr / syncInterval + 1;
10870     }
10871
10872     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10873
10874     // redefine engines, engine dir, etc.
10875     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10876     if(first.pr == NoProc) {
10877       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10878       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10879     }
10880     if(second.pr == NoProc) {
10881       SwapEngines(1);
10882       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10883       SwapEngines(1);         // and make that valid for second engine by swapping
10884       InitEngine(&second, 1);
10885     }
10886     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10887     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10888     return OK;
10889 }
10890
10891 void
10892 NextMatchGame ()
10893 {   // performs game initialization that does not invoke engines, and then tries to start the game
10894     int res, firstWhite, swapColors = 0;
10895     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10896     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
10897         char buf[MSG_SIZ];
10898         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10899         if(strcmp(buf, currentDebugFile)) { // name has changed
10900             FILE *f = fopen(buf, "w");
10901             if(f) { // if opening the new file failed, just keep using the old one
10902                 ASSIGN(currentDebugFile, buf);
10903                 fclose(debugFP);
10904                 debugFP = f;
10905             }
10906             if(appData.serverFileName) {
10907                 if(serverFP) fclose(serverFP);
10908                 serverFP = fopen(appData.serverFileName, "w");
10909                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10910                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10911             }
10912         }
10913     }
10914     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10915     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10916     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10917     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10918     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10919     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10920     Reset(FALSE, first.pr != NoProc);
10921     res = LoadGameOrPosition(matchGame); // setup game
10922     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10923     if(!res) return; // abort when bad game/pos file
10924     TwoMachinesEvent();
10925 }
10926
10927 void
10928 UserAdjudicationEvent (int result)
10929 {
10930     ChessMove gameResult = GameIsDrawn;
10931
10932     if( result > 0 ) {
10933         gameResult = WhiteWins;
10934     }
10935     else if( result < 0 ) {
10936         gameResult = BlackWins;
10937     }
10938
10939     if( gameMode == TwoMachinesPlay ) {
10940         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10941     }
10942 }
10943
10944
10945 // [HGM] save: calculate checksum of game to make games easily identifiable
10946 int
10947 StringCheckSum (char *s)
10948 {
10949         int i = 0;
10950         if(s==NULL) return 0;
10951         while(*s) i = i*259 + *s++;
10952         return i;
10953 }
10954
10955 int
10956 GameCheckSum ()
10957 {
10958         int i, sum=0;
10959         for(i=backwardMostMove; i<forwardMostMove; i++) {
10960                 sum += pvInfoList[i].depth;
10961                 sum += StringCheckSum(parseList[i]);
10962                 sum += StringCheckSum(commentList[i]);
10963                 sum *= 261;
10964         }
10965         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10966         return sum + StringCheckSum(commentList[i]);
10967 } // end of save patch
10968
10969 void
10970 GameEnds (ChessMove result, char *resultDetails, int whosays)
10971 {
10972     GameMode nextGameMode;
10973     int isIcsGame;
10974     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10975
10976     if(endingGame) return; /* [HGM] crash: forbid recursion */
10977     endingGame = 1;
10978     if(twoBoards) { // [HGM] dual: switch back to one board
10979         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10980         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10981     }
10982     if (appData.debugMode) {
10983       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10984               result, resultDetails ? resultDetails : "(null)", whosays);
10985     }
10986
10987     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10988
10989     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10990
10991     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10992         /* If we are playing on ICS, the server decides when the
10993            game is over, but the engine can offer to draw, claim
10994            a draw, or resign.
10995          */
10996 #if ZIPPY
10997         if (appData.zippyPlay && first.initDone) {
10998             if (result == GameIsDrawn) {
10999                 /* In case draw still needs to be claimed */
11000                 SendToICS(ics_prefix);
11001                 SendToICS("draw\n");
11002             } else if (StrCaseStr(resultDetails, "resign")) {
11003                 SendToICS(ics_prefix);
11004                 SendToICS("resign\n");
11005             }
11006         }
11007 #endif
11008         endingGame = 0; /* [HGM] crash */
11009         return;
11010     }
11011
11012     /* If we're loading the game from a file, stop */
11013     if (whosays == GE_FILE) {
11014       (void) StopLoadGameTimer();
11015       gameFileFP = NULL;
11016     }
11017
11018     /* Cancel draw offers */
11019     first.offeredDraw = second.offeredDraw = 0;
11020
11021     /* If this is an ICS game, only ICS can really say it's done;
11022        if not, anyone can. */
11023     isIcsGame = (gameMode == IcsPlayingWhite ||
11024                  gameMode == IcsPlayingBlack ||
11025                  gameMode == IcsObserving    ||
11026                  gameMode == IcsExamining);
11027
11028     if (!isIcsGame || whosays == GE_ICS) {
11029         /* OK -- not an ICS game, or ICS said it was done */
11030         StopClocks();
11031         if (!isIcsGame && !appData.noChessProgram)
11032           SetUserThinkingEnables();
11033
11034         /* [HGM] if a machine claims the game end we verify this claim */
11035         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11036             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11037                 char claimer;
11038                 ChessMove trueResult = (ChessMove) -1;
11039
11040                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11041                                             first.twoMachinesColor[0] :
11042                                             second.twoMachinesColor[0] ;
11043
11044                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11045                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11046                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11047                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11048                 } else
11049                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11050                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11051                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11052                 } else
11053                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11054                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11055                 }
11056
11057                 // now verify win claims, but not in drop games, as we don't understand those yet
11058                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11059                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11060                     (result == WhiteWins && claimer == 'w' ||
11061                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11062                       if (appData.debugMode) {
11063                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11064                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11065                       }
11066                       if(result != trueResult) {
11067                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11068                               result = claimer == 'w' ? BlackWins : WhiteWins;
11069                               resultDetails = buf;
11070                       }
11071                 } else
11072                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11073                     && (forwardMostMove <= backwardMostMove ||
11074                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11075                         (claimer=='b')==(forwardMostMove&1))
11076                                                                                   ) {
11077                       /* [HGM] verify: draws that were not flagged are false claims */
11078                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11079                       result = claimer == 'w' ? BlackWins : WhiteWins;
11080                       resultDetails = buf;
11081                 }
11082                 /* (Claiming a loss is accepted no questions asked!) */
11083             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11084                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11085                 result = GameUnfinished;
11086                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11087             }
11088             /* [HGM] bare: don't allow bare King to win */
11089             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11090                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11091                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11092                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11093                && result != GameIsDrawn)
11094             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11095                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11096                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11097                         if(p >= 0 && p <= (int)WhiteKing) k++;
11098                 }
11099                 if (appData.debugMode) {
11100                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11101                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11102                 }
11103                 if(k <= 1) {
11104                         result = GameIsDrawn;
11105                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11106                         resultDetails = buf;
11107                 }
11108             }
11109         }
11110
11111
11112         if(serverMoves != NULL && !loadFlag) { char c = '=';
11113             if(result==WhiteWins) c = '+';
11114             if(result==BlackWins) c = '-';
11115             if(resultDetails != NULL)
11116                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11117         }
11118         if (resultDetails != NULL) {
11119             gameInfo.result = result;
11120             gameInfo.resultDetails = StrSave(resultDetails);
11121
11122             /* display last move only if game was not loaded from file */
11123             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11124                 DisplayMove(currentMove - 1);
11125
11126             if (forwardMostMove != 0) {
11127                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11128                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11129                                                                 ) {
11130                     if (*appData.saveGameFile != NULLCHAR) {
11131                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11132                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11133                         else
11134                         SaveGameToFile(appData.saveGameFile, TRUE);
11135                     } else if (appData.autoSaveGames) {
11136                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11137                     }
11138                     if (*appData.savePositionFile != NULLCHAR) {
11139                         SavePositionToFile(appData.savePositionFile);
11140                     }
11141                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11142                 }
11143             }
11144
11145             /* Tell program how game ended in case it is learning */
11146             /* [HGM] Moved this to after saving the PGN, just in case */
11147             /* engine died and we got here through time loss. In that */
11148             /* case we will get a fatal error writing the pipe, which */
11149             /* would otherwise lose us the PGN.                       */
11150             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11151             /* output during GameEnds should never be fatal anymore   */
11152             if (gameMode == MachinePlaysWhite ||
11153                 gameMode == MachinePlaysBlack ||
11154                 gameMode == TwoMachinesPlay ||
11155                 gameMode == IcsPlayingWhite ||
11156                 gameMode == IcsPlayingBlack ||
11157                 gameMode == BeginningOfGame) {
11158                 char buf[MSG_SIZ];
11159                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11160                         resultDetails);
11161                 if (first.pr != NoProc) {
11162                     SendToProgram(buf, &first);
11163                 }
11164                 if (second.pr != NoProc &&
11165                     gameMode == TwoMachinesPlay) {
11166                     SendToProgram(buf, &second);
11167                 }
11168             }
11169         }
11170
11171         if (appData.icsActive) {
11172             if (appData.quietPlay &&
11173                 (gameMode == IcsPlayingWhite ||
11174                  gameMode == IcsPlayingBlack)) {
11175                 SendToICS(ics_prefix);
11176                 SendToICS("set shout 1\n");
11177             }
11178             nextGameMode = IcsIdle;
11179             ics_user_moved = FALSE;
11180             /* clean up premove.  It's ugly when the game has ended and the
11181              * premove highlights are still on the board.
11182              */
11183             if (gotPremove) {
11184               gotPremove = FALSE;
11185               ClearPremoveHighlights();
11186               DrawPosition(FALSE, boards[currentMove]);
11187             }
11188             if (whosays == GE_ICS) {
11189                 switch (result) {
11190                 case WhiteWins:
11191                     if (gameMode == IcsPlayingWhite)
11192                         PlayIcsWinSound();
11193                     else if(gameMode == IcsPlayingBlack)
11194                         PlayIcsLossSound();
11195                     break;
11196                 case BlackWins:
11197                     if (gameMode == IcsPlayingBlack)
11198                         PlayIcsWinSound();
11199                     else if(gameMode == IcsPlayingWhite)
11200                         PlayIcsLossSound();
11201                     break;
11202                 case GameIsDrawn:
11203                     PlayIcsDrawSound();
11204                     break;
11205                 default:
11206                     PlayIcsUnfinishedSound();
11207                 }
11208             }
11209             if(appData.quitNext) { ExitEvent(0); return; }
11210         } else if (gameMode == EditGame ||
11211                    gameMode == PlayFromGameFile ||
11212                    gameMode == AnalyzeMode ||
11213                    gameMode == AnalyzeFile) {
11214             nextGameMode = gameMode;
11215         } else {
11216             nextGameMode = EndOfGame;
11217         }
11218         pausing = FALSE;
11219         ModeHighlight();
11220     } else {
11221         nextGameMode = gameMode;
11222     }
11223
11224     if (appData.noChessProgram) {
11225         gameMode = nextGameMode;
11226         ModeHighlight();
11227         endingGame = 0; /* [HGM] crash */
11228         return;
11229     }
11230
11231     if (first.reuse) {
11232         /* Put first chess program into idle state */
11233         if (first.pr != NoProc &&
11234             (gameMode == MachinePlaysWhite ||
11235              gameMode == MachinePlaysBlack ||
11236              gameMode == TwoMachinesPlay ||
11237              gameMode == IcsPlayingWhite ||
11238              gameMode == IcsPlayingBlack ||
11239              gameMode == BeginningOfGame)) {
11240             SendToProgram("force\n", &first);
11241             if (first.usePing) {
11242               char buf[MSG_SIZ];
11243               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11244               SendToProgram(buf, &first);
11245             }
11246         }
11247     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11248         /* Kill off first chess program */
11249         if (first.isr != NULL)
11250           RemoveInputSource(first.isr);
11251         first.isr = NULL;
11252
11253         if (first.pr != NoProc) {
11254             ExitAnalyzeMode();
11255             DoSleep( appData.delayBeforeQuit );
11256             SendToProgram("quit\n", &first);
11257             DoSleep( appData.delayAfterQuit );
11258             DestroyChildProcess(first.pr, first.useSigterm);
11259             first.reload = TRUE;
11260         }
11261         first.pr = NoProc;
11262     }
11263     if (second.reuse) {
11264         /* Put second chess program into idle state */
11265         if (second.pr != NoProc &&
11266             gameMode == TwoMachinesPlay) {
11267             SendToProgram("force\n", &second);
11268             if (second.usePing) {
11269               char buf[MSG_SIZ];
11270               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11271               SendToProgram(buf, &second);
11272             }
11273         }
11274     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11275         /* Kill off second chess program */
11276         if (second.isr != NULL)
11277           RemoveInputSource(second.isr);
11278         second.isr = NULL;
11279
11280         if (second.pr != NoProc) {
11281             DoSleep( appData.delayBeforeQuit );
11282             SendToProgram("quit\n", &second);
11283             DoSleep( appData.delayAfterQuit );
11284             DestroyChildProcess(second.pr, second.useSigterm);
11285             second.reload = TRUE;
11286         }
11287         second.pr = NoProc;
11288     }
11289
11290     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11291         char resChar = '=';
11292         switch (result) {
11293         case WhiteWins:
11294           resChar = '+';
11295           if (first.twoMachinesColor[0] == 'w') {
11296             first.matchWins++;
11297           } else {
11298             second.matchWins++;
11299           }
11300           break;
11301         case BlackWins:
11302           resChar = '-';
11303           if (first.twoMachinesColor[0] == 'b') {
11304             first.matchWins++;
11305           } else {
11306             second.matchWins++;
11307           }
11308           break;
11309         case GameUnfinished:
11310           resChar = ' ';
11311         default:
11312           break;
11313         }
11314
11315         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11316         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11317             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11318             ReserveGame(nextGame, resChar); // sets nextGame
11319             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11320             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11321         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11322
11323         if (nextGame <= appData.matchGames && !abortMatch) {
11324             gameMode = nextGameMode;
11325             matchGame = nextGame; // this will be overruled in tourney mode!
11326             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11327             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11328             endingGame = 0; /* [HGM] crash */
11329             return;
11330         } else {
11331             gameMode = nextGameMode;
11332             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11333                      first.tidy, second.tidy,
11334                      first.matchWins, second.matchWins,
11335                      appData.matchGames - (first.matchWins + second.matchWins));
11336             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11337             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11338             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11339             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11340                 first.twoMachinesColor = "black\n";
11341                 second.twoMachinesColor = "white\n";
11342             } else {
11343                 first.twoMachinesColor = "white\n";
11344                 second.twoMachinesColor = "black\n";
11345             }
11346         }
11347     }
11348     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11349         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11350       ExitAnalyzeMode();
11351     gameMode = nextGameMode;
11352     ModeHighlight();
11353     endingGame = 0;  /* [HGM] crash */
11354     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11355         if(matchMode == TRUE) { // match through command line: exit with or without popup
11356             if(ranking) {
11357                 ToNrEvent(forwardMostMove);
11358                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11359                 else ExitEvent(0);
11360             } else DisplayFatalError(buf, 0, 0);
11361         } else { // match through menu; just stop, with or without popup
11362             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11363             ModeHighlight();
11364             if(ranking){
11365                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11366             } else DisplayNote(buf);
11367       }
11368       if(ranking) free(ranking);
11369     }
11370 }
11371
11372 /* Assumes program was just initialized (initString sent).
11373    Leaves program in force mode. */
11374 void
11375 FeedMovesToProgram (ChessProgramState *cps, int upto)
11376 {
11377     int i;
11378
11379     if (appData.debugMode)
11380       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11381               startedFromSetupPosition ? "position and " : "",
11382               backwardMostMove, upto, cps->which);
11383     if(currentlyInitializedVariant != gameInfo.variant) {
11384       char buf[MSG_SIZ];
11385         // [HGM] variantswitch: make engine aware of new variant
11386         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11387                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11388                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11389         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11390         SendToProgram(buf, cps);
11391         currentlyInitializedVariant = gameInfo.variant;
11392     }
11393     SendToProgram("force\n", cps);
11394     if (startedFromSetupPosition) {
11395         SendBoard(cps, backwardMostMove);
11396     if (appData.debugMode) {
11397         fprintf(debugFP, "feedMoves\n");
11398     }
11399     }
11400     for (i = backwardMostMove; i < upto; i++) {
11401         SendMoveToProgram(i, cps);
11402     }
11403 }
11404
11405
11406 int
11407 ResurrectChessProgram ()
11408 {
11409      /* The chess program may have exited.
11410         If so, restart it and feed it all the moves made so far. */
11411     static int doInit = 0;
11412
11413     if (appData.noChessProgram) return 1;
11414
11415     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11416         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11417         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11418         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11419     } else {
11420         if (first.pr != NoProc) return 1;
11421         StartChessProgram(&first);
11422     }
11423     InitChessProgram(&first, FALSE);
11424     FeedMovesToProgram(&first, currentMove);
11425
11426     if (!first.sendTime) {
11427         /* can't tell gnuchess what its clock should read,
11428            so we bow to its notion. */
11429         ResetClocks();
11430         timeRemaining[0][currentMove] = whiteTimeRemaining;
11431         timeRemaining[1][currentMove] = blackTimeRemaining;
11432     }
11433
11434     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11435                 appData.icsEngineAnalyze) && first.analysisSupport) {
11436       SendToProgram("analyze\n", &first);
11437       first.analyzing = TRUE;
11438     }
11439     return 1;
11440 }
11441
11442 /*
11443  * Button procedures
11444  */
11445 void
11446 Reset (int redraw, int init)
11447 {
11448     int i;
11449
11450     if (appData.debugMode) {
11451         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11452                 redraw, init, gameMode);
11453     }
11454     CleanupTail(); // [HGM] vari: delete any stored variations
11455     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11456     pausing = pauseExamInvalid = FALSE;
11457     startedFromSetupPosition = blackPlaysFirst = FALSE;
11458     firstMove = TRUE;
11459     whiteFlag = blackFlag = FALSE;
11460     userOfferedDraw = FALSE;
11461     hintRequested = bookRequested = FALSE;
11462     first.maybeThinking = FALSE;
11463     second.maybeThinking = FALSE;
11464     first.bookSuspend = FALSE; // [HGM] book
11465     second.bookSuspend = FALSE;
11466     thinkOutput[0] = NULLCHAR;
11467     lastHint[0] = NULLCHAR;
11468     ClearGameInfo(&gameInfo);
11469     gameInfo.variant = StringToVariant(appData.variant);
11470     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11471     ics_user_moved = ics_clock_paused = FALSE;
11472     ics_getting_history = H_FALSE;
11473     ics_gamenum = -1;
11474     white_holding[0] = black_holding[0] = NULLCHAR;
11475     ClearProgramStats();
11476     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11477
11478     ResetFrontEnd();
11479     ClearHighlights();
11480     flipView = appData.flipView;
11481     ClearPremoveHighlights();
11482     gotPremove = FALSE;
11483     alarmSounded = FALSE;
11484     killX = killY = -1; // [HGM] lion
11485
11486     GameEnds(EndOfFile, NULL, GE_PLAYER);
11487     if(appData.serverMovesName != NULL) {
11488         /* [HGM] prepare to make moves file for broadcasting */
11489         clock_t t = clock();
11490         if(serverMoves != NULL) fclose(serverMoves);
11491         serverMoves = fopen(appData.serverMovesName, "r");
11492         if(serverMoves != NULL) {
11493             fclose(serverMoves);
11494             /* delay 15 sec before overwriting, so all clients can see end */
11495             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11496         }
11497         serverMoves = fopen(appData.serverMovesName, "w");
11498     }
11499
11500     ExitAnalyzeMode();
11501     gameMode = BeginningOfGame;
11502     ModeHighlight();
11503     if(appData.icsActive) gameInfo.variant = VariantNormal;
11504     currentMove = forwardMostMove = backwardMostMove = 0;
11505     MarkTargetSquares(1);
11506     InitPosition(redraw);
11507     for (i = 0; i < MAX_MOVES; i++) {
11508         if (commentList[i] != NULL) {
11509             free(commentList[i]);
11510             commentList[i] = NULL;
11511         }
11512     }
11513     ResetClocks();
11514     timeRemaining[0][0] = whiteTimeRemaining;
11515     timeRemaining[1][0] = blackTimeRemaining;
11516
11517     if (first.pr == NoProc) {
11518         StartChessProgram(&first);
11519     }
11520     if (init) {
11521             InitChessProgram(&first, startedFromSetupPosition);
11522     }
11523     DisplayTitle("");
11524     DisplayMessage("", "");
11525     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11526     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11527     ClearMap();        // [HGM] exclude: invalidate map
11528 }
11529
11530 void
11531 AutoPlayGameLoop ()
11532 {
11533     for (;;) {
11534         if (!AutoPlayOneMove())
11535           return;
11536         if (matchMode || appData.timeDelay == 0)
11537           continue;
11538         if (appData.timeDelay < 0)
11539           return;
11540         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11541         break;
11542     }
11543 }
11544
11545 void
11546 AnalyzeNextGame()
11547 {
11548     ReloadGame(1); // next game
11549 }
11550
11551 int
11552 AutoPlayOneMove ()
11553 {
11554     int fromX, fromY, toX, toY;
11555
11556     if (appData.debugMode) {
11557       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11558     }
11559
11560     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11561       return FALSE;
11562
11563     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11564       pvInfoList[currentMove].depth = programStats.depth;
11565       pvInfoList[currentMove].score = programStats.score;
11566       pvInfoList[currentMove].time  = 0;
11567       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11568       else { // append analysis of final position as comment
11569         char buf[MSG_SIZ];
11570         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11571         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11572       }
11573       programStats.depth = 0;
11574     }
11575
11576     if (currentMove >= forwardMostMove) {
11577       if(gameMode == AnalyzeFile) {
11578           if(appData.loadGameIndex == -1) {
11579             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11580           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11581           } else {
11582           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11583         }
11584       }
11585 //      gameMode = EndOfGame;
11586 //      ModeHighlight();
11587
11588       /* [AS] Clear current move marker at the end of a game */
11589       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11590
11591       return FALSE;
11592     }
11593
11594     toX = moveList[currentMove][2] - AAA;
11595     toY = moveList[currentMove][3] - ONE;
11596
11597     if (moveList[currentMove][1] == '@') {
11598         if (appData.highlightLastMove) {
11599             SetHighlights(-1, -1, toX, toY);
11600         }
11601     } else {
11602         fromX = moveList[currentMove][0] - AAA;
11603         fromY = moveList[currentMove][1] - ONE;
11604
11605         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11606
11607         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11608
11609         if (appData.highlightLastMove) {
11610             SetHighlights(fromX, fromY, toX, toY);
11611         }
11612     }
11613     DisplayMove(currentMove);
11614     SendMoveToProgram(currentMove++, &first);
11615     DisplayBothClocks();
11616     DrawPosition(FALSE, boards[currentMove]);
11617     // [HGM] PV info: always display, routine tests if empty
11618     DisplayComment(currentMove - 1, commentList[currentMove]);
11619     return TRUE;
11620 }
11621
11622
11623 int
11624 LoadGameOneMove (ChessMove readAhead)
11625 {
11626     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11627     char promoChar = NULLCHAR;
11628     ChessMove moveType;
11629     char move[MSG_SIZ];
11630     char *p, *q;
11631
11632     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11633         gameMode != AnalyzeMode && gameMode != Training) {
11634         gameFileFP = NULL;
11635         return FALSE;
11636     }
11637
11638     yyboardindex = forwardMostMove;
11639     if (readAhead != EndOfFile) {
11640       moveType = readAhead;
11641     } else {
11642       if (gameFileFP == NULL)
11643           return FALSE;
11644       moveType = (ChessMove) Myylex();
11645     }
11646
11647     done = FALSE;
11648     switch (moveType) {
11649       case Comment:
11650         if (appData.debugMode)
11651           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11652         p = yy_text;
11653
11654         /* append the comment but don't display it */
11655         AppendComment(currentMove, p, FALSE);
11656         return TRUE;
11657
11658       case WhiteCapturesEnPassant:
11659       case BlackCapturesEnPassant:
11660       case WhitePromotion:
11661       case BlackPromotion:
11662       case WhiteNonPromotion:
11663       case BlackNonPromotion:
11664       case NormalMove:
11665       case FirstLeg:
11666       case WhiteKingSideCastle:
11667       case WhiteQueenSideCastle:
11668       case BlackKingSideCastle:
11669       case BlackQueenSideCastle:
11670       case WhiteKingSideCastleWild:
11671       case WhiteQueenSideCastleWild:
11672       case BlackKingSideCastleWild:
11673       case BlackQueenSideCastleWild:
11674       /* PUSH Fabien */
11675       case WhiteHSideCastleFR:
11676       case WhiteASideCastleFR:
11677       case BlackHSideCastleFR:
11678       case BlackASideCastleFR:
11679       /* POP Fabien */
11680         if (appData.debugMode)
11681           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11682         fromX = currentMoveString[0] - AAA;
11683         fromY = currentMoveString[1] - ONE;
11684         toX = currentMoveString[2] - AAA;
11685         toY = currentMoveString[3] - ONE;
11686         promoChar = currentMoveString[4];
11687         if(promoChar == ';') promoChar = NULLCHAR;
11688         break;
11689
11690       case WhiteDrop:
11691       case BlackDrop:
11692         if (appData.debugMode)
11693           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11694         fromX = moveType == WhiteDrop ?
11695           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11696         (int) CharToPiece(ToLower(currentMoveString[0]));
11697         fromY = DROP_RANK;
11698         toX = currentMoveString[2] - AAA;
11699         toY = currentMoveString[3] - ONE;
11700         break;
11701
11702       case WhiteWins:
11703       case BlackWins:
11704       case GameIsDrawn:
11705       case GameUnfinished:
11706         if (appData.debugMode)
11707           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11708         p = strchr(yy_text, '{');
11709         if (p == NULL) p = strchr(yy_text, '(');
11710         if (p == NULL) {
11711             p = yy_text;
11712             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11713         } else {
11714             q = strchr(p, *p == '{' ? '}' : ')');
11715             if (q != NULL) *q = NULLCHAR;
11716             p++;
11717         }
11718         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11719         GameEnds(moveType, p, GE_FILE);
11720         done = TRUE;
11721         if (cmailMsgLoaded) {
11722             ClearHighlights();
11723             flipView = WhiteOnMove(currentMove);
11724             if (moveType == GameUnfinished) flipView = !flipView;
11725             if (appData.debugMode)
11726               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11727         }
11728         break;
11729
11730       case EndOfFile:
11731         if (appData.debugMode)
11732           fprintf(debugFP, "Parser hit end of file\n");
11733         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11734           case MT_NONE:
11735           case MT_CHECK:
11736             break;
11737           case MT_CHECKMATE:
11738           case MT_STAINMATE:
11739             if (WhiteOnMove(currentMove)) {
11740                 GameEnds(BlackWins, "Black mates", GE_FILE);
11741             } else {
11742                 GameEnds(WhiteWins, "White mates", GE_FILE);
11743             }
11744             break;
11745           case MT_STALEMATE:
11746             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11747             break;
11748         }
11749         done = TRUE;
11750         break;
11751
11752       case MoveNumberOne:
11753         if (lastLoadGameStart == GNUChessGame) {
11754             /* GNUChessGames have numbers, but they aren't move numbers */
11755             if (appData.debugMode)
11756               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11757                       yy_text, (int) moveType);
11758             return LoadGameOneMove(EndOfFile); /* tail recursion */
11759         }
11760         /* else fall thru */
11761
11762       case XBoardGame:
11763       case GNUChessGame:
11764       case PGNTag:
11765         /* Reached start of next game in file */
11766         if (appData.debugMode)
11767           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11768         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11769           case MT_NONE:
11770           case MT_CHECK:
11771             break;
11772           case MT_CHECKMATE:
11773           case MT_STAINMATE:
11774             if (WhiteOnMove(currentMove)) {
11775                 GameEnds(BlackWins, "Black mates", GE_FILE);
11776             } else {
11777                 GameEnds(WhiteWins, "White mates", GE_FILE);
11778             }
11779             break;
11780           case MT_STALEMATE:
11781             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11782             break;
11783         }
11784         done = TRUE;
11785         break;
11786
11787       case PositionDiagram:     /* should not happen; ignore */
11788       case ElapsedTime:         /* ignore */
11789       case NAG:                 /* ignore */
11790         if (appData.debugMode)
11791           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11792                   yy_text, (int) moveType);
11793         return LoadGameOneMove(EndOfFile); /* tail recursion */
11794
11795       case IllegalMove:
11796         if (appData.testLegality) {
11797             if (appData.debugMode)
11798               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11799             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11800                     (forwardMostMove / 2) + 1,
11801                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11802             DisplayError(move, 0);
11803             done = TRUE;
11804         } else {
11805             if (appData.debugMode)
11806               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11807                       yy_text, currentMoveString);
11808             fromX = currentMoveString[0] - AAA;
11809             fromY = currentMoveString[1] - ONE;
11810             toX = currentMoveString[2] - AAA;
11811             toY = currentMoveString[3] - ONE;
11812             promoChar = currentMoveString[4];
11813         }
11814         break;
11815
11816       case AmbiguousMove:
11817         if (appData.debugMode)
11818           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11819         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11820                 (forwardMostMove / 2) + 1,
11821                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11822         DisplayError(move, 0);
11823         done = TRUE;
11824         break;
11825
11826       default:
11827       case ImpossibleMove:
11828         if (appData.debugMode)
11829           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11830         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11831                 (forwardMostMove / 2) + 1,
11832                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11833         DisplayError(move, 0);
11834         done = TRUE;
11835         break;
11836     }
11837
11838     if (done) {
11839         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11840             DrawPosition(FALSE, boards[currentMove]);
11841             DisplayBothClocks();
11842             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11843               DisplayComment(currentMove - 1, commentList[currentMove]);
11844         }
11845         (void) StopLoadGameTimer();
11846         gameFileFP = NULL;
11847         cmailOldMove = forwardMostMove;
11848         return FALSE;
11849     } else {
11850         /* currentMoveString is set as a side-effect of yylex */
11851
11852         thinkOutput[0] = NULLCHAR;
11853         MakeMove(fromX, fromY, toX, toY, promoChar);
11854         killX = killY = -1; // [HGM] lion: used up
11855         currentMove = forwardMostMove;
11856         return TRUE;
11857     }
11858 }
11859
11860 /* Load the nth game from the given file */
11861 int
11862 LoadGameFromFile (char *filename, int n, char *title, int useList)
11863 {
11864     FILE *f;
11865     char buf[MSG_SIZ];
11866
11867     if (strcmp(filename, "-") == 0) {
11868         f = stdin;
11869         title = "stdin";
11870     } else {
11871         f = fopen(filename, "rb");
11872         if (f == NULL) {
11873           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11874             DisplayError(buf, errno);
11875             return FALSE;
11876         }
11877     }
11878     if (fseek(f, 0, 0) == -1) {
11879         /* f is not seekable; probably a pipe */
11880         useList = FALSE;
11881     }
11882     if (useList && n == 0) {
11883         int error = GameListBuild(f);
11884         if (error) {
11885             DisplayError(_("Cannot build game list"), error);
11886         } else if (!ListEmpty(&gameList) &&
11887                    ((ListGame *) gameList.tailPred)->number > 1) {
11888             GameListPopUp(f, title);
11889             return TRUE;
11890         }
11891         GameListDestroy();
11892         n = 1;
11893     }
11894     if (n == 0) n = 1;
11895     return LoadGame(f, n, title, FALSE);
11896 }
11897
11898
11899 void
11900 MakeRegisteredMove ()
11901 {
11902     int fromX, fromY, toX, toY;
11903     char promoChar;
11904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11905         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11906           case CMAIL_MOVE:
11907           case CMAIL_DRAW:
11908             if (appData.debugMode)
11909               fprintf(debugFP, "Restoring %s for game %d\n",
11910                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11911
11912             thinkOutput[0] = NULLCHAR;
11913             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11914             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11915             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11916             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11917             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11918             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11919             MakeMove(fromX, fromY, toX, toY, promoChar);
11920             ShowMove(fromX, fromY, toX, toY);
11921
11922             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11923               case MT_NONE:
11924               case MT_CHECK:
11925                 break;
11926
11927               case MT_CHECKMATE:
11928               case MT_STAINMATE:
11929                 if (WhiteOnMove(currentMove)) {
11930                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11931                 } else {
11932                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11933                 }
11934                 break;
11935
11936               case MT_STALEMATE:
11937                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11938                 break;
11939             }
11940
11941             break;
11942
11943           case CMAIL_RESIGN:
11944             if (WhiteOnMove(currentMove)) {
11945                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11946             } else {
11947                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11948             }
11949             break;
11950
11951           case CMAIL_ACCEPT:
11952             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11953             break;
11954
11955           default:
11956             break;
11957         }
11958     }
11959
11960     return;
11961 }
11962
11963 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11964 int
11965 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11966 {
11967     int retVal;
11968
11969     if (gameNumber > nCmailGames) {
11970         DisplayError(_("No more games in this message"), 0);
11971         return FALSE;
11972     }
11973     if (f == lastLoadGameFP) {
11974         int offset = gameNumber - lastLoadGameNumber;
11975         if (offset == 0) {
11976             cmailMsg[0] = NULLCHAR;
11977             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11978                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11979                 nCmailMovesRegistered--;
11980             }
11981             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11982             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11983                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11984             }
11985         } else {
11986             if (! RegisterMove()) return FALSE;
11987         }
11988     }
11989
11990     retVal = LoadGame(f, gameNumber, title, useList);
11991
11992     /* Make move registered during previous look at this game, if any */
11993     MakeRegisteredMove();
11994
11995     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11996         commentList[currentMove]
11997           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11998         DisplayComment(currentMove - 1, commentList[currentMove]);
11999     }
12000
12001     return retVal;
12002 }
12003
12004 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12005 int
12006 ReloadGame (int offset)
12007 {
12008     int gameNumber = lastLoadGameNumber + offset;
12009     if (lastLoadGameFP == NULL) {
12010         DisplayError(_("No game has been loaded yet"), 0);
12011         return FALSE;
12012     }
12013     if (gameNumber <= 0) {
12014         DisplayError(_("Can't back up any further"), 0);
12015         return FALSE;
12016     }
12017     if (cmailMsgLoaded) {
12018         return CmailLoadGame(lastLoadGameFP, gameNumber,
12019                              lastLoadGameTitle, lastLoadGameUseList);
12020     } else {
12021         return LoadGame(lastLoadGameFP, gameNumber,
12022                         lastLoadGameTitle, lastLoadGameUseList);
12023     }
12024 }
12025
12026 int keys[EmptySquare+1];
12027
12028 int
12029 PositionMatches (Board b1, Board b2)
12030 {
12031     int r, f, sum=0;
12032     switch(appData.searchMode) {
12033         case 1: return CompareWithRights(b1, b2);
12034         case 2:
12035             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12036                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12037             }
12038             return TRUE;
12039         case 3:
12040             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12041               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12042                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12043             }
12044             return sum==0;
12045         case 4:
12046             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12047                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12048             }
12049             return sum==0;
12050     }
12051     return TRUE;
12052 }
12053
12054 #define Q_PROMO  4
12055 #define Q_EP     3
12056 #define Q_BCASTL 2
12057 #define Q_WCASTL 1
12058
12059 int pieceList[256], quickBoard[256];
12060 ChessSquare pieceType[256] = { EmptySquare };
12061 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12062 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12063 int soughtTotal, turn;
12064 Boolean epOK, flipSearch;
12065
12066 typedef struct {
12067     unsigned char piece, to;
12068 } Move;
12069
12070 #define DSIZE (250000)
12071
12072 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12073 Move *moveDatabase = initialSpace;
12074 unsigned int movePtr, dataSize = DSIZE;
12075
12076 int
12077 MakePieceList (Board board, int *counts)
12078 {
12079     int r, f, n=Q_PROMO, total=0;
12080     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12081     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12082         int sq = f + (r<<4);
12083         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12084             quickBoard[sq] = ++n;
12085             pieceList[n] = sq;
12086             pieceType[n] = board[r][f];
12087             counts[board[r][f]]++;
12088             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12089             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12090             total++;
12091         }
12092     }
12093     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12094     return total;
12095 }
12096
12097 void
12098 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12099 {
12100     int sq = fromX + (fromY<<4);
12101     int piece = quickBoard[sq];
12102     quickBoard[sq] = 0;
12103     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12104     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12105         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12106         moveDatabase[movePtr++].piece = Q_WCASTL;
12107         quickBoard[sq] = piece;
12108         piece = quickBoard[from]; quickBoard[from] = 0;
12109         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12110     } else
12111     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12112         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12113         moveDatabase[movePtr++].piece = Q_BCASTL;
12114         quickBoard[sq] = piece;
12115         piece = quickBoard[from]; quickBoard[from] = 0;
12116         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12117     } else
12118     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12119         quickBoard[(fromY<<4)+toX] = 0;
12120         moveDatabase[movePtr].piece = Q_EP;
12121         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12122         moveDatabase[movePtr].to = sq;
12123     } else
12124     if(promoPiece != pieceType[piece]) {
12125         moveDatabase[movePtr++].piece = Q_PROMO;
12126         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12127     }
12128     moveDatabase[movePtr].piece = piece;
12129     quickBoard[sq] = piece;
12130     movePtr++;
12131 }
12132
12133 int
12134 PackGame (Board board)
12135 {
12136     Move *newSpace = NULL;
12137     moveDatabase[movePtr].piece = 0; // terminate previous game
12138     if(movePtr > dataSize) {
12139         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12140         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12141         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12142         if(newSpace) {
12143             int i;
12144             Move *p = moveDatabase, *q = newSpace;
12145             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12146             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12147             moveDatabase = newSpace;
12148         } else { // calloc failed, we must be out of memory. Too bad...
12149             dataSize = 0; // prevent calloc events for all subsequent games
12150             return 0;     // and signal this one isn't cached
12151         }
12152     }
12153     movePtr++;
12154     MakePieceList(board, counts);
12155     return movePtr;
12156 }
12157
12158 int
12159 QuickCompare (Board board, int *minCounts, int *maxCounts)
12160 {   // compare according to search mode
12161     int r, f;
12162     switch(appData.searchMode)
12163     {
12164       case 1: // exact position match
12165         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12166         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12167             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12168         }
12169         break;
12170       case 2: // can have extra material on empty squares
12171         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12172             if(board[r][f] == EmptySquare) continue;
12173             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12174         }
12175         break;
12176       case 3: // material with exact Pawn structure
12177         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12178             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12179             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12180         } // fall through to material comparison
12181       case 4: // exact material
12182         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12183         break;
12184       case 6: // material range with given imbalance
12185         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12186         // fall through to range comparison
12187       case 5: // material range
12188         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12189     }
12190     return TRUE;
12191 }
12192
12193 int
12194 QuickScan (Board board, Move *move)
12195 {   // reconstruct game,and compare all positions in it
12196     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12197     do {
12198         int piece = move->piece;
12199         int to = move->to, from = pieceList[piece];
12200         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12201           if(!piece) return -1;
12202           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12203             piece = (++move)->piece;
12204             from = pieceList[piece];
12205             counts[pieceType[piece]]--;
12206             pieceType[piece] = (ChessSquare) move->to;
12207             counts[move->to]++;
12208           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12209             counts[pieceType[quickBoard[to]]]--;
12210             quickBoard[to] = 0; total--;
12211             move++;
12212             continue;
12213           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12214             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12215             from  = pieceList[piece]; // so this must be King
12216             quickBoard[from] = 0;
12217             pieceList[piece] = to;
12218             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12219             quickBoard[from] = 0; // rook
12220             quickBoard[to] = piece;
12221             to = move->to; piece = move->piece;
12222             goto aftercastle;
12223           }
12224         }
12225         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12226         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12227         quickBoard[from] = 0;
12228       aftercastle:
12229         quickBoard[to] = piece;
12230         pieceList[piece] = to;
12231         cnt++; turn ^= 3;
12232         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12233            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12234            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12235                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12236           ) {
12237             static int lastCounts[EmptySquare+1];
12238             int i;
12239             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12240             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12241         } else stretch = 0;
12242         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12243         move++;
12244     } while(1);
12245 }
12246
12247 void
12248 InitSearch ()
12249 {
12250     int r, f;
12251     flipSearch = FALSE;
12252     CopyBoard(soughtBoard, boards[currentMove]);
12253     soughtTotal = MakePieceList(soughtBoard, maxSought);
12254     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12255     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12256     CopyBoard(reverseBoard, boards[currentMove]);
12257     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12258         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12259         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12260         reverseBoard[r][f] = piece;
12261     }
12262     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12263     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12264     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12265                  || (boards[currentMove][CASTLING][2] == NoRights ||
12266                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12267                  && (boards[currentMove][CASTLING][5] == NoRights ||
12268                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12269       ) {
12270         flipSearch = TRUE;
12271         CopyBoard(flipBoard, soughtBoard);
12272         CopyBoard(rotateBoard, reverseBoard);
12273         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12274             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12275             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12276         }
12277     }
12278     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12279     if(appData.searchMode >= 5) {
12280         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12281         MakePieceList(soughtBoard, minSought);
12282         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12283     }
12284     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12285         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12286 }
12287
12288 GameInfo dummyInfo;
12289 static int creatingBook;
12290
12291 int
12292 GameContainsPosition (FILE *f, ListGame *lg)
12293 {
12294     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12295     int fromX, fromY, toX, toY;
12296     char promoChar;
12297     static int initDone=FALSE;
12298
12299     // weed out games based on numerical tag comparison
12300     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12301     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12302     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12303     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12304     if(!initDone) {
12305         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12306         initDone = TRUE;
12307     }
12308     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12309     else CopyBoard(boards[scratch], initialPosition); // default start position
12310     if(lg->moves) {
12311         turn = btm + 1;
12312         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12313         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12314     }
12315     if(btm) plyNr++;
12316     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12317     fseek(f, lg->offset, 0);
12318     yynewfile(f);
12319     while(1) {
12320         yyboardindex = scratch;
12321         quickFlag = plyNr+1;
12322         next = Myylex();
12323         quickFlag = 0;
12324         switch(next) {
12325             case PGNTag:
12326                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12327             default:
12328                 continue;
12329
12330             case XBoardGame:
12331             case GNUChessGame:
12332                 if(plyNr) return -1; // after we have seen moves, this is for new game
12333               continue;
12334
12335             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12336             case ImpossibleMove:
12337             case WhiteWins: // game ends here with these four
12338             case BlackWins:
12339             case GameIsDrawn:
12340             case GameUnfinished:
12341                 return -1;
12342
12343             case IllegalMove:
12344                 if(appData.testLegality) return -1;
12345             case WhiteCapturesEnPassant:
12346             case BlackCapturesEnPassant:
12347             case WhitePromotion:
12348             case BlackPromotion:
12349             case WhiteNonPromotion:
12350             case BlackNonPromotion:
12351             case NormalMove:
12352             case FirstLeg:
12353             case WhiteKingSideCastle:
12354             case WhiteQueenSideCastle:
12355             case BlackKingSideCastle:
12356             case BlackQueenSideCastle:
12357             case WhiteKingSideCastleWild:
12358             case WhiteQueenSideCastleWild:
12359             case BlackKingSideCastleWild:
12360             case BlackQueenSideCastleWild:
12361             case WhiteHSideCastleFR:
12362             case WhiteASideCastleFR:
12363             case BlackHSideCastleFR:
12364             case BlackASideCastleFR:
12365                 fromX = currentMoveString[0] - AAA;
12366                 fromY = currentMoveString[1] - ONE;
12367                 toX = currentMoveString[2] - AAA;
12368                 toY = currentMoveString[3] - ONE;
12369                 promoChar = currentMoveString[4];
12370                 break;
12371             case WhiteDrop:
12372             case BlackDrop:
12373                 fromX = next == WhiteDrop ?
12374                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12375                   (int) CharToPiece(ToLower(currentMoveString[0]));
12376                 fromY = DROP_RANK;
12377                 toX = currentMoveString[2] - AAA;
12378                 toY = currentMoveString[3] - ONE;
12379                 promoChar = 0;
12380                 break;
12381         }
12382         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12383         plyNr++;
12384         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12385         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12386         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12387         if(appData.findMirror) {
12388             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12389             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12390         }
12391     }
12392 }
12393
12394 /* Load the nth game from open file f */
12395 int
12396 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12397 {
12398     ChessMove cm;
12399     char buf[MSG_SIZ];
12400     int gn = gameNumber;
12401     ListGame *lg = NULL;
12402     int numPGNTags = 0;
12403     int err, pos = -1;
12404     GameMode oldGameMode;
12405     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12406
12407     if (appData.debugMode)
12408         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12409
12410     if (gameMode == Training )
12411         SetTrainingModeOff();
12412
12413     oldGameMode = gameMode;
12414     if (gameMode != BeginningOfGame) {
12415       Reset(FALSE, TRUE);
12416     }
12417     killX = killY = -1; // [HGM] lion: in case we did not Reset
12418
12419     gameFileFP = f;
12420     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12421         fclose(lastLoadGameFP);
12422     }
12423
12424     if (useList) {
12425         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12426
12427         if (lg) {
12428             fseek(f, lg->offset, 0);
12429             GameListHighlight(gameNumber);
12430             pos = lg->position;
12431             gn = 1;
12432         }
12433         else {
12434             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12435               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12436             else
12437             DisplayError(_("Game number out of range"), 0);
12438             return FALSE;
12439         }
12440     } else {
12441         GameListDestroy();
12442         if (fseek(f, 0, 0) == -1) {
12443             if (f == lastLoadGameFP ?
12444                 gameNumber == lastLoadGameNumber + 1 :
12445                 gameNumber == 1) {
12446                 gn = 1;
12447             } else {
12448                 DisplayError(_("Can't seek on game file"), 0);
12449                 return FALSE;
12450             }
12451         }
12452     }
12453     lastLoadGameFP = f;
12454     lastLoadGameNumber = gameNumber;
12455     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12456     lastLoadGameUseList = useList;
12457
12458     yynewfile(f);
12459
12460     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12461       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12462                 lg->gameInfo.black);
12463             DisplayTitle(buf);
12464     } else if (*title != NULLCHAR) {
12465         if (gameNumber > 1) {
12466           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12467             DisplayTitle(buf);
12468         } else {
12469             DisplayTitle(title);
12470         }
12471     }
12472
12473     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12474         gameMode = PlayFromGameFile;
12475         ModeHighlight();
12476     }
12477
12478     currentMove = forwardMostMove = backwardMostMove = 0;
12479     CopyBoard(boards[0], initialPosition);
12480     StopClocks();
12481
12482     /*
12483      * Skip the first gn-1 games in the file.
12484      * Also skip over anything that precedes an identifiable
12485      * start of game marker, to avoid being confused by
12486      * garbage at the start of the file.  Currently
12487      * recognized start of game markers are the move number "1",
12488      * the pattern "gnuchess .* game", the pattern
12489      * "^[#;%] [^ ]* game file", and a PGN tag block.
12490      * A game that starts with one of the latter two patterns
12491      * will also have a move number 1, possibly
12492      * following a position diagram.
12493      * 5-4-02: Let's try being more lenient and allowing a game to
12494      * start with an unnumbered move.  Does that break anything?
12495      */
12496     cm = lastLoadGameStart = EndOfFile;
12497     while (gn > 0) {
12498         yyboardindex = forwardMostMove;
12499         cm = (ChessMove) Myylex();
12500         switch (cm) {
12501           case EndOfFile:
12502             if (cmailMsgLoaded) {
12503                 nCmailGames = CMAIL_MAX_GAMES - gn;
12504             } else {
12505                 Reset(TRUE, TRUE);
12506                 DisplayError(_("Game not found in file"), 0);
12507             }
12508             return FALSE;
12509
12510           case GNUChessGame:
12511           case XBoardGame:
12512             gn--;
12513             lastLoadGameStart = cm;
12514             break;
12515
12516           case MoveNumberOne:
12517             switch (lastLoadGameStart) {
12518               case GNUChessGame:
12519               case XBoardGame:
12520               case PGNTag:
12521                 break;
12522               case MoveNumberOne:
12523               case EndOfFile:
12524                 gn--;           /* count this game */
12525                 lastLoadGameStart = cm;
12526                 break;
12527               default:
12528                 /* impossible */
12529                 break;
12530             }
12531             break;
12532
12533           case PGNTag:
12534             switch (lastLoadGameStart) {
12535               case GNUChessGame:
12536               case PGNTag:
12537               case MoveNumberOne:
12538               case EndOfFile:
12539                 gn--;           /* count this game */
12540                 lastLoadGameStart = cm;
12541                 break;
12542               case XBoardGame:
12543                 lastLoadGameStart = cm; /* game counted already */
12544                 break;
12545               default:
12546                 /* impossible */
12547                 break;
12548             }
12549             if (gn > 0) {
12550                 do {
12551                     yyboardindex = forwardMostMove;
12552                     cm = (ChessMove) Myylex();
12553                 } while (cm == PGNTag || cm == Comment);
12554             }
12555             break;
12556
12557           case WhiteWins:
12558           case BlackWins:
12559           case GameIsDrawn:
12560             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12561                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12562                     != CMAIL_OLD_RESULT) {
12563                     nCmailResults ++ ;
12564                     cmailResult[  CMAIL_MAX_GAMES
12565                                 - gn - 1] = CMAIL_OLD_RESULT;
12566                 }
12567             }
12568             break;
12569
12570           case NormalMove:
12571           case FirstLeg:
12572             /* Only a NormalMove can be at the start of a game
12573              * without a position diagram. */
12574             if (lastLoadGameStart == EndOfFile ) {
12575               gn--;
12576               lastLoadGameStart = MoveNumberOne;
12577             }
12578             break;
12579
12580           default:
12581             break;
12582         }
12583     }
12584
12585     if (appData.debugMode)
12586       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12587
12588     if (cm == XBoardGame) {
12589         /* Skip any header junk before position diagram and/or move 1 */
12590         for (;;) {
12591             yyboardindex = forwardMostMove;
12592             cm = (ChessMove) Myylex();
12593
12594             if (cm == EndOfFile ||
12595                 cm == GNUChessGame || cm == XBoardGame) {
12596                 /* Empty game; pretend end-of-file and handle later */
12597                 cm = EndOfFile;
12598                 break;
12599             }
12600
12601             if (cm == MoveNumberOne || cm == PositionDiagram ||
12602                 cm == PGNTag || cm == Comment)
12603               break;
12604         }
12605     } else if (cm == GNUChessGame) {
12606         if (gameInfo.event != NULL) {
12607             free(gameInfo.event);
12608         }
12609         gameInfo.event = StrSave(yy_text);
12610     }
12611
12612     startedFromSetupPosition = FALSE;
12613     while (cm == PGNTag) {
12614         if (appData.debugMode)
12615           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12616         err = ParsePGNTag(yy_text, &gameInfo);
12617         if (!err) numPGNTags++;
12618
12619         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12620         if(gameInfo.variant != oldVariant) {
12621             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12622             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12623             InitPosition(TRUE);
12624             oldVariant = gameInfo.variant;
12625             if (appData.debugMode)
12626               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12627         }
12628
12629
12630         if (gameInfo.fen != NULL) {
12631           Board initial_position;
12632           startedFromSetupPosition = TRUE;
12633           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12634             Reset(TRUE, TRUE);
12635             DisplayError(_("Bad FEN position in file"), 0);
12636             return FALSE;
12637           }
12638           CopyBoard(boards[0], initial_position);
12639           if (blackPlaysFirst) {
12640             currentMove = forwardMostMove = backwardMostMove = 1;
12641             CopyBoard(boards[1], initial_position);
12642             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12643             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12644             timeRemaining[0][1] = whiteTimeRemaining;
12645             timeRemaining[1][1] = blackTimeRemaining;
12646             if (commentList[0] != NULL) {
12647               commentList[1] = commentList[0];
12648               commentList[0] = NULL;
12649             }
12650           } else {
12651             currentMove = forwardMostMove = backwardMostMove = 0;
12652           }
12653           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12654           {   int i;
12655               initialRulePlies = FENrulePlies;
12656               for( i=0; i< nrCastlingRights; i++ )
12657                   initialRights[i] = initial_position[CASTLING][i];
12658           }
12659           yyboardindex = forwardMostMove;
12660           free(gameInfo.fen);
12661           gameInfo.fen = NULL;
12662         }
12663
12664         yyboardindex = forwardMostMove;
12665         cm = (ChessMove) Myylex();
12666
12667         /* Handle comments interspersed among the tags */
12668         while (cm == Comment) {
12669             char *p;
12670             if (appData.debugMode)
12671               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12672             p = yy_text;
12673             AppendComment(currentMove, p, FALSE);
12674             yyboardindex = forwardMostMove;
12675             cm = (ChessMove) Myylex();
12676         }
12677     }
12678
12679     /* don't rely on existence of Event tag since if game was
12680      * pasted from clipboard the Event tag may not exist
12681      */
12682     if (numPGNTags > 0){
12683         char *tags;
12684         if (gameInfo.variant == VariantNormal) {
12685           VariantClass v = StringToVariant(gameInfo.event);
12686           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12687           if(v < VariantShogi) gameInfo.variant = v;
12688         }
12689         if (!matchMode) {
12690           if( appData.autoDisplayTags ) {
12691             tags = PGNTags(&gameInfo);
12692             TagsPopUp(tags, CmailMsg());
12693             free(tags);
12694           }
12695         }
12696     } else {
12697         /* Make something up, but don't display it now */
12698         SetGameInfo();
12699         TagsPopDown();
12700     }
12701
12702     if (cm == PositionDiagram) {
12703         int i, j;
12704         char *p;
12705         Board initial_position;
12706
12707         if (appData.debugMode)
12708           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12709
12710         if (!startedFromSetupPosition) {
12711             p = yy_text;
12712             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12713               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12714                 switch (*p) {
12715                   case '{':
12716                   case '[':
12717                   case '-':
12718                   case ' ':
12719                   case '\t':
12720                   case '\n':
12721                   case '\r':
12722                     break;
12723                   default:
12724                     initial_position[i][j++] = CharToPiece(*p);
12725                     break;
12726                 }
12727             while (*p == ' ' || *p == '\t' ||
12728                    *p == '\n' || *p == '\r') p++;
12729
12730             if (strncmp(p, "black", strlen("black"))==0)
12731               blackPlaysFirst = TRUE;
12732             else
12733               blackPlaysFirst = FALSE;
12734             startedFromSetupPosition = TRUE;
12735
12736             CopyBoard(boards[0], initial_position);
12737             if (blackPlaysFirst) {
12738                 currentMove = forwardMostMove = backwardMostMove = 1;
12739                 CopyBoard(boards[1], initial_position);
12740                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12741                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12742                 timeRemaining[0][1] = whiteTimeRemaining;
12743                 timeRemaining[1][1] = blackTimeRemaining;
12744                 if (commentList[0] != NULL) {
12745                     commentList[1] = commentList[0];
12746                     commentList[0] = NULL;
12747                 }
12748             } else {
12749                 currentMove = forwardMostMove = backwardMostMove = 0;
12750             }
12751         }
12752         yyboardindex = forwardMostMove;
12753         cm = (ChessMove) Myylex();
12754     }
12755
12756   if(!creatingBook) {
12757     if (first.pr == NoProc) {
12758         StartChessProgram(&first);
12759     }
12760     InitChessProgram(&first, FALSE);
12761     SendToProgram("force\n", &first);
12762     if (startedFromSetupPosition) {
12763         SendBoard(&first, forwardMostMove);
12764     if (appData.debugMode) {
12765         fprintf(debugFP, "Load Game\n");
12766     }
12767         DisplayBothClocks();
12768     }
12769   }
12770
12771     /* [HGM] server: flag to write setup moves in broadcast file as one */
12772     loadFlag = appData.suppressLoadMoves;
12773
12774     while (cm == Comment) {
12775         char *p;
12776         if (appData.debugMode)
12777           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12778         p = yy_text;
12779         AppendComment(currentMove, p, FALSE);
12780         yyboardindex = forwardMostMove;
12781         cm = (ChessMove) Myylex();
12782     }
12783
12784     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12785         cm == WhiteWins || cm == BlackWins ||
12786         cm == GameIsDrawn || cm == GameUnfinished) {
12787         DisplayMessage("", _("No moves in game"));
12788         if (cmailMsgLoaded) {
12789             if (appData.debugMode)
12790               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12791             ClearHighlights();
12792             flipView = FALSE;
12793         }
12794         DrawPosition(FALSE, boards[currentMove]);
12795         DisplayBothClocks();
12796         gameMode = EditGame;
12797         ModeHighlight();
12798         gameFileFP = NULL;
12799         cmailOldMove = 0;
12800         return TRUE;
12801     }
12802
12803     // [HGM] PV info: routine tests if comment empty
12804     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12805         DisplayComment(currentMove - 1, commentList[currentMove]);
12806     }
12807     if (!matchMode && appData.timeDelay != 0)
12808       DrawPosition(FALSE, boards[currentMove]);
12809
12810     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12811       programStats.ok_to_send = 1;
12812     }
12813
12814     /* if the first token after the PGN tags is a move
12815      * and not move number 1, retrieve it from the parser
12816      */
12817     if (cm != MoveNumberOne)
12818         LoadGameOneMove(cm);
12819
12820     /* load the remaining moves from the file */
12821     while (LoadGameOneMove(EndOfFile)) {
12822       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12823       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12824     }
12825
12826     /* rewind to the start of the game */
12827     currentMove = backwardMostMove;
12828
12829     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12830
12831     if (oldGameMode == AnalyzeFile) {
12832       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12833       AnalyzeFileEvent();
12834     } else
12835     if (oldGameMode == AnalyzeMode) {
12836       AnalyzeFileEvent();
12837     }
12838
12839     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12840         long int w, b; // [HGM] adjourn: restore saved clock times
12841         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12842         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12843             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12844             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12845         }
12846     }
12847
12848     if(creatingBook) return TRUE;
12849     if (!matchMode && pos > 0) {
12850         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12851     } else
12852     if (matchMode || appData.timeDelay == 0) {
12853       ToEndEvent();
12854     } else if (appData.timeDelay > 0) {
12855       AutoPlayGameLoop();
12856     }
12857
12858     if (appData.debugMode)
12859         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12860
12861     loadFlag = 0; /* [HGM] true game starts */
12862     return TRUE;
12863 }
12864
12865 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12866 int
12867 ReloadPosition (int offset)
12868 {
12869     int positionNumber = lastLoadPositionNumber + offset;
12870     if (lastLoadPositionFP == NULL) {
12871         DisplayError(_("No position has been loaded yet"), 0);
12872         return FALSE;
12873     }
12874     if (positionNumber <= 0) {
12875         DisplayError(_("Can't back up any further"), 0);
12876         return FALSE;
12877     }
12878     return LoadPosition(lastLoadPositionFP, positionNumber,
12879                         lastLoadPositionTitle);
12880 }
12881
12882 /* Load the nth position from the given file */
12883 int
12884 LoadPositionFromFile (char *filename, int n, char *title)
12885 {
12886     FILE *f;
12887     char buf[MSG_SIZ];
12888
12889     if (strcmp(filename, "-") == 0) {
12890         return LoadPosition(stdin, n, "stdin");
12891     } else {
12892         f = fopen(filename, "rb");
12893         if (f == NULL) {
12894             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12895             DisplayError(buf, errno);
12896             return FALSE;
12897         } else {
12898             return LoadPosition(f, n, title);
12899         }
12900     }
12901 }
12902
12903 /* Load the nth position from the given open file, and close it */
12904 int
12905 LoadPosition (FILE *f, int positionNumber, char *title)
12906 {
12907     char *p, line[MSG_SIZ];
12908     Board initial_position;
12909     int i, j, fenMode, pn;
12910
12911     if (gameMode == Training )
12912         SetTrainingModeOff();
12913
12914     if (gameMode != BeginningOfGame) {
12915         Reset(FALSE, TRUE);
12916     }
12917     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12918         fclose(lastLoadPositionFP);
12919     }
12920     if (positionNumber == 0) positionNumber = 1;
12921     lastLoadPositionFP = f;
12922     lastLoadPositionNumber = positionNumber;
12923     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12924     if (first.pr == NoProc && !appData.noChessProgram) {
12925       StartChessProgram(&first);
12926       InitChessProgram(&first, FALSE);
12927     }
12928     pn = positionNumber;
12929     if (positionNumber < 0) {
12930         /* Negative position number means to seek to that byte offset */
12931         if (fseek(f, -positionNumber, 0) == -1) {
12932             DisplayError(_("Can't seek on position file"), 0);
12933             return FALSE;
12934         };
12935         pn = 1;
12936     } else {
12937         if (fseek(f, 0, 0) == -1) {
12938             if (f == lastLoadPositionFP ?
12939                 positionNumber == lastLoadPositionNumber + 1 :
12940                 positionNumber == 1) {
12941                 pn = 1;
12942             } else {
12943                 DisplayError(_("Can't seek on position file"), 0);
12944                 return FALSE;
12945             }
12946         }
12947     }
12948     /* See if this file is FEN or old-style xboard */
12949     if (fgets(line, MSG_SIZ, f) == NULL) {
12950         DisplayError(_("Position not found in file"), 0);
12951         return FALSE;
12952     }
12953     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12954     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12955
12956     if (pn >= 2) {
12957         if (fenMode || line[0] == '#') pn--;
12958         while (pn > 0) {
12959             /* skip positions before number pn */
12960             if (fgets(line, MSG_SIZ, f) == NULL) {
12961                 Reset(TRUE, TRUE);
12962                 DisplayError(_("Position not found in file"), 0);
12963                 return FALSE;
12964             }
12965             if (fenMode || line[0] == '#') pn--;
12966         }
12967     }
12968
12969     if (fenMode) {
12970         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12971             DisplayError(_("Bad FEN position in file"), 0);
12972             return FALSE;
12973         }
12974     } else {
12975         (void) fgets(line, MSG_SIZ, f);
12976         (void) fgets(line, MSG_SIZ, f);
12977
12978         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12979             (void) fgets(line, MSG_SIZ, f);
12980             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12981                 if (*p == ' ')
12982                   continue;
12983                 initial_position[i][j++] = CharToPiece(*p);
12984             }
12985         }
12986
12987         blackPlaysFirst = FALSE;
12988         if (!feof(f)) {
12989             (void) fgets(line, MSG_SIZ, f);
12990             if (strncmp(line, "black", strlen("black"))==0)
12991               blackPlaysFirst = TRUE;
12992         }
12993     }
12994     startedFromSetupPosition = TRUE;
12995
12996     CopyBoard(boards[0], initial_position);
12997     if (blackPlaysFirst) {
12998         currentMove = forwardMostMove = backwardMostMove = 1;
12999         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13000         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13001         CopyBoard(boards[1], initial_position);
13002         DisplayMessage("", _("Black to play"));
13003     } else {
13004         currentMove = forwardMostMove = backwardMostMove = 0;
13005         DisplayMessage("", _("White to play"));
13006     }
13007     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13008     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13009         SendToProgram("force\n", &first);
13010         SendBoard(&first, forwardMostMove);
13011     }
13012     if (appData.debugMode) {
13013 int i, j;
13014   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13015   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13016         fprintf(debugFP, "Load Position\n");
13017     }
13018
13019     if (positionNumber > 1) {
13020       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13021         DisplayTitle(line);
13022     } else {
13023         DisplayTitle(title);
13024     }
13025     gameMode = EditGame;
13026     ModeHighlight();
13027     ResetClocks();
13028     timeRemaining[0][1] = whiteTimeRemaining;
13029     timeRemaining[1][1] = blackTimeRemaining;
13030     DrawPosition(FALSE, boards[currentMove]);
13031
13032     return TRUE;
13033 }
13034
13035
13036 void
13037 CopyPlayerNameIntoFileName (char **dest, char *src)
13038 {
13039     while (*src != NULLCHAR && *src != ',') {
13040         if (*src == ' ') {
13041             *(*dest)++ = '_';
13042             src++;
13043         } else {
13044             *(*dest)++ = *src++;
13045         }
13046     }
13047 }
13048
13049 char *
13050 DefaultFileName (char *ext)
13051 {
13052     static char def[MSG_SIZ];
13053     char *p;
13054
13055     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13056         p = def;
13057         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13058         *p++ = '-';
13059         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13060         *p++ = '.';
13061         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13062     } else {
13063         def[0] = NULLCHAR;
13064     }
13065     return def;
13066 }
13067
13068 /* Save the current game to the given file */
13069 int
13070 SaveGameToFile (char *filename, int append)
13071 {
13072     FILE *f;
13073     char buf[MSG_SIZ];
13074     int result, i, t,tot=0;
13075
13076     if (strcmp(filename, "-") == 0) {
13077         return SaveGame(stdout, 0, NULL);
13078     } else {
13079         for(i=0; i<10; i++) { // upto 10 tries
13080              f = fopen(filename, append ? "a" : "w");
13081              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13082              if(f || errno != 13) break;
13083              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13084              tot += t;
13085         }
13086         if (f == NULL) {
13087             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13088             DisplayError(buf, errno);
13089             return FALSE;
13090         } else {
13091             safeStrCpy(buf, lastMsg, MSG_SIZ);
13092             DisplayMessage(_("Waiting for access to save file"), "");
13093             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13094             DisplayMessage(_("Saving game"), "");
13095             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13096             result = SaveGame(f, 0, NULL);
13097             DisplayMessage(buf, "");
13098             return result;
13099         }
13100     }
13101 }
13102
13103 char *
13104 SavePart (char *str)
13105 {
13106     static char buf[MSG_SIZ];
13107     char *p;
13108
13109     p = strchr(str, ' ');
13110     if (p == NULL) return str;
13111     strncpy(buf, str, p - str);
13112     buf[p - str] = NULLCHAR;
13113     return buf;
13114 }
13115
13116 #define PGN_MAX_LINE 75
13117
13118 #define PGN_SIDE_WHITE  0
13119 #define PGN_SIDE_BLACK  1
13120
13121 static int
13122 FindFirstMoveOutOfBook (int side)
13123 {
13124     int result = -1;
13125
13126     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13127         int index = backwardMostMove;
13128         int has_book_hit = 0;
13129
13130         if( (index % 2) != side ) {
13131             index++;
13132         }
13133
13134         while( index < forwardMostMove ) {
13135             /* Check to see if engine is in book */
13136             int depth = pvInfoList[index].depth;
13137             int score = pvInfoList[index].score;
13138             int in_book = 0;
13139
13140             if( depth <= 2 ) {
13141                 in_book = 1;
13142             }
13143             else if( score == 0 && depth == 63 ) {
13144                 in_book = 1; /* Zappa */
13145             }
13146             else if( score == 2 && depth == 99 ) {
13147                 in_book = 1; /* Abrok */
13148             }
13149
13150             has_book_hit += in_book;
13151
13152             if( ! in_book ) {
13153                 result = index;
13154
13155                 break;
13156             }
13157
13158             index += 2;
13159         }
13160     }
13161
13162     return result;
13163 }
13164
13165 void
13166 GetOutOfBookInfo (char * buf)
13167 {
13168     int oob[2];
13169     int i;
13170     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13171
13172     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13173     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13174
13175     *buf = '\0';
13176
13177     if( oob[0] >= 0 || oob[1] >= 0 ) {
13178         for( i=0; i<2; i++ ) {
13179             int idx = oob[i];
13180
13181             if( idx >= 0 ) {
13182                 if( i > 0 && oob[0] >= 0 ) {
13183                     strcat( buf, "   " );
13184                 }
13185
13186                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13187                 sprintf( buf+strlen(buf), "%s%.2f",
13188                     pvInfoList[idx].score >= 0 ? "+" : "",
13189                     pvInfoList[idx].score / 100.0 );
13190             }
13191         }
13192     }
13193 }
13194
13195 /* Save game in PGN style and close the file */
13196 int
13197 SaveGamePGN (FILE *f)
13198 {
13199     int i, offset, linelen, newblock;
13200 //    char *movetext;
13201     char numtext[32];
13202     int movelen, numlen, blank;
13203     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13204
13205     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13206
13207     PrintPGNTags(f, &gameInfo);
13208
13209     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13210
13211     if (backwardMostMove > 0 || startedFromSetupPosition) {
13212         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13213         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13214         fprintf(f, "\n{--------------\n");
13215         PrintPosition(f, backwardMostMove);
13216         fprintf(f, "--------------}\n");
13217         free(fen);
13218     }
13219     else {
13220         /* [AS] Out of book annotation */
13221         if( appData.saveOutOfBookInfo ) {
13222             char buf[64];
13223
13224             GetOutOfBookInfo( buf );
13225
13226             if( buf[0] != '\0' ) {
13227                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13228             }
13229         }
13230
13231         fprintf(f, "\n");
13232     }
13233
13234     i = backwardMostMove;
13235     linelen = 0;
13236     newblock = TRUE;
13237
13238     while (i < forwardMostMove) {
13239         /* Print comments preceding this move */
13240         if (commentList[i] != NULL) {
13241             if (linelen > 0) fprintf(f, "\n");
13242             fprintf(f, "%s", commentList[i]);
13243             linelen = 0;
13244             newblock = TRUE;
13245         }
13246
13247         /* Format move number */
13248         if ((i % 2) == 0)
13249           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13250         else
13251           if (newblock)
13252             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13253           else
13254             numtext[0] = NULLCHAR;
13255
13256         numlen = strlen(numtext);
13257         newblock = FALSE;
13258
13259         /* Print move number */
13260         blank = linelen > 0 && numlen > 0;
13261         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13262             fprintf(f, "\n");
13263             linelen = 0;
13264             blank = 0;
13265         }
13266         if (blank) {
13267             fprintf(f, " ");
13268             linelen++;
13269         }
13270         fprintf(f, "%s", numtext);
13271         linelen += numlen;
13272
13273         /* Get move */
13274         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13275         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13276
13277         /* Print move */
13278         blank = linelen > 0 && movelen > 0;
13279         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13280             fprintf(f, "\n");
13281             linelen = 0;
13282             blank = 0;
13283         }
13284         if (blank) {
13285             fprintf(f, " ");
13286             linelen++;
13287         }
13288         fprintf(f, "%s", move_buffer);
13289         linelen += movelen;
13290
13291         /* [AS] Add PV info if present */
13292         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13293             /* [HGM] add time */
13294             char buf[MSG_SIZ]; int seconds;
13295
13296             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13297
13298             if( seconds <= 0)
13299               buf[0] = 0;
13300             else
13301               if( seconds < 30 )
13302                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13303               else
13304                 {
13305                   seconds = (seconds + 4)/10; // round to full seconds
13306                   if( seconds < 60 )
13307                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13308                   else
13309                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13310                 }
13311
13312             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13313                       pvInfoList[i].score >= 0 ? "+" : "",
13314                       pvInfoList[i].score / 100.0,
13315                       pvInfoList[i].depth,
13316                       buf );
13317
13318             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13319
13320             /* Print score/depth */
13321             blank = linelen > 0 && movelen > 0;
13322             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13323                 fprintf(f, "\n");
13324                 linelen = 0;
13325                 blank = 0;
13326             }
13327             if (blank) {
13328                 fprintf(f, " ");
13329                 linelen++;
13330             }
13331             fprintf(f, "%s", move_buffer);
13332             linelen += movelen;
13333         }
13334
13335         i++;
13336     }
13337
13338     /* Start a new line */
13339     if (linelen > 0) fprintf(f, "\n");
13340
13341     /* Print comments after last move */
13342     if (commentList[i] != NULL) {
13343         fprintf(f, "%s\n", commentList[i]);
13344     }
13345
13346     /* Print result */
13347     if (gameInfo.resultDetails != NULL &&
13348         gameInfo.resultDetails[0] != NULLCHAR) {
13349         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13350         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13351            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13352             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13353         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13354     } else {
13355         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13356     }
13357
13358     fclose(f);
13359     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13360     return TRUE;
13361 }
13362
13363 /* Save game in old style and close the file */
13364 int
13365 SaveGameOldStyle (FILE *f)
13366 {
13367     int i, offset;
13368     time_t tm;
13369
13370     tm = time((time_t *) NULL);
13371
13372     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13373     PrintOpponents(f);
13374
13375     if (backwardMostMove > 0 || startedFromSetupPosition) {
13376         fprintf(f, "\n[--------------\n");
13377         PrintPosition(f, backwardMostMove);
13378         fprintf(f, "--------------]\n");
13379     } else {
13380         fprintf(f, "\n");
13381     }
13382
13383     i = backwardMostMove;
13384     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13385
13386     while (i < forwardMostMove) {
13387         if (commentList[i] != NULL) {
13388             fprintf(f, "[%s]\n", commentList[i]);
13389         }
13390
13391         if ((i % 2) == 1) {
13392             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13393             i++;
13394         } else {
13395             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13396             i++;
13397             if (commentList[i] != NULL) {
13398                 fprintf(f, "\n");
13399                 continue;
13400             }
13401             if (i >= forwardMostMove) {
13402                 fprintf(f, "\n");
13403                 break;
13404             }
13405             fprintf(f, "%s\n", parseList[i]);
13406             i++;
13407         }
13408     }
13409
13410     if (commentList[i] != NULL) {
13411         fprintf(f, "[%s]\n", commentList[i]);
13412     }
13413
13414     /* This isn't really the old style, but it's close enough */
13415     if (gameInfo.resultDetails != NULL &&
13416         gameInfo.resultDetails[0] != NULLCHAR) {
13417         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13418                 gameInfo.resultDetails);
13419     } else {
13420         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13421     }
13422
13423     fclose(f);
13424     return TRUE;
13425 }
13426
13427 /* Save the current game to open file f and close the file */
13428 int
13429 SaveGame (FILE *f, int dummy, char *dummy2)
13430 {
13431     if (gameMode == EditPosition) EditPositionDone(TRUE);
13432     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13433     if (appData.oldSaveStyle)
13434       return SaveGameOldStyle(f);
13435     else
13436       return SaveGamePGN(f);
13437 }
13438
13439 /* Save the current position to the given file */
13440 int
13441 SavePositionToFile (char *filename)
13442 {
13443     FILE *f;
13444     char buf[MSG_SIZ];
13445
13446     if (strcmp(filename, "-") == 0) {
13447         return SavePosition(stdout, 0, NULL);
13448     } else {
13449         f = fopen(filename, "a");
13450         if (f == NULL) {
13451             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13452             DisplayError(buf, errno);
13453             return FALSE;
13454         } else {
13455             safeStrCpy(buf, lastMsg, MSG_SIZ);
13456             DisplayMessage(_("Waiting for access to save file"), "");
13457             flock(fileno(f), LOCK_EX); // [HGM] lock
13458             DisplayMessage(_("Saving position"), "");
13459             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13460             SavePosition(f, 0, NULL);
13461             DisplayMessage(buf, "");
13462             return TRUE;
13463         }
13464     }
13465 }
13466
13467 /* Save the current position to the given open file and close the file */
13468 int
13469 SavePosition (FILE *f, int dummy, char *dummy2)
13470 {
13471     time_t tm;
13472     char *fen;
13473
13474     if (gameMode == EditPosition) EditPositionDone(TRUE);
13475     if (appData.oldSaveStyle) {
13476         tm = time((time_t *) NULL);
13477
13478         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13479         PrintOpponents(f);
13480         fprintf(f, "[--------------\n");
13481         PrintPosition(f, currentMove);
13482         fprintf(f, "--------------]\n");
13483     } else {
13484         fen = PositionToFEN(currentMove, NULL, 1);
13485         fprintf(f, "%s\n", fen);
13486         free(fen);
13487     }
13488     fclose(f);
13489     return TRUE;
13490 }
13491
13492 void
13493 ReloadCmailMsgEvent (int unregister)
13494 {
13495 #if !WIN32
13496     static char *inFilename = NULL;
13497     static char *outFilename;
13498     int i;
13499     struct stat inbuf, outbuf;
13500     int status;
13501
13502     /* Any registered moves are unregistered if unregister is set, */
13503     /* i.e. invoked by the signal handler */
13504     if (unregister) {
13505         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13506             cmailMoveRegistered[i] = FALSE;
13507             if (cmailCommentList[i] != NULL) {
13508                 free(cmailCommentList[i]);
13509                 cmailCommentList[i] = NULL;
13510             }
13511         }
13512         nCmailMovesRegistered = 0;
13513     }
13514
13515     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13516         cmailResult[i] = CMAIL_NOT_RESULT;
13517     }
13518     nCmailResults = 0;
13519
13520     if (inFilename == NULL) {
13521         /* Because the filenames are static they only get malloced once  */
13522         /* and they never get freed                                      */
13523         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13524         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13525
13526         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13527         sprintf(outFilename, "%s.out", appData.cmailGameName);
13528     }
13529
13530     status = stat(outFilename, &outbuf);
13531     if (status < 0) {
13532         cmailMailedMove = FALSE;
13533     } else {
13534         status = stat(inFilename, &inbuf);
13535         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13536     }
13537
13538     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13539        counts the games, notes how each one terminated, etc.
13540
13541        It would be nice to remove this kludge and instead gather all
13542        the information while building the game list.  (And to keep it
13543        in the game list nodes instead of having a bunch of fixed-size
13544        parallel arrays.)  Note this will require getting each game's
13545        termination from the PGN tags, as the game list builder does
13546        not process the game moves.  --mann
13547        */
13548     cmailMsgLoaded = TRUE;
13549     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13550
13551     /* Load first game in the file or popup game menu */
13552     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13553
13554 #endif /* !WIN32 */
13555     return;
13556 }
13557
13558 int
13559 RegisterMove ()
13560 {
13561     FILE *f;
13562     char string[MSG_SIZ];
13563
13564     if (   cmailMailedMove
13565         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13566         return TRUE;            /* Allow free viewing  */
13567     }
13568
13569     /* Unregister move to ensure that we don't leave RegisterMove        */
13570     /* with the move registered when the conditions for registering no   */
13571     /* longer hold                                                       */
13572     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13573         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13574         nCmailMovesRegistered --;
13575
13576         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13577           {
13578               free(cmailCommentList[lastLoadGameNumber - 1]);
13579               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13580           }
13581     }
13582
13583     if (cmailOldMove == -1) {
13584         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13585         return FALSE;
13586     }
13587
13588     if (currentMove > cmailOldMove + 1) {
13589         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13590         return FALSE;
13591     }
13592
13593     if (currentMove < cmailOldMove) {
13594         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13595         return FALSE;
13596     }
13597
13598     if (forwardMostMove > currentMove) {
13599         /* Silently truncate extra moves */
13600         TruncateGame();
13601     }
13602
13603     if (   (currentMove == cmailOldMove + 1)
13604         || (   (currentMove == cmailOldMove)
13605             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13606                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13607         if (gameInfo.result != GameUnfinished) {
13608             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13609         }
13610
13611         if (commentList[currentMove] != NULL) {
13612             cmailCommentList[lastLoadGameNumber - 1]
13613               = StrSave(commentList[currentMove]);
13614         }
13615         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13616
13617         if (appData.debugMode)
13618           fprintf(debugFP, "Saving %s for game %d\n",
13619                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13620
13621         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13622
13623         f = fopen(string, "w");
13624         if (appData.oldSaveStyle) {
13625             SaveGameOldStyle(f); /* also closes the file */
13626
13627             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13628             f = fopen(string, "w");
13629             SavePosition(f, 0, NULL); /* also closes the file */
13630         } else {
13631             fprintf(f, "{--------------\n");
13632             PrintPosition(f, currentMove);
13633             fprintf(f, "--------------}\n\n");
13634
13635             SaveGame(f, 0, NULL); /* also closes the file*/
13636         }
13637
13638         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13639         nCmailMovesRegistered ++;
13640     } else if (nCmailGames == 1) {
13641         DisplayError(_("You have not made a move yet"), 0);
13642         return FALSE;
13643     }
13644
13645     return TRUE;
13646 }
13647
13648 void
13649 MailMoveEvent ()
13650 {
13651 #if !WIN32
13652     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13653     FILE *commandOutput;
13654     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13655     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13656     int nBuffers;
13657     int i;
13658     int archived;
13659     char *arcDir;
13660
13661     if (! cmailMsgLoaded) {
13662         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13663         return;
13664     }
13665
13666     if (nCmailGames == nCmailResults) {
13667         DisplayError(_("No unfinished games"), 0);
13668         return;
13669     }
13670
13671 #if CMAIL_PROHIBIT_REMAIL
13672     if (cmailMailedMove) {
13673       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);
13674         DisplayError(msg, 0);
13675         return;
13676     }
13677 #endif
13678
13679     if (! (cmailMailedMove || RegisterMove())) return;
13680
13681     if (   cmailMailedMove
13682         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13683       snprintf(string, MSG_SIZ, partCommandString,
13684                appData.debugMode ? " -v" : "", appData.cmailGameName);
13685         commandOutput = popen(string, "r");
13686
13687         if (commandOutput == NULL) {
13688             DisplayError(_("Failed to invoke cmail"), 0);
13689         } else {
13690             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13691                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13692             }
13693             if (nBuffers > 1) {
13694                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13695                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13696                 nBytes = MSG_SIZ - 1;
13697             } else {
13698                 (void) memcpy(msg, buffer, nBytes);
13699             }
13700             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13701
13702             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13703                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13704
13705                 archived = TRUE;
13706                 for (i = 0; i < nCmailGames; i ++) {
13707                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13708                         archived = FALSE;
13709                     }
13710                 }
13711                 if (   archived
13712                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13713                         != NULL)) {
13714                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13715                            arcDir,
13716                            appData.cmailGameName,
13717                            gameInfo.date);
13718                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13719                     cmailMsgLoaded = FALSE;
13720                 }
13721             }
13722
13723             DisplayInformation(msg);
13724             pclose(commandOutput);
13725         }
13726     } else {
13727         if ((*cmailMsg) != '\0') {
13728             DisplayInformation(cmailMsg);
13729         }
13730     }
13731
13732     return;
13733 #endif /* !WIN32 */
13734 }
13735
13736 char *
13737 CmailMsg ()
13738 {
13739 #if WIN32
13740     return NULL;
13741 #else
13742     int  prependComma = 0;
13743     char number[5];
13744     char string[MSG_SIZ];       /* Space for game-list */
13745     int  i;
13746
13747     if (!cmailMsgLoaded) return "";
13748
13749     if (cmailMailedMove) {
13750       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13751     } else {
13752         /* Create a list of games left */
13753       snprintf(string, MSG_SIZ, "[");
13754         for (i = 0; i < nCmailGames; i ++) {
13755             if (! (   cmailMoveRegistered[i]
13756                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13757                 if (prependComma) {
13758                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13759                 } else {
13760                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13761                     prependComma = 1;
13762                 }
13763
13764                 strcat(string, number);
13765             }
13766         }
13767         strcat(string, "]");
13768
13769         if (nCmailMovesRegistered + nCmailResults == 0) {
13770             switch (nCmailGames) {
13771               case 1:
13772                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13773                 break;
13774
13775               case 2:
13776                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13777                 break;
13778
13779               default:
13780                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13781                          nCmailGames);
13782                 break;
13783             }
13784         } else {
13785             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13786               case 1:
13787                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13788                          string);
13789                 break;
13790
13791               case 0:
13792                 if (nCmailResults == nCmailGames) {
13793                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13794                 } else {
13795                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13796                 }
13797                 break;
13798
13799               default:
13800                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13801                          string);
13802             }
13803         }
13804     }
13805     return cmailMsg;
13806 #endif /* WIN32 */
13807 }
13808
13809 void
13810 ResetGameEvent ()
13811 {
13812     if (gameMode == Training)
13813       SetTrainingModeOff();
13814
13815     Reset(TRUE, TRUE);
13816     cmailMsgLoaded = FALSE;
13817     if (appData.icsActive) {
13818       SendToICS(ics_prefix);
13819       SendToICS("refresh\n");
13820     }
13821 }
13822
13823 void
13824 ExitEvent (int status)
13825 {
13826     exiting++;
13827     if (exiting > 2) {
13828       /* Give up on clean exit */
13829       exit(status);
13830     }
13831     if (exiting > 1) {
13832       /* Keep trying for clean exit */
13833       return;
13834     }
13835
13836     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13837
13838     if (telnetISR != NULL) {
13839       RemoveInputSource(telnetISR);
13840     }
13841     if (icsPR != NoProc) {
13842       DestroyChildProcess(icsPR, TRUE);
13843     }
13844
13845     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13846     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13847
13848     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13849     /* make sure this other one finishes before killing it!                  */
13850     if(endingGame) { int count = 0;
13851         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13852         while(endingGame && count++ < 10) DoSleep(1);
13853         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13854     }
13855
13856     /* Kill off chess programs */
13857     if (first.pr != NoProc) {
13858         ExitAnalyzeMode();
13859
13860         DoSleep( appData.delayBeforeQuit );
13861         SendToProgram("quit\n", &first);
13862         DoSleep( appData.delayAfterQuit );
13863         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13864     }
13865     if (second.pr != NoProc) {
13866         DoSleep( appData.delayBeforeQuit );
13867         SendToProgram("quit\n", &second);
13868         DoSleep( appData.delayAfterQuit );
13869         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13870     }
13871     if (first.isr != NULL) {
13872         RemoveInputSource(first.isr);
13873     }
13874     if (second.isr != NULL) {
13875         RemoveInputSource(second.isr);
13876     }
13877
13878     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13879     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13880
13881     ShutDownFrontEnd();
13882     exit(status);
13883 }
13884
13885 void
13886 PauseEngine (ChessProgramState *cps)
13887 {
13888     SendToProgram("pause\n", cps);
13889     cps->pause = 2;
13890 }
13891
13892 void
13893 UnPauseEngine (ChessProgramState *cps)
13894 {
13895     SendToProgram("resume\n", cps);
13896     cps->pause = 1;
13897 }
13898
13899 void
13900 PauseEvent ()
13901 {
13902     if (appData.debugMode)
13903         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13904     if (pausing) {
13905         pausing = FALSE;
13906         ModeHighlight();
13907         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13908             StartClocks();
13909             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13910                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13911                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13912             }
13913             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13914             HandleMachineMove(stashedInputMove, stalledEngine);
13915             stalledEngine = NULL;
13916             return;
13917         }
13918         if (gameMode == MachinePlaysWhite ||
13919             gameMode == TwoMachinesPlay   ||
13920             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13921             if(first.pause)  UnPauseEngine(&first);
13922             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13923             if(second.pause) UnPauseEngine(&second);
13924             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13925             StartClocks();
13926         } else {
13927             DisplayBothClocks();
13928         }
13929         if (gameMode == PlayFromGameFile) {
13930             if (appData.timeDelay >= 0)
13931                 AutoPlayGameLoop();
13932         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13933             Reset(FALSE, TRUE);
13934             SendToICS(ics_prefix);
13935             SendToICS("refresh\n");
13936         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13937             ForwardInner(forwardMostMove);
13938         }
13939         pauseExamInvalid = FALSE;
13940     } else {
13941         switch (gameMode) {
13942           default:
13943             return;
13944           case IcsExamining:
13945             pauseExamForwardMostMove = forwardMostMove;
13946             pauseExamInvalid = FALSE;
13947             /* fall through */
13948           case IcsObserving:
13949           case IcsPlayingWhite:
13950           case IcsPlayingBlack:
13951             pausing = TRUE;
13952             ModeHighlight();
13953             return;
13954           case PlayFromGameFile:
13955             (void) StopLoadGameTimer();
13956             pausing = TRUE;
13957             ModeHighlight();
13958             break;
13959           case BeginningOfGame:
13960             if (appData.icsActive) return;
13961             /* else fall through */
13962           case MachinePlaysWhite:
13963           case MachinePlaysBlack:
13964           case TwoMachinesPlay:
13965             if (forwardMostMove == 0)
13966               return;           /* don't pause if no one has moved */
13967             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13968                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13969                 if(onMove->pause) {           // thinking engine can be paused
13970                     PauseEngine(onMove);      // do it
13971                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13972                         PauseEngine(onMove->other);
13973                     else
13974                         SendToProgram("easy\n", onMove->other);
13975                     StopClocks();
13976                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13977             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13978                 if(first.pause) {
13979                     PauseEngine(&first);
13980                     StopClocks();
13981                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13982             } else { // human on move, pause pondering by either method
13983                 if(first.pause)
13984                     PauseEngine(&first);
13985                 else if(appData.ponderNextMove)
13986                     SendToProgram("easy\n", &first);
13987                 StopClocks();
13988             }
13989             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13990           case AnalyzeMode:
13991             pausing = TRUE;
13992             ModeHighlight();
13993             break;
13994         }
13995     }
13996 }
13997
13998 void
13999 EditCommentEvent ()
14000 {
14001     char title[MSG_SIZ];
14002
14003     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14004       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14005     } else {
14006       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14007                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14008                parseList[currentMove - 1]);
14009     }
14010
14011     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14012 }
14013
14014
14015 void
14016 EditTagsEvent ()
14017 {
14018     char *tags = PGNTags(&gameInfo);
14019     bookUp = FALSE;
14020     EditTagsPopUp(tags, NULL);
14021     free(tags);
14022 }
14023
14024 void
14025 ToggleSecond ()
14026 {
14027   if(second.analyzing) {
14028     SendToProgram("exit\n", &second);
14029     second.analyzing = FALSE;
14030   } else {
14031     if (second.pr == NoProc) StartChessProgram(&second);
14032     InitChessProgram(&second, FALSE);
14033     FeedMovesToProgram(&second, currentMove);
14034
14035     SendToProgram("analyze\n", &second);
14036     second.analyzing = TRUE;
14037   }
14038 }
14039
14040 /* Toggle ShowThinking */
14041 void
14042 ToggleShowThinking()
14043 {
14044   appData.showThinking = !appData.showThinking;
14045   ShowThinkingEvent();
14046 }
14047
14048 int
14049 AnalyzeModeEvent ()
14050 {
14051     char buf[MSG_SIZ];
14052
14053     if (!first.analysisSupport) {
14054       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14055       DisplayError(buf, 0);
14056       return 0;
14057     }
14058     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14059     if (appData.icsActive) {
14060         if (gameMode != IcsObserving) {
14061           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14062             DisplayError(buf, 0);
14063             /* secure check */
14064             if (appData.icsEngineAnalyze) {
14065                 if (appData.debugMode)
14066                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14067                 ExitAnalyzeMode();
14068                 ModeHighlight();
14069             }
14070             return 0;
14071         }
14072         /* if enable, user wants to disable icsEngineAnalyze */
14073         if (appData.icsEngineAnalyze) {
14074                 ExitAnalyzeMode();
14075                 ModeHighlight();
14076                 return 0;
14077         }
14078         appData.icsEngineAnalyze = TRUE;
14079         if (appData.debugMode)
14080             fprintf(debugFP, "ICS engine analyze starting... \n");
14081     }
14082
14083     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14084     if (appData.noChessProgram || gameMode == AnalyzeMode)
14085       return 0;
14086
14087     if (gameMode != AnalyzeFile) {
14088         if (!appData.icsEngineAnalyze) {
14089                EditGameEvent();
14090                if (gameMode != EditGame) return 0;
14091         }
14092         if (!appData.showThinking) ToggleShowThinking();
14093         ResurrectChessProgram();
14094         SendToProgram("analyze\n", &first);
14095         first.analyzing = TRUE;
14096         /*first.maybeThinking = TRUE;*/
14097         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14098         EngineOutputPopUp();
14099     }
14100     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14101     pausing = FALSE;
14102     ModeHighlight();
14103     SetGameInfo();
14104
14105     StartAnalysisClock();
14106     GetTimeMark(&lastNodeCountTime);
14107     lastNodeCount = 0;
14108     return 1;
14109 }
14110
14111 void
14112 AnalyzeFileEvent ()
14113 {
14114     if (appData.noChessProgram || gameMode == AnalyzeFile)
14115       return;
14116
14117     if (!first.analysisSupport) {
14118       char buf[MSG_SIZ];
14119       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14120       DisplayError(buf, 0);
14121       return;
14122     }
14123
14124     if (gameMode != AnalyzeMode) {
14125         keepInfo = 1; // mere annotating should not alter PGN tags
14126         EditGameEvent();
14127         keepInfo = 0;
14128         if (gameMode != EditGame) return;
14129         if (!appData.showThinking) ToggleShowThinking();
14130         ResurrectChessProgram();
14131         SendToProgram("analyze\n", &first);
14132         first.analyzing = TRUE;
14133         /*first.maybeThinking = TRUE;*/
14134         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14135         EngineOutputPopUp();
14136     }
14137     gameMode = AnalyzeFile;
14138     pausing = FALSE;
14139     ModeHighlight();
14140
14141     StartAnalysisClock();
14142     GetTimeMark(&lastNodeCountTime);
14143     lastNodeCount = 0;
14144     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14145     AnalysisPeriodicEvent(1);
14146 }
14147
14148 void
14149 MachineWhiteEvent ()
14150 {
14151     char buf[MSG_SIZ];
14152     char *bookHit = NULL;
14153
14154     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14155       return;
14156
14157
14158     if (gameMode == PlayFromGameFile ||
14159         gameMode == TwoMachinesPlay  ||
14160         gameMode == Training         ||
14161         gameMode == AnalyzeMode      ||
14162         gameMode == EndOfGame)
14163         EditGameEvent();
14164
14165     if (gameMode == EditPosition)
14166         EditPositionDone(TRUE);
14167
14168     if (!WhiteOnMove(currentMove)) {
14169         DisplayError(_("It is not White's turn"), 0);
14170         return;
14171     }
14172
14173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14174       ExitAnalyzeMode();
14175
14176     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14177         gameMode == AnalyzeFile)
14178         TruncateGame();
14179
14180     ResurrectChessProgram();    /* in case it isn't running */
14181     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14182         gameMode = MachinePlaysWhite;
14183         ResetClocks();
14184     } else
14185     gameMode = MachinePlaysWhite;
14186     pausing = FALSE;
14187     ModeHighlight();
14188     SetGameInfo();
14189     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14190     DisplayTitle(buf);
14191     if (first.sendName) {
14192       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14193       SendToProgram(buf, &first);
14194     }
14195     if (first.sendTime) {
14196       if (first.useColors) {
14197         SendToProgram("black\n", &first); /*gnu kludge*/
14198       }
14199       SendTimeRemaining(&first, TRUE);
14200     }
14201     if (first.useColors) {
14202       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14203     }
14204     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14205     SetMachineThinkingEnables();
14206     first.maybeThinking = TRUE;
14207     StartClocks();
14208     firstMove = FALSE;
14209
14210     if (appData.autoFlipView && !flipView) {
14211       flipView = !flipView;
14212       DrawPosition(FALSE, NULL);
14213       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14214     }
14215
14216     if(bookHit) { // [HGM] book: simulate book reply
14217         static char bookMove[MSG_SIZ]; // a bit generous?
14218
14219         programStats.nodes = programStats.depth = programStats.time =
14220         programStats.score = programStats.got_only_move = 0;
14221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14222
14223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14224         strcat(bookMove, bookHit);
14225         HandleMachineMove(bookMove, &first);
14226     }
14227 }
14228
14229 void
14230 MachineBlackEvent ()
14231 {
14232   char buf[MSG_SIZ];
14233   char *bookHit = NULL;
14234
14235     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14236         return;
14237
14238
14239     if (gameMode == PlayFromGameFile ||
14240         gameMode == TwoMachinesPlay  ||
14241         gameMode == Training         ||
14242         gameMode == AnalyzeMode      ||
14243         gameMode == EndOfGame)
14244         EditGameEvent();
14245
14246     if (gameMode == EditPosition)
14247         EditPositionDone(TRUE);
14248
14249     if (WhiteOnMove(currentMove)) {
14250         DisplayError(_("It is not Black's turn"), 0);
14251         return;
14252     }
14253
14254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14255       ExitAnalyzeMode();
14256
14257     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14258         gameMode == AnalyzeFile)
14259         TruncateGame();
14260
14261     ResurrectChessProgram();    /* in case it isn't running */
14262     gameMode = MachinePlaysBlack;
14263     pausing = FALSE;
14264     ModeHighlight();
14265     SetGameInfo();
14266     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14267     DisplayTitle(buf);
14268     if (first.sendName) {
14269       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14270       SendToProgram(buf, &first);
14271     }
14272     if (first.sendTime) {
14273       if (first.useColors) {
14274         SendToProgram("white\n", &first); /*gnu kludge*/
14275       }
14276       SendTimeRemaining(&first, FALSE);
14277     }
14278     if (first.useColors) {
14279       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14280     }
14281     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14282     SetMachineThinkingEnables();
14283     first.maybeThinking = TRUE;
14284     StartClocks();
14285
14286     if (appData.autoFlipView && flipView) {
14287       flipView = !flipView;
14288       DrawPosition(FALSE, NULL);
14289       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14290     }
14291     if(bookHit) { // [HGM] book: simulate book reply
14292         static char bookMove[MSG_SIZ]; // a bit generous?
14293
14294         programStats.nodes = programStats.depth = programStats.time =
14295         programStats.score = programStats.got_only_move = 0;
14296         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14297
14298         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14299         strcat(bookMove, bookHit);
14300         HandleMachineMove(bookMove, &first);
14301     }
14302 }
14303
14304
14305 void
14306 DisplayTwoMachinesTitle ()
14307 {
14308     char buf[MSG_SIZ];
14309     if (appData.matchGames > 0) {
14310         if(appData.tourneyFile[0]) {
14311           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14312                    gameInfo.white, _("vs."), gameInfo.black,
14313                    nextGame+1, appData.matchGames+1,
14314                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14315         } else
14316         if (first.twoMachinesColor[0] == 'w') {
14317           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14318                    gameInfo.white, _("vs."),  gameInfo.black,
14319                    first.matchWins, second.matchWins,
14320                    matchGame - 1 - (first.matchWins + second.matchWins));
14321         } else {
14322           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14323                    gameInfo.white, _("vs."), gameInfo.black,
14324                    second.matchWins, first.matchWins,
14325                    matchGame - 1 - (first.matchWins + second.matchWins));
14326         }
14327     } else {
14328       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14329     }
14330     DisplayTitle(buf);
14331 }
14332
14333 void
14334 SettingsMenuIfReady ()
14335 {
14336   if (second.lastPing != second.lastPong) {
14337     DisplayMessage("", _("Waiting for second chess program"));
14338     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14339     return;
14340   }
14341   ThawUI();
14342   DisplayMessage("", "");
14343   SettingsPopUp(&second);
14344 }
14345
14346 int
14347 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14348 {
14349     char buf[MSG_SIZ];
14350     if (cps->pr == NoProc) {
14351         StartChessProgram(cps);
14352         if (cps->protocolVersion == 1) {
14353           retry();
14354           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14355         } else {
14356           /* kludge: allow timeout for initial "feature" command */
14357           if(retry != TwoMachinesEventIfReady) FreezeUI();
14358           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14359           DisplayMessage("", buf);
14360           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14361         }
14362         return 1;
14363     }
14364     return 0;
14365 }
14366
14367 void
14368 TwoMachinesEvent P((void))
14369 {
14370     int i;
14371     char buf[MSG_SIZ];
14372     ChessProgramState *onmove;
14373     char *bookHit = NULL;
14374     static int stalling = 0;
14375     TimeMark now;
14376     long wait;
14377
14378     if (appData.noChessProgram) return;
14379
14380     switch (gameMode) {
14381       case TwoMachinesPlay:
14382         return;
14383       case MachinePlaysWhite:
14384       case MachinePlaysBlack:
14385         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14386             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14387             return;
14388         }
14389         /* fall through */
14390       case BeginningOfGame:
14391       case PlayFromGameFile:
14392       case EndOfGame:
14393         EditGameEvent();
14394         if (gameMode != EditGame) return;
14395         break;
14396       case EditPosition:
14397         EditPositionDone(TRUE);
14398         break;
14399       case AnalyzeMode:
14400       case AnalyzeFile:
14401         ExitAnalyzeMode();
14402         break;
14403       case EditGame:
14404       default:
14405         break;
14406     }
14407
14408 //    forwardMostMove = currentMove;
14409     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14410     startingEngine = TRUE;
14411
14412     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14413
14414     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14415     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14416       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14417       return;
14418     }
14419     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14420
14421     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14422                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14423         startingEngine = FALSE;
14424         DisplayError("second engine does not play this", 0);
14425         return;
14426     }
14427
14428     if(!stalling) {
14429       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14430       SendToProgram("force\n", &second);
14431       stalling = 1;
14432       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14433       return;
14434     }
14435     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14436     if(appData.matchPause>10000 || appData.matchPause<10)
14437                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14438     wait = SubtractTimeMarks(&now, &pauseStart);
14439     if(wait < appData.matchPause) {
14440         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14441         return;
14442     }
14443     // we are now committed to starting the game
14444     stalling = 0;
14445     DisplayMessage("", "");
14446     if (startedFromSetupPosition) {
14447         SendBoard(&second, backwardMostMove);
14448     if (appData.debugMode) {
14449         fprintf(debugFP, "Two Machines\n");
14450     }
14451     }
14452     for (i = backwardMostMove; i < forwardMostMove; i++) {
14453         SendMoveToProgram(i, &second);
14454     }
14455
14456     gameMode = TwoMachinesPlay;
14457     pausing = startingEngine = FALSE;
14458     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14459     SetGameInfo();
14460     DisplayTwoMachinesTitle();
14461     firstMove = TRUE;
14462     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14463         onmove = &first;
14464     } else {
14465         onmove = &second;
14466     }
14467     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14468     SendToProgram(first.computerString, &first);
14469     if (first.sendName) {
14470       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14471       SendToProgram(buf, &first);
14472     }
14473     SendToProgram(second.computerString, &second);
14474     if (second.sendName) {
14475       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14476       SendToProgram(buf, &second);
14477     }
14478
14479     ResetClocks();
14480     if (!first.sendTime || !second.sendTime) {
14481         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14482         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14483     }
14484     if (onmove->sendTime) {
14485       if (onmove->useColors) {
14486         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14487       }
14488       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14489     }
14490     if (onmove->useColors) {
14491       SendToProgram(onmove->twoMachinesColor, onmove);
14492     }
14493     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14494 //    SendToProgram("go\n", onmove);
14495     onmove->maybeThinking = TRUE;
14496     SetMachineThinkingEnables();
14497
14498     StartClocks();
14499
14500     if(bookHit) { // [HGM] book: simulate book reply
14501         static char bookMove[MSG_SIZ]; // a bit generous?
14502
14503         programStats.nodes = programStats.depth = programStats.time =
14504         programStats.score = programStats.got_only_move = 0;
14505         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14506
14507         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14508         strcat(bookMove, bookHit);
14509         savedMessage = bookMove; // args for deferred call
14510         savedState = onmove;
14511         ScheduleDelayedEvent(DeferredBookMove, 1);
14512     }
14513 }
14514
14515 void
14516 TrainingEvent ()
14517 {
14518     if (gameMode == Training) {
14519       SetTrainingModeOff();
14520       gameMode = PlayFromGameFile;
14521       DisplayMessage("", _("Training mode off"));
14522     } else {
14523       gameMode = Training;
14524       animateTraining = appData.animate;
14525
14526       /* make sure we are not already at the end of the game */
14527       if (currentMove < forwardMostMove) {
14528         SetTrainingModeOn();
14529         DisplayMessage("", _("Training mode on"));
14530       } else {
14531         gameMode = PlayFromGameFile;
14532         DisplayError(_("Already at end of game"), 0);
14533       }
14534     }
14535     ModeHighlight();
14536 }
14537
14538 void
14539 IcsClientEvent ()
14540 {
14541     if (!appData.icsActive) return;
14542     switch (gameMode) {
14543       case IcsPlayingWhite:
14544       case IcsPlayingBlack:
14545       case IcsObserving:
14546       case IcsIdle:
14547       case BeginningOfGame:
14548       case IcsExamining:
14549         return;
14550
14551       case EditGame:
14552         break;
14553
14554       case EditPosition:
14555         EditPositionDone(TRUE);
14556         break;
14557
14558       case AnalyzeMode:
14559       case AnalyzeFile:
14560         ExitAnalyzeMode();
14561         break;
14562
14563       default:
14564         EditGameEvent();
14565         break;
14566     }
14567
14568     gameMode = IcsIdle;
14569     ModeHighlight();
14570     return;
14571 }
14572
14573 void
14574 EditGameEvent ()
14575 {
14576     int i;
14577
14578     switch (gameMode) {
14579       case Training:
14580         SetTrainingModeOff();
14581         break;
14582       case MachinePlaysWhite:
14583       case MachinePlaysBlack:
14584       case BeginningOfGame:
14585         SendToProgram("force\n", &first);
14586         SetUserThinkingEnables();
14587         break;
14588       case PlayFromGameFile:
14589         (void) StopLoadGameTimer();
14590         if (gameFileFP != NULL) {
14591             gameFileFP = NULL;
14592         }
14593         break;
14594       case EditPosition:
14595         EditPositionDone(TRUE);
14596         break;
14597       case AnalyzeMode:
14598       case AnalyzeFile:
14599         ExitAnalyzeMode();
14600         SendToProgram("force\n", &first);
14601         break;
14602       case TwoMachinesPlay:
14603         GameEnds(EndOfFile, NULL, GE_PLAYER);
14604         ResurrectChessProgram();
14605         SetUserThinkingEnables();
14606         break;
14607       case EndOfGame:
14608         ResurrectChessProgram();
14609         break;
14610       case IcsPlayingBlack:
14611       case IcsPlayingWhite:
14612         DisplayError(_("Warning: You are still playing a game"), 0);
14613         break;
14614       case IcsObserving:
14615         DisplayError(_("Warning: You are still observing a game"), 0);
14616         break;
14617       case IcsExamining:
14618         DisplayError(_("Warning: You are still examining a game"), 0);
14619         break;
14620       case IcsIdle:
14621         break;
14622       case EditGame:
14623       default:
14624         return;
14625     }
14626
14627     pausing = FALSE;
14628     StopClocks();
14629     first.offeredDraw = second.offeredDraw = 0;
14630
14631     if (gameMode == PlayFromGameFile) {
14632         whiteTimeRemaining = timeRemaining[0][currentMove];
14633         blackTimeRemaining = timeRemaining[1][currentMove];
14634         DisplayTitle("");
14635     }
14636
14637     if (gameMode == MachinePlaysWhite ||
14638         gameMode == MachinePlaysBlack ||
14639         gameMode == TwoMachinesPlay ||
14640         gameMode == EndOfGame) {
14641         i = forwardMostMove;
14642         while (i > currentMove) {
14643             SendToProgram("undo\n", &first);
14644             i--;
14645         }
14646         if(!adjustedClock) {
14647         whiteTimeRemaining = timeRemaining[0][currentMove];
14648         blackTimeRemaining = timeRemaining[1][currentMove];
14649         DisplayBothClocks();
14650         }
14651         if (whiteFlag || blackFlag) {
14652             whiteFlag = blackFlag = 0;
14653         }
14654         DisplayTitle("");
14655     }
14656
14657     gameMode = EditGame;
14658     ModeHighlight();
14659     SetGameInfo();
14660 }
14661
14662
14663 void
14664 EditPositionEvent ()
14665 {
14666     if (gameMode == EditPosition) {
14667         EditGameEvent();
14668         return;
14669     }
14670
14671     EditGameEvent();
14672     if (gameMode != EditGame) return;
14673
14674     gameMode = EditPosition;
14675     ModeHighlight();
14676     SetGameInfo();
14677     if (currentMove > 0)
14678       CopyBoard(boards[0], boards[currentMove]);
14679
14680     blackPlaysFirst = !WhiteOnMove(currentMove);
14681     ResetClocks();
14682     currentMove = forwardMostMove = backwardMostMove = 0;
14683     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14684     DisplayMove(-1);
14685     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14686 }
14687
14688 void
14689 ExitAnalyzeMode ()
14690 {
14691     /* [DM] icsEngineAnalyze - possible call from other functions */
14692     if (appData.icsEngineAnalyze) {
14693         appData.icsEngineAnalyze = FALSE;
14694
14695         DisplayMessage("",_("Close ICS engine analyze..."));
14696     }
14697     if (first.analysisSupport && first.analyzing) {
14698       SendToBoth("exit\n");
14699       first.analyzing = second.analyzing = FALSE;
14700     }
14701     thinkOutput[0] = NULLCHAR;
14702 }
14703
14704 void
14705 EditPositionDone (Boolean fakeRights)
14706 {
14707     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14708
14709     startedFromSetupPosition = TRUE;
14710     InitChessProgram(&first, FALSE);
14711     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14712       boards[0][EP_STATUS] = EP_NONE;
14713       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14714       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14715         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14716         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14717       } else boards[0][CASTLING][2] = NoRights;
14718       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14719         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14720         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14721       } else boards[0][CASTLING][5] = NoRights;
14722       if(gameInfo.variant == VariantSChess) {
14723         int i;
14724         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14725           boards[0][VIRGIN][i] = 0;
14726           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14727           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14728         }
14729       }
14730     }
14731     SendToProgram("force\n", &first);
14732     if (blackPlaysFirst) {
14733         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14734         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14735         currentMove = forwardMostMove = backwardMostMove = 1;
14736         CopyBoard(boards[1], boards[0]);
14737     } else {
14738         currentMove = forwardMostMove = backwardMostMove = 0;
14739     }
14740     SendBoard(&first, forwardMostMove);
14741     if (appData.debugMode) {
14742         fprintf(debugFP, "EditPosDone\n");
14743     }
14744     DisplayTitle("");
14745     DisplayMessage("", "");
14746     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14747     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14748     gameMode = EditGame;
14749     ModeHighlight();
14750     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14751     ClearHighlights(); /* [AS] */
14752 }
14753
14754 /* Pause for `ms' milliseconds */
14755 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14756 void
14757 TimeDelay (long ms)
14758 {
14759     TimeMark m1, m2;
14760
14761     GetTimeMark(&m1);
14762     do {
14763         GetTimeMark(&m2);
14764     } while (SubtractTimeMarks(&m2, &m1) < ms);
14765 }
14766
14767 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14768 void
14769 SendMultiLineToICS (char *buf)
14770 {
14771     char temp[MSG_SIZ+1], *p;
14772     int len;
14773
14774     len = strlen(buf);
14775     if (len > MSG_SIZ)
14776       len = MSG_SIZ;
14777
14778     strncpy(temp, buf, len);
14779     temp[len] = 0;
14780
14781     p = temp;
14782     while (*p) {
14783         if (*p == '\n' || *p == '\r')
14784           *p = ' ';
14785         ++p;
14786     }
14787
14788     strcat(temp, "\n");
14789     SendToICS(temp);
14790     SendToPlayer(temp, strlen(temp));
14791 }
14792
14793 void
14794 SetWhiteToPlayEvent ()
14795 {
14796     if (gameMode == EditPosition) {
14797         blackPlaysFirst = FALSE;
14798         DisplayBothClocks();    /* works because currentMove is 0 */
14799     } else if (gameMode == IcsExamining) {
14800         SendToICS(ics_prefix);
14801         SendToICS("tomove white\n");
14802     }
14803 }
14804
14805 void
14806 SetBlackToPlayEvent ()
14807 {
14808     if (gameMode == EditPosition) {
14809         blackPlaysFirst = TRUE;
14810         currentMove = 1;        /* kludge */
14811         DisplayBothClocks();
14812         currentMove = 0;
14813     } else if (gameMode == IcsExamining) {
14814         SendToICS(ics_prefix);
14815         SendToICS("tomove black\n");
14816     }
14817 }
14818
14819 void
14820 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14821 {
14822     char buf[MSG_SIZ];
14823     ChessSquare piece = boards[0][y][x];
14824     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14825     static int lastVariant;
14826
14827     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14828
14829     switch (selection) {
14830       case ClearBoard:
14831         CopyBoard(currentBoard, boards[0]);
14832         CopyBoard(menuBoard, initialPosition);
14833         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14834             SendToICS(ics_prefix);
14835             SendToICS("bsetup clear\n");
14836         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14837             SendToICS(ics_prefix);
14838             SendToICS("clearboard\n");
14839         } else {
14840             int nonEmpty = 0;
14841             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14842                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14843                 for (y = 0; y < BOARD_HEIGHT; y++) {
14844                     if (gameMode == IcsExamining) {
14845                         if (boards[currentMove][y][x] != EmptySquare) {
14846                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14847                                     AAA + x, ONE + y);
14848                             SendToICS(buf);
14849                         }
14850                     } else {
14851                         if(boards[0][y][x] != p) nonEmpty++;
14852                         boards[0][y][x] = p;
14853                     }
14854                 }
14855                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14856             }
14857             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14858                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14859                     ChessSquare p = menuBoard[0][x];
14860                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14861                     p = menuBoard[BOARD_HEIGHT-1][x];
14862                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14863                 }
14864                 DisplayMessage("Clicking clock again restores position", "");
14865                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14866                 if(!nonEmpty) { // asked to clear an empty board
14867                     CopyBoard(boards[0], menuBoard);
14868                 } else
14869                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14870                     CopyBoard(boards[0], initialPosition);
14871                 } else
14872                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14873                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14874                     CopyBoard(boards[0], erasedBoard);
14875                 } else
14876                     CopyBoard(erasedBoard, currentBoard);
14877
14878             }
14879         }
14880         if (gameMode == EditPosition) {
14881             DrawPosition(FALSE, boards[0]);
14882         }
14883         break;
14884
14885       case WhitePlay:
14886         SetWhiteToPlayEvent();
14887         break;
14888
14889       case BlackPlay:
14890         SetBlackToPlayEvent();
14891         break;
14892
14893       case EmptySquare:
14894         if (gameMode == IcsExamining) {
14895             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14896             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14897             SendToICS(buf);
14898         } else {
14899             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14900                 if(x == BOARD_LEFT-2) {
14901                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14902                     boards[0][y][1] = 0;
14903                 } else
14904                 if(x == BOARD_RGHT+1) {
14905                     if(y >= gameInfo.holdingsSize) break;
14906                     boards[0][y][BOARD_WIDTH-2] = 0;
14907                 } else break;
14908             }
14909             boards[0][y][x] = EmptySquare;
14910             DrawPosition(FALSE, boards[0]);
14911         }
14912         break;
14913
14914       case PromotePiece:
14915         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14916            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14917             selection = (ChessSquare) (PROMOTED piece);
14918         } else if(piece == EmptySquare) selection = WhiteSilver;
14919         else selection = (ChessSquare)((int)piece - 1);
14920         goto defaultlabel;
14921
14922       case DemotePiece:
14923         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14924            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14925             selection = (ChessSquare) (DEMOTED piece);
14926         } else if(piece == EmptySquare) selection = BlackSilver;
14927         else selection = (ChessSquare)((int)piece + 1);
14928         goto defaultlabel;
14929
14930       case WhiteQueen:
14931       case BlackQueen:
14932         if(gameInfo.variant == VariantShatranj ||
14933            gameInfo.variant == VariantXiangqi  ||
14934            gameInfo.variant == VariantCourier  ||
14935            gameInfo.variant == VariantASEAN    ||
14936            gameInfo.variant == VariantMakruk     )
14937             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14938         goto defaultlabel;
14939
14940       case WhiteKing:
14941       case BlackKing:
14942         if(gameInfo.variant == VariantXiangqi)
14943             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14944         if(gameInfo.variant == VariantKnightmate)
14945             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14946       default:
14947         defaultlabel:
14948         if (gameMode == IcsExamining) {
14949             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14950             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14951                      PieceToChar(selection), AAA + x, ONE + y);
14952             SendToICS(buf);
14953         } else {
14954             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14955                 int n;
14956                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14957                     n = PieceToNumber(selection - BlackPawn);
14958                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14959                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14960                     boards[0][BOARD_HEIGHT-1-n][1]++;
14961                 } else
14962                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14963                     n = PieceToNumber(selection);
14964                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14965                     boards[0][n][BOARD_WIDTH-1] = selection;
14966                     boards[0][n][BOARD_WIDTH-2]++;
14967                 }
14968             } else
14969             boards[0][y][x] = selection;
14970             DrawPosition(TRUE, boards[0]);
14971             ClearHighlights();
14972             fromX = fromY = -1;
14973         }
14974         break;
14975     }
14976 }
14977
14978
14979 void
14980 DropMenuEvent (ChessSquare selection, int x, int y)
14981 {
14982     ChessMove moveType;
14983
14984     switch (gameMode) {
14985       case IcsPlayingWhite:
14986       case MachinePlaysBlack:
14987         if (!WhiteOnMove(currentMove)) {
14988             DisplayMoveError(_("It is Black's turn"));
14989             return;
14990         }
14991         moveType = WhiteDrop;
14992         break;
14993       case IcsPlayingBlack:
14994       case MachinePlaysWhite:
14995         if (WhiteOnMove(currentMove)) {
14996             DisplayMoveError(_("It is White's turn"));
14997             return;
14998         }
14999         moveType = BlackDrop;
15000         break;
15001       case EditGame:
15002         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15003         break;
15004       default:
15005         return;
15006     }
15007
15008     if (moveType == BlackDrop && selection < BlackPawn) {
15009       selection = (ChessSquare) ((int) selection
15010                                  + (int) BlackPawn - (int) WhitePawn);
15011     }
15012     if (boards[currentMove][y][x] != EmptySquare) {
15013         DisplayMoveError(_("That square is occupied"));
15014         return;
15015     }
15016
15017     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15018 }
15019
15020 void
15021 AcceptEvent ()
15022 {
15023     /* Accept a pending offer of any kind from opponent */
15024
15025     if (appData.icsActive) {
15026         SendToICS(ics_prefix);
15027         SendToICS("accept\n");
15028     } else if (cmailMsgLoaded) {
15029         if (currentMove == cmailOldMove &&
15030             commentList[cmailOldMove] != NULL &&
15031             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15032                    "Black offers a draw" : "White offers a draw")) {
15033             TruncateGame();
15034             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15035             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15036         } else {
15037             DisplayError(_("There is no pending offer on this move"), 0);
15038             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15039         }
15040     } else {
15041         /* Not used for offers from chess program */
15042     }
15043 }
15044
15045 void
15046 DeclineEvent ()
15047 {
15048     /* Decline a pending offer of any kind from opponent */
15049
15050     if (appData.icsActive) {
15051         SendToICS(ics_prefix);
15052         SendToICS("decline\n");
15053     } else if (cmailMsgLoaded) {
15054         if (currentMove == cmailOldMove &&
15055             commentList[cmailOldMove] != NULL &&
15056             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15057                    "Black offers a draw" : "White offers a draw")) {
15058 #ifdef NOTDEF
15059             AppendComment(cmailOldMove, "Draw declined", TRUE);
15060             DisplayComment(cmailOldMove - 1, "Draw declined");
15061 #endif /*NOTDEF*/
15062         } else {
15063             DisplayError(_("There is no pending offer on this move"), 0);
15064         }
15065     } else {
15066         /* Not used for offers from chess program */
15067     }
15068 }
15069
15070 void
15071 RematchEvent ()
15072 {
15073     /* Issue ICS rematch command */
15074     if (appData.icsActive) {
15075         SendToICS(ics_prefix);
15076         SendToICS("rematch\n");
15077     }
15078 }
15079
15080 void
15081 CallFlagEvent ()
15082 {
15083     /* Call your opponent's flag (claim a win on time) */
15084     if (appData.icsActive) {
15085         SendToICS(ics_prefix);
15086         SendToICS("flag\n");
15087     } else {
15088         switch (gameMode) {
15089           default:
15090             return;
15091           case MachinePlaysWhite:
15092             if (whiteFlag) {
15093                 if (blackFlag)
15094                   GameEnds(GameIsDrawn, "Both players ran out of time",
15095                            GE_PLAYER);
15096                 else
15097                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15098             } else {
15099                 DisplayError(_("Your opponent is not out of time"), 0);
15100             }
15101             break;
15102           case MachinePlaysBlack:
15103             if (blackFlag) {
15104                 if (whiteFlag)
15105                   GameEnds(GameIsDrawn, "Both players ran out of time",
15106                            GE_PLAYER);
15107                 else
15108                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15109             } else {
15110                 DisplayError(_("Your opponent is not out of time"), 0);
15111             }
15112             break;
15113         }
15114     }
15115 }
15116
15117 void
15118 ClockClick (int which)
15119 {       // [HGM] code moved to back-end from winboard.c
15120         if(which) { // black clock
15121           if (gameMode == EditPosition || gameMode == IcsExamining) {
15122             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15123             SetBlackToPlayEvent();
15124           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15125           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15126           } else if (shiftKey) {
15127             AdjustClock(which, -1);
15128           } else if (gameMode == IcsPlayingWhite ||
15129                      gameMode == MachinePlaysBlack) {
15130             CallFlagEvent();
15131           }
15132         } else { // white clock
15133           if (gameMode == EditPosition || gameMode == IcsExamining) {
15134             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15135             SetWhiteToPlayEvent();
15136           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15137           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15138           } else if (shiftKey) {
15139             AdjustClock(which, -1);
15140           } else if (gameMode == IcsPlayingBlack ||
15141                    gameMode == MachinePlaysWhite) {
15142             CallFlagEvent();
15143           }
15144         }
15145 }
15146
15147 void
15148 DrawEvent ()
15149 {
15150     /* Offer draw or accept pending draw offer from opponent */
15151
15152     if (appData.icsActive) {
15153         /* Note: tournament rules require draw offers to be
15154            made after you make your move but before you punch
15155            your clock.  Currently ICS doesn't let you do that;
15156            instead, you immediately punch your clock after making
15157            a move, but you can offer a draw at any time. */
15158
15159         SendToICS(ics_prefix);
15160         SendToICS("draw\n");
15161         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15162     } else if (cmailMsgLoaded) {
15163         if (currentMove == cmailOldMove &&
15164             commentList[cmailOldMove] != NULL &&
15165             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15166                    "Black offers a draw" : "White offers a draw")) {
15167             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15168             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15169         } else if (currentMove == cmailOldMove + 1) {
15170             char *offer = WhiteOnMove(cmailOldMove) ?
15171               "White offers a draw" : "Black offers a draw";
15172             AppendComment(currentMove, offer, TRUE);
15173             DisplayComment(currentMove - 1, offer);
15174             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15175         } else {
15176             DisplayError(_("You must make your move before offering a draw"), 0);
15177             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15178         }
15179     } else if (first.offeredDraw) {
15180         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15181     } else {
15182         if (first.sendDrawOffers) {
15183             SendToProgram("draw\n", &first);
15184             userOfferedDraw = TRUE;
15185         }
15186     }
15187 }
15188
15189 void
15190 AdjournEvent ()
15191 {
15192     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15193
15194     if (appData.icsActive) {
15195         SendToICS(ics_prefix);
15196         SendToICS("adjourn\n");
15197     } else {
15198         /* Currently GNU Chess doesn't offer or accept Adjourns */
15199     }
15200 }
15201
15202
15203 void
15204 AbortEvent ()
15205 {
15206     /* Offer Abort or accept pending Abort offer from opponent */
15207
15208     if (appData.icsActive) {
15209         SendToICS(ics_prefix);
15210         SendToICS("abort\n");
15211     } else {
15212         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15213     }
15214 }
15215
15216 void
15217 ResignEvent ()
15218 {
15219     /* Resign.  You can do this even if it's not your turn. */
15220
15221     if (appData.icsActive) {
15222         SendToICS(ics_prefix);
15223         SendToICS("resign\n");
15224     } else {
15225         switch (gameMode) {
15226           case MachinePlaysWhite:
15227             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15228             break;
15229           case MachinePlaysBlack:
15230             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15231             break;
15232           case EditGame:
15233             if (cmailMsgLoaded) {
15234                 TruncateGame();
15235                 if (WhiteOnMove(cmailOldMove)) {
15236                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15237                 } else {
15238                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15239                 }
15240                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15241             }
15242             break;
15243           default:
15244             break;
15245         }
15246     }
15247 }
15248
15249
15250 void
15251 StopObservingEvent ()
15252 {
15253     /* Stop observing current games */
15254     SendToICS(ics_prefix);
15255     SendToICS("unobserve\n");
15256 }
15257
15258 void
15259 StopExaminingEvent ()
15260 {
15261     /* Stop observing current game */
15262     SendToICS(ics_prefix);
15263     SendToICS("unexamine\n");
15264 }
15265
15266 void
15267 ForwardInner (int target)
15268 {
15269     int limit; int oldSeekGraphUp = seekGraphUp;
15270
15271     if (appData.debugMode)
15272         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15273                 target, currentMove, forwardMostMove);
15274
15275     if (gameMode == EditPosition)
15276       return;
15277
15278     seekGraphUp = FALSE;
15279     MarkTargetSquares(1);
15280
15281     if (gameMode == PlayFromGameFile && !pausing)
15282       PauseEvent();
15283
15284     if (gameMode == IcsExamining && pausing)
15285       limit = pauseExamForwardMostMove;
15286     else
15287       limit = forwardMostMove;
15288
15289     if (target > limit) target = limit;
15290
15291     if (target > 0 && moveList[target - 1][0]) {
15292         int fromX, fromY, toX, toY;
15293         toX = moveList[target - 1][2] - AAA;
15294         toY = moveList[target - 1][3] - ONE;
15295         if (moveList[target - 1][1] == '@') {
15296             if (appData.highlightLastMove) {
15297                 SetHighlights(-1, -1, toX, toY);
15298             }
15299         } else {
15300             fromX = moveList[target - 1][0] - AAA;
15301             fromY = moveList[target - 1][1] - ONE;
15302             if (target == currentMove + 1) {
15303                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15304             }
15305             if (appData.highlightLastMove) {
15306                 SetHighlights(fromX, fromY, toX, toY);
15307             }
15308         }
15309     }
15310     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15311         gameMode == Training || gameMode == PlayFromGameFile ||
15312         gameMode == AnalyzeFile) {
15313         while (currentMove < target) {
15314             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15315             SendMoveToProgram(currentMove++, &first);
15316         }
15317     } else {
15318         currentMove = target;
15319     }
15320
15321     if (gameMode == EditGame || gameMode == EndOfGame) {
15322         whiteTimeRemaining = timeRemaining[0][currentMove];
15323         blackTimeRemaining = timeRemaining[1][currentMove];
15324     }
15325     DisplayBothClocks();
15326     DisplayMove(currentMove - 1);
15327     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15328     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15329     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15330         DisplayComment(currentMove - 1, commentList[currentMove]);
15331     }
15332     ClearMap(); // [HGM] exclude: invalidate map
15333 }
15334
15335
15336 void
15337 ForwardEvent ()
15338 {
15339     if (gameMode == IcsExamining && !pausing) {
15340         SendToICS(ics_prefix);
15341         SendToICS("forward\n");
15342     } else {
15343         ForwardInner(currentMove + 1);
15344     }
15345 }
15346
15347 void
15348 ToEndEvent ()
15349 {
15350     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15351         /* to optimze, we temporarily turn off analysis mode while we feed
15352          * the remaining moves to the engine. Otherwise we get analysis output
15353          * after each move.
15354          */
15355         if (first.analysisSupport) {
15356           SendToProgram("exit\nforce\n", &first);
15357           first.analyzing = FALSE;
15358         }
15359     }
15360
15361     if (gameMode == IcsExamining && !pausing) {
15362         SendToICS(ics_prefix);
15363         SendToICS("forward 999999\n");
15364     } else {
15365         ForwardInner(forwardMostMove);
15366     }
15367
15368     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15369         /* we have fed all the moves, so reactivate analysis mode */
15370         SendToProgram("analyze\n", &first);
15371         first.analyzing = TRUE;
15372         /*first.maybeThinking = TRUE;*/
15373         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15374     }
15375 }
15376
15377 void
15378 BackwardInner (int target)
15379 {
15380     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15381
15382     if (appData.debugMode)
15383         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15384                 target, currentMove, forwardMostMove);
15385
15386     if (gameMode == EditPosition) return;
15387     seekGraphUp = FALSE;
15388     MarkTargetSquares(1);
15389     if (currentMove <= backwardMostMove) {
15390         ClearHighlights();
15391         DrawPosition(full_redraw, boards[currentMove]);
15392         return;
15393     }
15394     if (gameMode == PlayFromGameFile && !pausing)
15395       PauseEvent();
15396
15397     if (moveList[target][0]) {
15398         int fromX, fromY, toX, toY;
15399         toX = moveList[target][2] - AAA;
15400         toY = moveList[target][3] - ONE;
15401         if (moveList[target][1] == '@') {
15402             if (appData.highlightLastMove) {
15403                 SetHighlights(-1, -1, toX, toY);
15404             }
15405         } else {
15406             fromX = moveList[target][0] - AAA;
15407             fromY = moveList[target][1] - ONE;
15408             if (target == currentMove - 1) {
15409                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15410             }
15411             if (appData.highlightLastMove) {
15412                 SetHighlights(fromX, fromY, toX, toY);
15413             }
15414         }
15415     }
15416     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15417         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15418         while (currentMove > target) {
15419             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15420                 // null move cannot be undone. Reload program with move history before it.
15421                 int i;
15422                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15423                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15424                 }
15425                 SendBoard(&first, i);
15426               if(second.analyzing) SendBoard(&second, i);
15427                 for(currentMove=i; currentMove<target; currentMove++) {
15428                     SendMoveToProgram(currentMove, &first);
15429                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15430                 }
15431                 break;
15432             }
15433             SendToBoth("undo\n");
15434             currentMove--;
15435         }
15436     } else {
15437         currentMove = target;
15438     }
15439
15440     if (gameMode == EditGame || gameMode == EndOfGame) {
15441         whiteTimeRemaining = timeRemaining[0][currentMove];
15442         blackTimeRemaining = timeRemaining[1][currentMove];
15443     }
15444     DisplayBothClocks();
15445     DisplayMove(currentMove - 1);
15446     DrawPosition(full_redraw, boards[currentMove]);
15447     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15448     // [HGM] PV info: routine tests if comment empty
15449     DisplayComment(currentMove - 1, commentList[currentMove]);
15450     ClearMap(); // [HGM] exclude: invalidate map
15451 }
15452
15453 void
15454 BackwardEvent ()
15455 {
15456     if (gameMode == IcsExamining && !pausing) {
15457         SendToICS(ics_prefix);
15458         SendToICS("backward\n");
15459     } else {
15460         BackwardInner(currentMove - 1);
15461     }
15462 }
15463
15464 void
15465 ToStartEvent ()
15466 {
15467     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15468         /* to optimize, we temporarily turn off analysis mode while we undo
15469          * all the moves. Otherwise we get analysis output after each undo.
15470          */
15471         if (first.analysisSupport) {
15472           SendToProgram("exit\nforce\n", &first);
15473           first.analyzing = FALSE;
15474         }
15475     }
15476
15477     if (gameMode == IcsExamining && !pausing) {
15478         SendToICS(ics_prefix);
15479         SendToICS("backward 999999\n");
15480     } else {
15481         BackwardInner(backwardMostMove);
15482     }
15483
15484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15485         /* we have fed all the moves, so reactivate analysis mode */
15486         SendToProgram("analyze\n", &first);
15487         first.analyzing = TRUE;
15488         /*first.maybeThinking = TRUE;*/
15489         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15490     }
15491 }
15492
15493 void
15494 ToNrEvent (int to)
15495 {
15496   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15497   if (to >= forwardMostMove) to = forwardMostMove;
15498   if (to <= backwardMostMove) to = backwardMostMove;
15499   if (to < currentMove) {
15500     BackwardInner(to);
15501   } else {
15502     ForwardInner(to);
15503   }
15504 }
15505
15506 void
15507 RevertEvent (Boolean annotate)
15508 {
15509     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15510         return;
15511     }
15512     if (gameMode != IcsExamining) {
15513         DisplayError(_("You are not examining a game"), 0);
15514         return;
15515     }
15516     if (pausing) {
15517         DisplayError(_("You can't revert while pausing"), 0);
15518         return;
15519     }
15520     SendToICS(ics_prefix);
15521     SendToICS("revert\n");
15522 }
15523
15524 void
15525 RetractMoveEvent ()
15526 {
15527     switch (gameMode) {
15528       case MachinePlaysWhite:
15529       case MachinePlaysBlack:
15530         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15531             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15532             return;
15533         }
15534         if (forwardMostMove < 2) return;
15535         currentMove = forwardMostMove = forwardMostMove - 2;
15536         whiteTimeRemaining = timeRemaining[0][currentMove];
15537         blackTimeRemaining = timeRemaining[1][currentMove];
15538         DisplayBothClocks();
15539         DisplayMove(currentMove - 1);
15540         ClearHighlights();/*!! could figure this out*/
15541         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15542         SendToProgram("remove\n", &first);
15543         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15544         break;
15545
15546       case BeginningOfGame:
15547       default:
15548         break;
15549
15550       case IcsPlayingWhite:
15551       case IcsPlayingBlack:
15552         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15553             SendToICS(ics_prefix);
15554             SendToICS("takeback 2\n");
15555         } else {
15556             SendToICS(ics_prefix);
15557             SendToICS("takeback 1\n");
15558         }
15559         break;
15560     }
15561 }
15562
15563 void
15564 MoveNowEvent ()
15565 {
15566     ChessProgramState *cps;
15567
15568     switch (gameMode) {
15569       case MachinePlaysWhite:
15570         if (!WhiteOnMove(forwardMostMove)) {
15571             DisplayError(_("It is your turn"), 0);
15572             return;
15573         }
15574         cps = &first;
15575         break;
15576       case MachinePlaysBlack:
15577         if (WhiteOnMove(forwardMostMove)) {
15578             DisplayError(_("It is your turn"), 0);
15579             return;
15580         }
15581         cps = &first;
15582         break;
15583       case TwoMachinesPlay:
15584         if (WhiteOnMove(forwardMostMove) ==
15585             (first.twoMachinesColor[0] == 'w')) {
15586             cps = &first;
15587         } else {
15588             cps = &second;
15589         }
15590         break;
15591       case BeginningOfGame:
15592       default:
15593         return;
15594     }
15595     SendToProgram("?\n", cps);
15596 }
15597
15598 void
15599 TruncateGameEvent ()
15600 {
15601     EditGameEvent();
15602     if (gameMode != EditGame) return;
15603     TruncateGame();
15604 }
15605
15606 void
15607 TruncateGame ()
15608 {
15609     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15610     if (forwardMostMove > currentMove) {
15611         if (gameInfo.resultDetails != NULL) {
15612             free(gameInfo.resultDetails);
15613             gameInfo.resultDetails = NULL;
15614             gameInfo.result = GameUnfinished;
15615         }
15616         forwardMostMove = currentMove;
15617         HistorySet(parseList, backwardMostMove, forwardMostMove,
15618                    currentMove-1);
15619     }
15620 }
15621
15622 void
15623 HintEvent ()
15624 {
15625     if (appData.noChessProgram) return;
15626     switch (gameMode) {
15627       case MachinePlaysWhite:
15628         if (WhiteOnMove(forwardMostMove)) {
15629             DisplayError(_("Wait until your turn."), 0);
15630             return;
15631         }
15632         break;
15633       case BeginningOfGame:
15634       case MachinePlaysBlack:
15635         if (!WhiteOnMove(forwardMostMove)) {
15636             DisplayError(_("Wait until your turn."), 0);
15637             return;
15638         }
15639         break;
15640       default:
15641         DisplayError(_("No hint available"), 0);
15642         return;
15643     }
15644     SendToProgram("hint\n", &first);
15645     hintRequested = TRUE;
15646 }
15647
15648 void
15649 CreateBookEvent ()
15650 {
15651     ListGame * lg = (ListGame *) gameList.head;
15652     FILE *f, *g;
15653     int nItem;
15654     static int secondTime = FALSE;
15655
15656     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15657         DisplayError(_("Game list not loaded or empty"), 0);
15658         return;
15659     }
15660
15661     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15662         fclose(g);
15663         secondTime++;
15664         DisplayNote(_("Book file exists! Try again for overwrite."));
15665         return;
15666     }
15667
15668     creatingBook = TRUE;
15669     secondTime = FALSE;
15670
15671     /* Get list size */
15672     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15673         LoadGame(f, nItem, "", TRUE);
15674         AddGameToBook(TRUE);
15675         lg = (ListGame *) lg->node.succ;
15676     }
15677
15678     creatingBook = FALSE;
15679     FlushBook();
15680 }
15681
15682 void
15683 BookEvent ()
15684 {
15685     if (appData.noChessProgram) return;
15686     switch (gameMode) {
15687       case MachinePlaysWhite:
15688         if (WhiteOnMove(forwardMostMove)) {
15689             DisplayError(_("Wait until your turn."), 0);
15690             return;
15691         }
15692         break;
15693       case BeginningOfGame:
15694       case MachinePlaysBlack:
15695         if (!WhiteOnMove(forwardMostMove)) {
15696             DisplayError(_("Wait until your turn."), 0);
15697             return;
15698         }
15699         break;
15700       case EditPosition:
15701         EditPositionDone(TRUE);
15702         break;
15703       case TwoMachinesPlay:
15704         return;
15705       default:
15706         break;
15707     }
15708     SendToProgram("bk\n", &first);
15709     bookOutput[0] = NULLCHAR;
15710     bookRequested = TRUE;
15711 }
15712
15713 void
15714 AboutGameEvent ()
15715 {
15716     char *tags = PGNTags(&gameInfo);
15717     TagsPopUp(tags, CmailMsg());
15718     free(tags);
15719 }
15720
15721 /* end button procedures */
15722
15723 void
15724 PrintPosition (FILE *fp, int move)
15725 {
15726     int i, j;
15727
15728     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15729         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15730             char c = PieceToChar(boards[move][i][j]);
15731             fputc(c == 'x' ? '.' : c, fp);
15732             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15733         }
15734     }
15735     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15736       fprintf(fp, "white to play\n");
15737     else
15738       fprintf(fp, "black to play\n");
15739 }
15740
15741 void
15742 PrintOpponents (FILE *fp)
15743 {
15744     if (gameInfo.white != NULL) {
15745         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15746     } else {
15747         fprintf(fp, "\n");
15748     }
15749 }
15750
15751 /* Find last component of program's own name, using some heuristics */
15752 void
15753 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15754 {
15755     char *p, *q, c;
15756     int local = (strcmp(host, "localhost") == 0);
15757     while (!local && (p = strchr(prog, ';')) != NULL) {
15758         p++;
15759         while (*p == ' ') p++;
15760         prog = p;
15761     }
15762     if (*prog == '"' || *prog == '\'') {
15763         q = strchr(prog + 1, *prog);
15764     } else {
15765         q = strchr(prog, ' ');
15766     }
15767     if (q == NULL) q = prog + strlen(prog);
15768     p = q;
15769     while (p >= prog && *p != '/' && *p != '\\') p--;
15770     p++;
15771     if(p == prog && *p == '"') p++;
15772     c = *q; *q = 0;
15773     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15774     memcpy(buf, p, q - p);
15775     buf[q - p] = NULLCHAR;
15776     if (!local) {
15777         strcat(buf, "@");
15778         strcat(buf, host);
15779     }
15780 }
15781
15782 char *
15783 TimeControlTagValue ()
15784 {
15785     char buf[MSG_SIZ];
15786     if (!appData.clockMode) {
15787       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15788     } else if (movesPerSession > 0) {
15789       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15790     } else if (timeIncrement == 0) {
15791       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15792     } else {
15793       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15794     }
15795     return StrSave(buf);
15796 }
15797
15798 void
15799 SetGameInfo ()
15800 {
15801     /* This routine is used only for certain modes */
15802     VariantClass v = gameInfo.variant;
15803     ChessMove r = GameUnfinished;
15804     char *p = NULL;
15805
15806     if(keepInfo) return;
15807
15808     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15809         r = gameInfo.result;
15810         p = gameInfo.resultDetails;
15811         gameInfo.resultDetails = NULL;
15812     }
15813     ClearGameInfo(&gameInfo);
15814     gameInfo.variant = v;
15815
15816     switch (gameMode) {
15817       case MachinePlaysWhite:
15818         gameInfo.event = StrSave( appData.pgnEventHeader );
15819         gameInfo.site = StrSave(HostName());
15820         gameInfo.date = PGNDate();
15821         gameInfo.round = StrSave("-");
15822         gameInfo.white = StrSave(first.tidy);
15823         gameInfo.black = StrSave(UserName());
15824         gameInfo.timeControl = TimeControlTagValue();
15825         break;
15826
15827       case MachinePlaysBlack:
15828         gameInfo.event = StrSave( appData.pgnEventHeader );
15829         gameInfo.site = StrSave(HostName());
15830         gameInfo.date = PGNDate();
15831         gameInfo.round = StrSave("-");
15832         gameInfo.white = StrSave(UserName());
15833         gameInfo.black = StrSave(first.tidy);
15834         gameInfo.timeControl = TimeControlTagValue();
15835         break;
15836
15837       case TwoMachinesPlay:
15838         gameInfo.event = StrSave( appData.pgnEventHeader );
15839         gameInfo.site = StrSave(HostName());
15840         gameInfo.date = PGNDate();
15841         if (roundNr > 0) {
15842             char buf[MSG_SIZ];
15843             snprintf(buf, MSG_SIZ, "%d", roundNr);
15844             gameInfo.round = StrSave(buf);
15845         } else {
15846             gameInfo.round = StrSave("-");
15847         }
15848         if (first.twoMachinesColor[0] == 'w') {
15849             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15850             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15851         } else {
15852             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15853             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15854         }
15855         gameInfo.timeControl = TimeControlTagValue();
15856         break;
15857
15858       case EditGame:
15859         gameInfo.event = StrSave("Edited game");
15860         gameInfo.site = StrSave(HostName());
15861         gameInfo.date = PGNDate();
15862         gameInfo.round = StrSave("-");
15863         gameInfo.white = StrSave("-");
15864         gameInfo.black = StrSave("-");
15865         gameInfo.result = r;
15866         gameInfo.resultDetails = p;
15867         break;
15868
15869       case EditPosition:
15870         gameInfo.event = StrSave("Edited position");
15871         gameInfo.site = StrSave(HostName());
15872         gameInfo.date = PGNDate();
15873         gameInfo.round = StrSave("-");
15874         gameInfo.white = StrSave("-");
15875         gameInfo.black = StrSave("-");
15876         break;
15877
15878       case IcsPlayingWhite:
15879       case IcsPlayingBlack:
15880       case IcsObserving:
15881       case IcsExamining:
15882         break;
15883
15884       case PlayFromGameFile:
15885         gameInfo.event = StrSave("Game from non-PGN file");
15886         gameInfo.site = StrSave(HostName());
15887         gameInfo.date = PGNDate();
15888         gameInfo.round = StrSave("-");
15889         gameInfo.white = StrSave("?");
15890         gameInfo.black = StrSave("?");
15891         break;
15892
15893       default:
15894         break;
15895     }
15896 }
15897
15898 void
15899 ReplaceComment (int index, char *text)
15900 {
15901     int len;
15902     char *p;
15903     float score;
15904
15905     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15906        pvInfoList[index-1].depth == len &&
15907        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15908        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15909     while (*text == '\n') text++;
15910     len = strlen(text);
15911     while (len > 0 && text[len - 1] == '\n') len--;
15912
15913     if (commentList[index] != NULL)
15914       free(commentList[index]);
15915
15916     if (len == 0) {
15917         commentList[index] = NULL;
15918         return;
15919     }
15920   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15921       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15922       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15923     commentList[index] = (char *) malloc(len + 2);
15924     strncpy(commentList[index], text, len);
15925     commentList[index][len] = '\n';
15926     commentList[index][len + 1] = NULLCHAR;
15927   } else {
15928     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15929     char *p;
15930     commentList[index] = (char *) malloc(len + 7);
15931     safeStrCpy(commentList[index], "{\n", 3);
15932     safeStrCpy(commentList[index]+2, text, len+1);
15933     commentList[index][len+2] = NULLCHAR;
15934     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15935     strcat(commentList[index], "\n}\n");
15936   }
15937 }
15938
15939 void
15940 CrushCRs (char *text)
15941 {
15942   char *p = text;
15943   char *q = text;
15944   char ch;
15945
15946   do {
15947     ch = *p++;
15948     if (ch == '\r') continue;
15949     *q++ = ch;
15950   } while (ch != '\0');
15951 }
15952
15953 void
15954 AppendComment (int index, char *text, Boolean addBraces)
15955 /* addBraces  tells if we should add {} */
15956 {
15957     int oldlen, len;
15958     char *old;
15959
15960 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15961     if(addBraces == 3) addBraces = 0; else // force appending literally
15962     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15963
15964     CrushCRs(text);
15965     while (*text == '\n') text++;
15966     len = strlen(text);
15967     while (len > 0 && text[len - 1] == '\n') len--;
15968     text[len] = NULLCHAR;
15969
15970     if (len == 0) return;
15971
15972     if (commentList[index] != NULL) {
15973       Boolean addClosingBrace = addBraces;
15974         old = commentList[index];
15975         oldlen = strlen(old);
15976         while(commentList[index][oldlen-1] ==  '\n')
15977           commentList[index][--oldlen] = NULLCHAR;
15978         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15979         safeStrCpy(commentList[index], old, oldlen + len + 6);
15980         free(old);
15981         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15982         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15983           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15984           while (*text == '\n') { text++; len--; }
15985           commentList[index][--oldlen] = NULLCHAR;
15986       }
15987         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15988         else          strcat(commentList[index], "\n");
15989         strcat(commentList[index], text);
15990         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15991         else          strcat(commentList[index], "\n");
15992     } else {
15993         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15994         if(addBraces)
15995           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15996         else commentList[index][0] = NULLCHAR;
15997         strcat(commentList[index], text);
15998         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15999         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16000     }
16001 }
16002
16003 static char *
16004 FindStr (char * text, char * sub_text)
16005 {
16006     char * result = strstr( text, sub_text );
16007
16008     if( result != NULL ) {
16009         result += strlen( sub_text );
16010     }
16011
16012     return result;
16013 }
16014
16015 /* [AS] Try to extract PV info from PGN comment */
16016 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16017 char *
16018 GetInfoFromComment (int index, char * text)
16019 {
16020     char * sep = text, *p;
16021
16022     if( text != NULL && index > 0 ) {
16023         int score = 0;
16024         int depth = 0;
16025         int time = -1, sec = 0, deci;
16026         char * s_eval = FindStr( text, "[%eval " );
16027         char * s_emt = FindStr( text, "[%emt " );
16028 #if 0
16029         if( s_eval != NULL || s_emt != NULL ) {
16030 #else
16031         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16032 #endif
16033             /* New style */
16034             char delim;
16035
16036             if( s_eval != NULL ) {
16037                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16038                     return text;
16039                 }
16040
16041                 if( delim != ']' ) {
16042                     return text;
16043                 }
16044             }
16045
16046             if( s_emt != NULL ) {
16047             }
16048                 return text;
16049         }
16050         else {
16051             /* We expect something like: [+|-]nnn.nn/dd */
16052             int score_lo = 0;
16053
16054             if(*text != '{') return text; // [HGM] braces: must be normal comment
16055
16056             sep = strchr( text, '/' );
16057             if( sep == NULL || sep < (text+4) ) {
16058                 return text;
16059             }
16060
16061             p = text;
16062             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16063             if(p[1] == '(') { // comment starts with PV
16064                p = strchr(p, ')'); // locate end of PV
16065                if(p == NULL || sep < p+5) return text;
16066                // at this point we have something like "{(.*) +0.23/6 ..."
16067                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16068                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16069                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16070             }
16071             time = -1; sec = -1; deci = -1;
16072             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16073                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16074                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16075                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16076                 return text;
16077             }
16078
16079             if( score_lo < 0 || score_lo >= 100 ) {
16080                 return text;
16081             }
16082
16083             if(sec >= 0) time = 600*time + 10*sec; else
16084             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16085
16086             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16087
16088             /* [HGM] PV time: now locate end of PV info */
16089             while( *++sep >= '0' && *sep <= '9'); // strip depth
16090             if(time >= 0)
16091             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16092             if(sec >= 0)
16093             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16094             if(deci >= 0)
16095             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16096             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16097         }
16098
16099         if( depth <= 0 ) {
16100             return text;
16101         }
16102
16103         if( time < 0 ) {
16104             time = -1;
16105         }
16106
16107         pvInfoList[index-1].depth = depth;
16108         pvInfoList[index-1].score = score;
16109         pvInfoList[index-1].time  = 10*time; // centi-sec
16110         if(*sep == '}') *sep = 0; else *--sep = '{';
16111         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16112     }
16113     return sep;
16114 }
16115
16116 void
16117 SendToProgram (char *message, ChessProgramState *cps)
16118 {
16119     int count, outCount, error;
16120     char buf[MSG_SIZ];
16121
16122     if (cps->pr == NoProc) return;
16123     Attention(cps);
16124
16125     if (appData.debugMode) {
16126         TimeMark now;
16127         GetTimeMark(&now);
16128         fprintf(debugFP, "%ld >%-6s: %s",
16129                 SubtractTimeMarks(&now, &programStartTime),
16130                 cps->which, message);
16131         if(serverFP)
16132             fprintf(serverFP, "%ld >%-6s: %s",
16133                 SubtractTimeMarks(&now, &programStartTime),
16134                 cps->which, message), fflush(serverFP);
16135     }
16136
16137     count = strlen(message);
16138     outCount = OutputToProcess(cps->pr, message, count, &error);
16139     if (outCount < count && !exiting
16140                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16141       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16142       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16143         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16144             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16145                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16146                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16147                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16148             } else {
16149                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16150                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16151                 gameInfo.result = res;
16152             }
16153             gameInfo.resultDetails = StrSave(buf);
16154         }
16155         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16156         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16157     }
16158 }
16159
16160 void
16161 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16162 {
16163     char *end_str;
16164     char buf[MSG_SIZ];
16165     ChessProgramState *cps = (ChessProgramState *)closure;
16166
16167     if (isr != cps->isr) return; /* Killed intentionally */
16168     if (count <= 0) {
16169         if (count == 0) {
16170             RemoveInputSource(cps->isr);
16171             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16172                     _(cps->which), cps->program);
16173             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16174             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16175                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16176                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16177                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16178                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16179                 } else {
16180                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16181                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16182                     gameInfo.result = res;
16183                 }
16184                 gameInfo.resultDetails = StrSave(buf);
16185             }
16186             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16187             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16188         } else {
16189             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16190                     _(cps->which), cps->program);
16191             RemoveInputSource(cps->isr);
16192
16193             /* [AS] Program is misbehaving badly... kill it */
16194             if( count == -2 ) {
16195                 DestroyChildProcess( cps->pr, 9 );
16196                 cps->pr = NoProc;
16197             }
16198
16199             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16200         }
16201         return;
16202     }
16203
16204     if ((end_str = strchr(message, '\r')) != NULL)
16205       *end_str = NULLCHAR;
16206     if ((end_str = strchr(message, '\n')) != NULL)
16207       *end_str = NULLCHAR;
16208
16209     if (appData.debugMode) {
16210         TimeMark now; int print = 1;
16211         char *quote = ""; char c; int i;
16212
16213         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16214                 char start = message[0];
16215                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16216                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16217                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16218                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16219                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16220                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16221                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16222                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16223                    sscanf(message, "hint: %c", &c)!=1 &&
16224                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16225                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16226                     print = (appData.engineComments >= 2);
16227                 }
16228                 message[0] = start; // restore original message
16229         }
16230         if(print) {
16231                 GetTimeMark(&now);
16232                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16233                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16234                         quote,
16235                         message);
16236                 if(serverFP)
16237                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16238                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16239                         quote,
16240                         message), fflush(serverFP);
16241         }
16242     }
16243
16244     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16245     if (appData.icsEngineAnalyze) {
16246         if (strstr(message, "whisper") != NULL ||
16247              strstr(message, "kibitz") != NULL ||
16248             strstr(message, "tellics") != NULL) return;
16249     }
16250
16251     HandleMachineMove(message, cps);
16252 }
16253
16254
16255 void
16256 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16257 {
16258     char buf[MSG_SIZ];
16259     int seconds;
16260
16261     if( timeControl_2 > 0 ) {
16262         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16263             tc = timeControl_2;
16264         }
16265     }
16266     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16267     inc /= cps->timeOdds;
16268     st  /= cps->timeOdds;
16269
16270     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16271
16272     if (st > 0) {
16273       /* Set exact time per move, normally using st command */
16274       if (cps->stKludge) {
16275         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16276         seconds = st % 60;
16277         if (seconds == 0) {
16278           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16279         } else {
16280           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16281         }
16282       } else {
16283         snprintf(buf, MSG_SIZ, "st %d\n", st);
16284       }
16285     } else {
16286       /* Set conventional or incremental time control, using level command */
16287       if (seconds == 0) {
16288         /* Note old gnuchess bug -- minutes:seconds used to not work.
16289            Fixed in later versions, but still avoid :seconds
16290            when seconds is 0. */
16291         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16292       } else {
16293         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16294                  seconds, inc/1000.);
16295       }
16296     }
16297     SendToProgram(buf, cps);
16298
16299     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16300     /* Orthogonally, limit search to given depth */
16301     if (sd > 0) {
16302       if (cps->sdKludge) {
16303         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16304       } else {
16305         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16306       }
16307       SendToProgram(buf, cps);
16308     }
16309
16310     if(cps->nps >= 0) { /* [HGM] nps */
16311         if(cps->supportsNPS == FALSE)
16312           cps->nps = -1; // don't use if engine explicitly says not supported!
16313         else {
16314           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16315           SendToProgram(buf, cps);
16316         }
16317     }
16318 }
16319
16320 ChessProgramState *
16321 WhitePlayer ()
16322 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16323 {
16324     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16325        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16326         return &second;
16327     return &first;
16328 }
16329
16330 void
16331 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16332 {
16333     char message[MSG_SIZ];
16334     long time, otime;
16335
16336     /* Note: this routine must be called when the clocks are stopped
16337        or when they have *just* been set or switched; otherwise
16338        it will be off by the time since the current tick started.
16339     */
16340     if (machineWhite) {
16341         time = whiteTimeRemaining / 10;
16342         otime = blackTimeRemaining / 10;
16343     } else {
16344         time = blackTimeRemaining / 10;
16345         otime = whiteTimeRemaining / 10;
16346     }
16347     /* [HGM] translate opponent's time by time-odds factor */
16348     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16349
16350     if (time <= 0) time = 1;
16351     if (otime <= 0) otime = 1;
16352
16353     snprintf(message, MSG_SIZ, "time %ld\n", time);
16354     SendToProgram(message, cps);
16355
16356     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16357     SendToProgram(message, cps);
16358 }
16359
16360 char *
16361 EngineDefinedVariant (ChessProgramState *cps, int n)
16362 {   // return name of n-th unknown variant that engine supports
16363     static char buf[MSG_SIZ];
16364     char *p, *s = cps->variants;
16365     if(!s) return NULL;
16366     do { // parse string from variants feature
16367       VariantClass v;
16368         p = strchr(s, ',');
16369         if(p) *p = NULLCHAR;
16370       v = StringToVariant(s);
16371       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16372         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16373             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16374         }
16375         if(p) *p++ = ',';
16376         if(n < 0) return buf;
16377     } while(s = p);
16378     return NULL;
16379 }
16380
16381 int
16382 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16383 {
16384   char buf[MSG_SIZ];
16385   int len = strlen(name);
16386   int val;
16387
16388   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16389     (*p) += len + 1;
16390     sscanf(*p, "%d", &val);
16391     *loc = (val != 0);
16392     while (**p && **p != ' ')
16393       (*p)++;
16394     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16395     SendToProgram(buf, cps);
16396     return TRUE;
16397   }
16398   return FALSE;
16399 }
16400
16401 int
16402 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16403 {
16404   char buf[MSG_SIZ];
16405   int len = strlen(name);
16406   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16407     (*p) += len + 1;
16408     sscanf(*p, "%d", loc);
16409     while (**p && **p != ' ') (*p)++;
16410     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16411     SendToProgram(buf, cps);
16412     return TRUE;
16413   }
16414   return FALSE;
16415 }
16416
16417 int
16418 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16419 {
16420   char buf[MSG_SIZ];
16421   int len = strlen(name);
16422   if (strncmp((*p), name, len) == 0
16423       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16424     (*p) += len + 2;
16425     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16426     sscanf(*p, "%[^\"]", *loc);
16427     while (**p && **p != '\"') (*p)++;
16428     if (**p == '\"') (*p)++;
16429     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16430     SendToProgram(buf, cps);
16431     return TRUE;
16432   }
16433   return FALSE;
16434 }
16435
16436 int
16437 ParseOption (Option *opt, ChessProgramState *cps)
16438 // [HGM] options: process the string that defines an engine option, and determine
16439 // name, type, default value, and allowed value range
16440 {
16441         char *p, *q, buf[MSG_SIZ];
16442         int n, min = (-1)<<31, max = 1<<31, def;
16443
16444         if(p = strstr(opt->name, " -spin ")) {
16445             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16446             if(max < min) max = min; // enforce consistency
16447             if(def < min) def = min;
16448             if(def > max) def = max;
16449             opt->value = def;
16450             opt->min = min;
16451             opt->max = max;
16452             opt->type = Spin;
16453         } else if((p = strstr(opt->name, " -slider "))) {
16454             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16455             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16456             if(max < min) max = min; // enforce consistency
16457             if(def < min) def = min;
16458             if(def > max) def = max;
16459             opt->value = def;
16460             opt->min = min;
16461             opt->max = max;
16462             opt->type = Spin; // Slider;
16463         } else if((p = strstr(opt->name, " -string "))) {
16464             opt->textValue = p+9;
16465             opt->type = TextBox;
16466         } else if((p = strstr(opt->name, " -file "))) {
16467             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16468             opt->textValue = p+7;
16469             opt->type = FileName; // FileName;
16470         } else if((p = strstr(opt->name, " -path "))) {
16471             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16472             opt->textValue = p+7;
16473             opt->type = PathName; // PathName;
16474         } else if(p = strstr(opt->name, " -check ")) {
16475             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16476             opt->value = (def != 0);
16477             opt->type = CheckBox;
16478         } else if(p = strstr(opt->name, " -combo ")) {
16479             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16480             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16481             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16482             opt->value = n = 0;
16483             while(q = StrStr(q, " /// ")) {
16484                 n++; *q = 0;    // count choices, and null-terminate each of them
16485                 q += 5;
16486                 if(*q == '*') { // remember default, which is marked with * prefix
16487                     q++;
16488                     opt->value = n;
16489                 }
16490                 cps->comboList[cps->comboCnt++] = q;
16491             }
16492             cps->comboList[cps->comboCnt++] = NULL;
16493             opt->max = n + 1;
16494             opt->type = ComboBox;
16495         } else if(p = strstr(opt->name, " -button")) {
16496             opt->type = Button;
16497         } else if(p = strstr(opt->name, " -save")) {
16498             opt->type = SaveButton;
16499         } else return FALSE;
16500         *p = 0; // terminate option name
16501         // now look if the command-line options define a setting for this engine option.
16502         if(cps->optionSettings && cps->optionSettings[0])
16503             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16504         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16505           snprintf(buf, MSG_SIZ, "option %s", p);
16506                 if(p = strstr(buf, ",")) *p = 0;
16507                 if(q = strchr(buf, '=')) switch(opt->type) {
16508                     case ComboBox:
16509                         for(n=0; n<opt->max; n++)
16510                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16511                         break;
16512                     case TextBox:
16513                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16514                         break;
16515                     case Spin:
16516                     case CheckBox:
16517                         opt->value = atoi(q+1);
16518                     default:
16519                         break;
16520                 }
16521                 strcat(buf, "\n");
16522                 SendToProgram(buf, cps);
16523         }
16524         return TRUE;
16525 }
16526
16527 void
16528 FeatureDone (ChessProgramState *cps, int val)
16529 {
16530   DelayedEventCallback cb = GetDelayedEvent();
16531   if ((cb == InitBackEnd3 && cps == &first) ||
16532       (cb == SettingsMenuIfReady && cps == &second) ||
16533       (cb == LoadEngine) ||
16534       (cb == TwoMachinesEventIfReady)) {
16535     CancelDelayedEvent();
16536     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16537   }
16538   cps->initDone = val;
16539   if(val) cps->reload = FALSE;
16540 }
16541
16542 /* Parse feature command from engine */
16543 void
16544 ParseFeatures (char *args, ChessProgramState *cps)
16545 {
16546   char *p = args;
16547   char *q = NULL;
16548   int val;
16549   char buf[MSG_SIZ];
16550
16551   for (;;) {
16552     while (*p == ' ') p++;
16553     if (*p == NULLCHAR) return;
16554
16555     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16556     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16557     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16558     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16559     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16560     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16561     if (BoolFeature(&p, "reuse", &val, cps)) {
16562       /* Engine can disable reuse, but can't enable it if user said no */
16563       if (!val) cps->reuse = FALSE;
16564       continue;
16565     }
16566     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16567     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16568       if (gameMode == TwoMachinesPlay) {
16569         DisplayTwoMachinesTitle();
16570       } else {
16571         DisplayTitle("");
16572       }
16573       continue;
16574     }
16575     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16576     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16577     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16578     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16579     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16580     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16581     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16582     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16583     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16584     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16585     if (IntFeature(&p, "done", &val, cps)) {
16586       FeatureDone(cps, val);
16587       continue;
16588     }
16589     /* Added by Tord: */
16590     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16591     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16592     /* End of additions by Tord */
16593
16594     /* [HGM] added features: */
16595     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16596     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16597     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16598     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16599     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16600     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16601     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16602     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16603         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16604         FREE(cps->option[cps->nrOptions].name);
16605         cps->option[cps->nrOptions].name = q; q = NULL;
16606         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16607           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16608             SendToProgram(buf, cps);
16609             continue;
16610         }
16611         if(cps->nrOptions >= MAX_OPTIONS) {
16612             cps->nrOptions--;
16613             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16614             DisplayError(buf, 0);
16615         }
16616         continue;
16617     }
16618     /* End of additions by HGM */
16619
16620     /* unknown feature: complain and skip */
16621     q = p;
16622     while (*q && *q != '=') q++;
16623     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16624     SendToProgram(buf, cps);
16625     p = q;
16626     if (*p == '=') {
16627       p++;
16628       if (*p == '\"') {
16629         p++;
16630         while (*p && *p != '\"') p++;
16631         if (*p == '\"') p++;
16632       } else {
16633         while (*p && *p != ' ') p++;
16634       }
16635     }
16636   }
16637
16638 }
16639
16640 void
16641 PeriodicUpdatesEvent (int newState)
16642 {
16643     if (newState == appData.periodicUpdates)
16644       return;
16645
16646     appData.periodicUpdates=newState;
16647
16648     /* Display type changes, so update it now */
16649 //    DisplayAnalysis();
16650
16651     /* Get the ball rolling again... */
16652     if (newState) {
16653         AnalysisPeriodicEvent(1);
16654         StartAnalysisClock();
16655     }
16656 }
16657
16658 void
16659 PonderNextMoveEvent (int newState)
16660 {
16661     if (newState == appData.ponderNextMove) return;
16662     if (gameMode == EditPosition) EditPositionDone(TRUE);
16663     if (newState) {
16664         SendToProgram("hard\n", &first);
16665         if (gameMode == TwoMachinesPlay) {
16666             SendToProgram("hard\n", &second);
16667         }
16668     } else {
16669         SendToProgram("easy\n", &first);
16670         thinkOutput[0] = NULLCHAR;
16671         if (gameMode == TwoMachinesPlay) {
16672             SendToProgram("easy\n", &second);
16673         }
16674     }
16675     appData.ponderNextMove = newState;
16676 }
16677
16678 void
16679 NewSettingEvent (int option, int *feature, char *command, int value)
16680 {
16681     char buf[MSG_SIZ];
16682
16683     if (gameMode == EditPosition) EditPositionDone(TRUE);
16684     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16685     if(feature == NULL || *feature) SendToProgram(buf, &first);
16686     if (gameMode == TwoMachinesPlay) {
16687         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16688     }
16689 }
16690
16691 void
16692 ShowThinkingEvent ()
16693 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16694 {
16695     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16696     int newState = appData.showThinking
16697         // [HGM] thinking: other features now need thinking output as well
16698         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16699
16700     if (oldState == newState) return;
16701     oldState = newState;
16702     if (gameMode == EditPosition) EditPositionDone(TRUE);
16703     if (oldState) {
16704         SendToProgram("post\n", &first);
16705         if (gameMode == TwoMachinesPlay) {
16706             SendToProgram("post\n", &second);
16707         }
16708     } else {
16709         SendToProgram("nopost\n", &first);
16710         thinkOutput[0] = NULLCHAR;
16711         if (gameMode == TwoMachinesPlay) {
16712             SendToProgram("nopost\n", &second);
16713         }
16714     }
16715 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16716 }
16717
16718 void
16719 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16720 {
16721   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16722   if (pr == NoProc) return;
16723   AskQuestion(title, question, replyPrefix, pr);
16724 }
16725
16726 void
16727 TypeInEvent (char firstChar)
16728 {
16729     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16730         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16731         gameMode == AnalyzeMode || gameMode == EditGame ||
16732         gameMode == EditPosition || gameMode == IcsExamining ||
16733         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16734         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16735                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16736                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16737         gameMode == Training) PopUpMoveDialog(firstChar);
16738 }
16739
16740 void
16741 TypeInDoneEvent (char *move)
16742 {
16743         Board board;
16744         int n, fromX, fromY, toX, toY;
16745         char promoChar;
16746         ChessMove moveType;
16747
16748         // [HGM] FENedit
16749         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16750                 EditPositionPasteFEN(move);
16751                 return;
16752         }
16753         // [HGM] movenum: allow move number to be typed in any mode
16754         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16755           ToNrEvent(2*n-1);
16756           return;
16757         }
16758         // undocumented kludge: allow command-line option to be typed in!
16759         // (potentially fatal, and does not implement the effect of the option.)
16760         // should only be used for options that are values on which future decisions will be made,
16761         // and definitely not on options that would be used during initialization.
16762         if(strstr(move, "!!! -") == move) {
16763             ParseArgsFromString(move+4);
16764             return;
16765         }
16766
16767       if (gameMode != EditGame && currentMove != forwardMostMove &&
16768         gameMode != Training) {
16769         DisplayMoveError(_("Displayed move is not current"));
16770       } else {
16771         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16772           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16773         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16774         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16775           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16776           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16777         } else {
16778           DisplayMoveError(_("Could not parse move"));
16779         }
16780       }
16781 }
16782
16783 void
16784 DisplayMove (int moveNumber)
16785 {
16786     char message[MSG_SIZ];
16787     char res[MSG_SIZ];
16788     char cpThinkOutput[MSG_SIZ];
16789
16790     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16791
16792     if (moveNumber == forwardMostMove - 1 ||
16793         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16794
16795         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16796
16797         if (strchr(cpThinkOutput, '\n')) {
16798             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16799         }
16800     } else {
16801         *cpThinkOutput = NULLCHAR;
16802     }
16803
16804     /* [AS] Hide thinking from human user */
16805     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16806         *cpThinkOutput = NULLCHAR;
16807         if( thinkOutput[0] != NULLCHAR ) {
16808             int i;
16809
16810             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16811                 cpThinkOutput[i] = '.';
16812             }
16813             cpThinkOutput[i] = NULLCHAR;
16814             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16815         }
16816     }
16817
16818     if (moveNumber == forwardMostMove - 1 &&
16819         gameInfo.resultDetails != NULL) {
16820         if (gameInfo.resultDetails[0] == NULLCHAR) {
16821           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16822         } else {
16823           snprintf(res, MSG_SIZ, " {%s} %s",
16824                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16825         }
16826     } else {
16827         res[0] = NULLCHAR;
16828     }
16829
16830     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16831         DisplayMessage(res, cpThinkOutput);
16832     } else {
16833       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16834                 WhiteOnMove(moveNumber) ? " " : ".. ",
16835                 parseList[moveNumber], res);
16836         DisplayMessage(message, cpThinkOutput);
16837     }
16838 }
16839
16840 void
16841 DisplayComment (int moveNumber, char *text)
16842 {
16843     char title[MSG_SIZ];
16844
16845     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16846       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16847     } else {
16848       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16849               WhiteOnMove(moveNumber) ? " " : ".. ",
16850               parseList[moveNumber]);
16851     }
16852     if (text != NULL && (appData.autoDisplayComment || commentUp))
16853         CommentPopUp(title, text);
16854 }
16855
16856 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16857  * might be busy thinking or pondering.  It can be omitted if your
16858  * gnuchess is configured to stop thinking immediately on any user
16859  * input.  However, that gnuchess feature depends on the FIONREAD
16860  * ioctl, which does not work properly on some flavors of Unix.
16861  */
16862 void
16863 Attention (ChessProgramState *cps)
16864 {
16865 #if ATTENTION
16866     if (!cps->useSigint) return;
16867     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16868     switch (gameMode) {
16869       case MachinePlaysWhite:
16870       case MachinePlaysBlack:
16871       case TwoMachinesPlay:
16872       case IcsPlayingWhite:
16873       case IcsPlayingBlack:
16874       case AnalyzeMode:
16875       case AnalyzeFile:
16876         /* Skip if we know it isn't thinking */
16877         if (!cps->maybeThinking) return;
16878         if (appData.debugMode)
16879           fprintf(debugFP, "Interrupting %s\n", cps->which);
16880         InterruptChildProcess(cps->pr);
16881         cps->maybeThinking = FALSE;
16882         break;
16883       default:
16884         break;
16885     }
16886 #endif /*ATTENTION*/
16887 }
16888
16889 int
16890 CheckFlags ()
16891 {
16892     if (whiteTimeRemaining <= 0) {
16893         if (!whiteFlag) {
16894             whiteFlag = TRUE;
16895             if (appData.icsActive) {
16896                 if (appData.autoCallFlag &&
16897                     gameMode == IcsPlayingBlack && !blackFlag) {
16898                   SendToICS(ics_prefix);
16899                   SendToICS("flag\n");
16900                 }
16901             } else {
16902                 if (blackFlag) {
16903                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16904                 } else {
16905                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16906                     if (appData.autoCallFlag) {
16907                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16908                         return TRUE;
16909                     }
16910                 }
16911             }
16912         }
16913     }
16914     if (blackTimeRemaining <= 0) {
16915         if (!blackFlag) {
16916             blackFlag = TRUE;
16917             if (appData.icsActive) {
16918                 if (appData.autoCallFlag &&
16919                     gameMode == IcsPlayingWhite && !whiteFlag) {
16920                   SendToICS(ics_prefix);
16921                   SendToICS("flag\n");
16922                 }
16923             } else {
16924                 if (whiteFlag) {
16925                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16926                 } else {
16927                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16928                     if (appData.autoCallFlag) {
16929                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16930                         return TRUE;
16931                     }
16932                 }
16933             }
16934         }
16935     }
16936     return FALSE;
16937 }
16938
16939 void
16940 CheckTimeControl ()
16941 {
16942     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16943         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16944
16945     /*
16946      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16947      */
16948     if ( !WhiteOnMove(forwardMostMove) ) {
16949         /* White made time control */
16950         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16951         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16952         /* [HGM] time odds: correct new time quota for time odds! */
16953                                             / WhitePlayer()->timeOdds;
16954         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16955     } else {
16956         lastBlack -= blackTimeRemaining;
16957         /* Black made time control */
16958         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16959                                             / WhitePlayer()->other->timeOdds;
16960         lastWhite = whiteTimeRemaining;
16961     }
16962 }
16963
16964 void
16965 DisplayBothClocks ()
16966 {
16967     int wom = gameMode == EditPosition ?
16968       !blackPlaysFirst : WhiteOnMove(currentMove);
16969     DisplayWhiteClock(whiteTimeRemaining, wom);
16970     DisplayBlackClock(blackTimeRemaining, !wom);
16971 }
16972
16973
16974 /* Timekeeping seems to be a portability nightmare.  I think everyone
16975    has ftime(), but I'm really not sure, so I'm including some ifdefs
16976    to use other calls if you don't.  Clocks will be less accurate if
16977    you have neither ftime nor gettimeofday.
16978 */
16979
16980 /* VS 2008 requires the #include outside of the function */
16981 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16982 #include <sys/timeb.h>
16983 #endif
16984
16985 /* Get the current time as a TimeMark */
16986 void
16987 GetTimeMark (TimeMark *tm)
16988 {
16989 #if HAVE_GETTIMEOFDAY
16990
16991     struct timeval timeVal;
16992     struct timezone timeZone;
16993
16994     gettimeofday(&timeVal, &timeZone);
16995     tm->sec = (long) timeVal.tv_sec;
16996     tm->ms = (int) (timeVal.tv_usec / 1000L);
16997
16998 #else /*!HAVE_GETTIMEOFDAY*/
16999 #if HAVE_FTIME
17000
17001 // include <sys/timeb.h> / moved to just above start of function
17002     struct timeb timeB;
17003
17004     ftime(&timeB);
17005     tm->sec = (long) timeB.time;
17006     tm->ms = (int) timeB.millitm;
17007
17008 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17009     tm->sec = (long) time(NULL);
17010     tm->ms = 0;
17011 #endif
17012 #endif
17013 }
17014
17015 /* Return the difference in milliseconds between two
17016    time marks.  We assume the difference will fit in a long!
17017 */
17018 long
17019 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17020 {
17021     return 1000L*(tm2->sec - tm1->sec) +
17022            (long) (tm2->ms - tm1->ms);
17023 }
17024
17025
17026 /*
17027  * Code to manage the game clocks.
17028  *
17029  * In tournament play, black starts the clock and then white makes a move.
17030  * We give the human user a slight advantage if he is playing white---the
17031  * clocks don't run until he makes his first move, so it takes zero time.
17032  * Also, we don't account for network lag, so we could get out of sync
17033  * with GNU Chess's clock -- but then, referees are always right.
17034  */
17035
17036 static TimeMark tickStartTM;
17037 static long intendedTickLength;
17038
17039 long
17040 NextTickLength (long timeRemaining)
17041 {
17042     long nominalTickLength, nextTickLength;
17043
17044     if (timeRemaining > 0L && timeRemaining <= 10000L)
17045       nominalTickLength = 100L;
17046     else
17047       nominalTickLength = 1000L;
17048     nextTickLength = timeRemaining % nominalTickLength;
17049     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17050
17051     return nextTickLength;
17052 }
17053
17054 /* Adjust clock one minute up or down */
17055 void
17056 AdjustClock (Boolean which, int dir)
17057 {
17058     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17059     if(which) blackTimeRemaining += 60000*dir;
17060     else      whiteTimeRemaining += 60000*dir;
17061     DisplayBothClocks();
17062     adjustedClock = TRUE;
17063 }
17064
17065 /* Stop clocks and reset to a fresh time control */
17066 void
17067 ResetClocks ()
17068 {
17069     (void) StopClockTimer();
17070     if (appData.icsActive) {
17071         whiteTimeRemaining = blackTimeRemaining = 0;
17072     } else if (searchTime) {
17073         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17074         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17075     } else { /* [HGM] correct new time quote for time odds */
17076         whiteTC = blackTC = fullTimeControlString;
17077         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17078         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17079     }
17080     if (whiteFlag || blackFlag) {
17081         DisplayTitle("");
17082         whiteFlag = blackFlag = FALSE;
17083     }
17084     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17085     DisplayBothClocks();
17086     adjustedClock = FALSE;
17087 }
17088
17089 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17090
17091 /* Decrement running clock by amount of time that has passed */
17092 void
17093 DecrementClocks ()
17094 {
17095     long timeRemaining;
17096     long lastTickLength, fudge;
17097     TimeMark now;
17098
17099     if (!appData.clockMode) return;
17100     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17101
17102     GetTimeMark(&now);
17103
17104     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17105
17106     /* Fudge if we woke up a little too soon */
17107     fudge = intendedTickLength - lastTickLength;
17108     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17109
17110     if (WhiteOnMove(forwardMostMove)) {
17111         if(whiteNPS >= 0) lastTickLength = 0;
17112         timeRemaining = whiteTimeRemaining -= lastTickLength;
17113         if(timeRemaining < 0 && !appData.icsActive) {
17114             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17115             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17116                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17117                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17118             }
17119         }
17120         DisplayWhiteClock(whiteTimeRemaining - fudge,
17121                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17122     } else {
17123         if(blackNPS >= 0) lastTickLength = 0;
17124         timeRemaining = blackTimeRemaining -= lastTickLength;
17125         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17126             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17127             if(suddenDeath) {
17128                 blackStartMove = forwardMostMove;
17129                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17130             }
17131         }
17132         DisplayBlackClock(blackTimeRemaining - fudge,
17133                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17134     }
17135     if (CheckFlags()) return;
17136
17137     if(twoBoards) { // count down secondary board's clocks as well
17138         activePartnerTime -= lastTickLength;
17139         partnerUp = 1;
17140         if(activePartner == 'W')
17141             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17142         else
17143             DisplayBlackClock(activePartnerTime, TRUE);
17144         partnerUp = 0;
17145     }
17146
17147     tickStartTM = now;
17148     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17149     StartClockTimer(intendedTickLength);
17150
17151     /* if the time remaining has fallen below the alarm threshold, sound the
17152      * alarm. if the alarm has sounded and (due to a takeback or time control
17153      * with increment) the time remaining has increased to a level above the
17154      * threshold, reset the alarm so it can sound again.
17155      */
17156
17157     if (appData.icsActive && appData.icsAlarm) {
17158
17159         /* make sure we are dealing with the user's clock */
17160         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17161                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17162            )) return;
17163
17164         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17165             alarmSounded = FALSE;
17166         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17167             PlayAlarmSound();
17168             alarmSounded = TRUE;
17169         }
17170     }
17171 }
17172
17173
17174 /* A player has just moved, so stop the previously running
17175    clock and (if in clock mode) start the other one.
17176    We redisplay both clocks in case we're in ICS mode, because
17177    ICS gives us an update to both clocks after every move.
17178    Note that this routine is called *after* forwardMostMove
17179    is updated, so the last fractional tick must be subtracted
17180    from the color that is *not* on move now.
17181 */
17182 void
17183 SwitchClocks (int newMoveNr)
17184 {
17185     long lastTickLength;
17186     TimeMark now;
17187     int flagged = FALSE;
17188
17189     GetTimeMark(&now);
17190
17191     if (StopClockTimer() && appData.clockMode) {
17192         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17193         if (!WhiteOnMove(forwardMostMove)) {
17194             if(blackNPS >= 0) lastTickLength = 0;
17195             blackTimeRemaining -= lastTickLength;
17196            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17197 //         if(pvInfoList[forwardMostMove].time == -1)
17198                  pvInfoList[forwardMostMove].time =               // use GUI time
17199                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17200         } else {
17201            if(whiteNPS >= 0) lastTickLength = 0;
17202            whiteTimeRemaining -= lastTickLength;
17203            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17204 //         if(pvInfoList[forwardMostMove].time == -1)
17205                  pvInfoList[forwardMostMove].time =
17206                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17207         }
17208         flagged = CheckFlags();
17209     }
17210     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17211     CheckTimeControl();
17212
17213     if (flagged || !appData.clockMode) return;
17214
17215     switch (gameMode) {
17216       case MachinePlaysBlack:
17217       case MachinePlaysWhite:
17218       case BeginningOfGame:
17219         if (pausing) return;
17220         break;
17221
17222       case EditGame:
17223       case PlayFromGameFile:
17224       case IcsExamining:
17225         return;
17226
17227       default:
17228         break;
17229     }
17230
17231     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17232         if(WhiteOnMove(forwardMostMove))
17233              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17234         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17235     }
17236
17237     tickStartTM = now;
17238     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17239       whiteTimeRemaining : blackTimeRemaining);
17240     StartClockTimer(intendedTickLength);
17241 }
17242
17243
17244 /* Stop both clocks */
17245 void
17246 StopClocks ()
17247 {
17248     long lastTickLength;
17249     TimeMark now;
17250
17251     if (!StopClockTimer()) return;
17252     if (!appData.clockMode) return;
17253
17254     GetTimeMark(&now);
17255
17256     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17257     if (WhiteOnMove(forwardMostMove)) {
17258         if(whiteNPS >= 0) lastTickLength = 0;
17259         whiteTimeRemaining -= lastTickLength;
17260         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17261     } else {
17262         if(blackNPS >= 0) lastTickLength = 0;
17263         blackTimeRemaining -= lastTickLength;
17264         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17265     }
17266     CheckFlags();
17267 }
17268
17269 /* Start clock of player on move.  Time may have been reset, so
17270    if clock is already running, stop and restart it. */
17271 void
17272 StartClocks ()
17273 {
17274     (void) StopClockTimer(); /* in case it was running already */
17275     DisplayBothClocks();
17276     if (CheckFlags()) return;
17277
17278     if (!appData.clockMode) return;
17279     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17280
17281     GetTimeMark(&tickStartTM);
17282     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17283       whiteTimeRemaining : blackTimeRemaining);
17284
17285    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17286     whiteNPS = blackNPS = -1;
17287     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17288        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17289         whiteNPS = first.nps;
17290     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17291        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17292         blackNPS = first.nps;
17293     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17294         whiteNPS = second.nps;
17295     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17296         blackNPS = second.nps;
17297     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17298
17299     StartClockTimer(intendedTickLength);
17300 }
17301
17302 char *
17303 TimeString (long ms)
17304 {
17305     long second, minute, hour, day;
17306     char *sign = "";
17307     static char buf[32];
17308
17309     if (ms > 0 && ms <= 9900) {
17310       /* convert milliseconds to tenths, rounding up */
17311       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17312
17313       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17314       return buf;
17315     }
17316
17317     /* convert milliseconds to seconds, rounding up */
17318     /* use floating point to avoid strangeness of integer division
17319        with negative dividends on many machines */
17320     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17321
17322     if (second < 0) {
17323         sign = "-";
17324         second = -second;
17325     }
17326
17327     day = second / (60 * 60 * 24);
17328     second = second % (60 * 60 * 24);
17329     hour = second / (60 * 60);
17330     second = second % (60 * 60);
17331     minute = second / 60;
17332     second = second % 60;
17333
17334     if (day > 0)
17335       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17336               sign, day, hour, minute, second);
17337     else if (hour > 0)
17338       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17339     else
17340       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17341
17342     return buf;
17343 }
17344
17345
17346 /*
17347  * This is necessary because some C libraries aren't ANSI C compliant yet.
17348  */
17349 char *
17350 StrStr (char *string, char *match)
17351 {
17352     int i, length;
17353
17354     length = strlen(match);
17355
17356     for (i = strlen(string) - length; i >= 0; i--, string++)
17357       if (!strncmp(match, string, length))
17358         return string;
17359
17360     return NULL;
17361 }
17362
17363 char *
17364 StrCaseStr (char *string, char *match)
17365 {
17366     int i, j, length;
17367
17368     length = strlen(match);
17369
17370     for (i = strlen(string) - length; i >= 0; i--, string++) {
17371         for (j = 0; j < length; j++) {
17372             if (ToLower(match[j]) != ToLower(string[j]))
17373               break;
17374         }
17375         if (j == length) return string;
17376     }
17377
17378     return NULL;
17379 }
17380
17381 #ifndef _amigados
17382 int
17383 StrCaseCmp (char *s1, char *s2)
17384 {
17385     char c1, c2;
17386
17387     for (;;) {
17388         c1 = ToLower(*s1++);
17389         c2 = ToLower(*s2++);
17390         if (c1 > c2) return 1;
17391         if (c1 < c2) return -1;
17392         if (c1 == NULLCHAR) return 0;
17393     }
17394 }
17395
17396
17397 int
17398 ToLower (int c)
17399 {
17400     return isupper(c) ? tolower(c) : c;
17401 }
17402
17403
17404 int
17405 ToUpper (int c)
17406 {
17407     return islower(c) ? toupper(c) : c;
17408 }
17409 #endif /* !_amigados    */
17410
17411 char *
17412 StrSave (char *s)
17413 {
17414   char *ret;
17415
17416   if ((ret = (char *) malloc(strlen(s) + 1)))
17417     {
17418       safeStrCpy(ret, s, strlen(s)+1);
17419     }
17420   return ret;
17421 }
17422
17423 char *
17424 StrSavePtr (char *s, char **savePtr)
17425 {
17426     if (*savePtr) {
17427         free(*savePtr);
17428     }
17429     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17430       safeStrCpy(*savePtr, s, strlen(s)+1);
17431     }
17432     return(*savePtr);
17433 }
17434
17435 char *
17436 PGNDate ()
17437 {
17438     time_t clock;
17439     struct tm *tm;
17440     char buf[MSG_SIZ];
17441
17442     clock = time((time_t *)NULL);
17443     tm = localtime(&clock);
17444     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17445             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17446     return StrSave(buf);
17447 }
17448
17449
17450 char *
17451 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17452 {
17453     int i, j, fromX, fromY, toX, toY;
17454     int whiteToPlay;
17455     char buf[MSG_SIZ];
17456     char *p, *q;
17457     int emptycount;
17458     ChessSquare piece;
17459
17460     whiteToPlay = (gameMode == EditPosition) ?
17461       !blackPlaysFirst : (move % 2 == 0);
17462     p = buf;
17463
17464     /* Piece placement data */
17465     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17466         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17467         emptycount = 0;
17468         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17469             if (boards[move][i][j] == EmptySquare) {
17470                 emptycount++;
17471             } else { ChessSquare piece = boards[move][i][j];
17472                 if (emptycount > 0) {
17473                     if(emptycount<10) /* [HGM] can be >= 10 */
17474                         *p++ = '0' + emptycount;
17475                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17476                     emptycount = 0;
17477                 }
17478                 if(PieceToChar(piece) == '+') {
17479                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17480                     *p++ = '+';
17481                     piece = (ChessSquare)(DEMOTED piece);
17482                 }
17483                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17484                 if(p[-1] == '~') {
17485                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17486                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17487                     *p++ = '~';
17488                 }
17489             }
17490         }
17491         if (emptycount > 0) {
17492             if(emptycount<10) /* [HGM] can be >= 10 */
17493                 *p++ = '0' + emptycount;
17494             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17495             emptycount = 0;
17496         }
17497         *p++ = '/';
17498     }
17499     *(p - 1) = ' ';
17500
17501     /* [HGM] print Crazyhouse or Shogi holdings */
17502     if( gameInfo.holdingsWidth ) {
17503         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17504         q = p;
17505         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17506             piece = boards[move][i][BOARD_WIDTH-1];
17507             if( piece != EmptySquare )
17508               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17509                   *p++ = PieceToChar(piece);
17510         }
17511         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17512             piece = boards[move][BOARD_HEIGHT-i-1][0];
17513             if( piece != EmptySquare )
17514               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17515                   *p++ = PieceToChar(piece);
17516         }
17517
17518         if( q == p ) *p++ = '-';
17519         *p++ = ']';
17520         *p++ = ' ';
17521     }
17522
17523     /* Active color */
17524     *p++ = whiteToPlay ? 'w' : 'b';
17525     *p++ = ' ';
17526
17527   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17528     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17529   } else {
17530   if(nrCastlingRights) {
17531      q = p;
17532      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17533        /* [HGM] write directly from rights */
17534            if(boards[move][CASTLING][2] != NoRights &&
17535               boards[move][CASTLING][0] != NoRights   )
17536                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17537            if(boards[move][CASTLING][2] != NoRights &&
17538               boards[move][CASTLING][1] != NoRights   )
17539                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17540            if(boards[move][CASTLING][5] != NoRights &&
17541               boards[move][CASTLING][3] != NoRights   )
17542                 *p++ = boards[move][CASTLING][3] + AAA;
17543            if(boards[move][CASTLING][5] != NoRights &&
17544               boards[move][CASTLING][4] != NoRights   )
17545                 *p++ = boards[move][CASTLING][4] + AAA;
17546      } else {
17547
17548         /* [HGM] write true castling rights */
17549         if( nrCastlingRights == 6 ) {
17550             int q, k=0;
17551             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17552                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17553             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17554                  boards[move][CASTLING][2] != NoRights  );
17555             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17556                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17557                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17558                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17559                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17560             }
17561             if(q) *p++ = 'Q';
17562             k = 0;
17563             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17564                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17565             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17566                  boards[move][CASTLING][5] != NoRights  );
17567             if(gameInfo.variant == VariantSChess) {
17568                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17569                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17570                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17571                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17572             }
17573             if(q) *p++ = 'q';
17574         }
17575      }
17576      if (q == p) *p++ = '-'; /* No castling rights */
17577      *p++ = ' ';
17578   }
17579
17580   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17581      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17582      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17583     /* En passant target square */
17584     if (move > backwardMostMove) {
17585         fromX = moveList[move - 1][0] - AAA;
17586         fromY = moveList[move - 1][1] - ONE;
17587         toX = moveList[move - 1][2] - AAA;
17588         toY = moveList[move - 1][3] - ONE;
17589         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17590             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17591             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17592             fromX == toX) {
17593             /* 2-square pawn move just happened */
17594             *p++ = toX + AAA;
17595             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17596         } else {
17597             *p++ = '-';
17598         }
17599     } else if(move == backwardMostMove) {
17600         // [HGM] perhaps we should always do it like this, and forget the above?
17601         if((signed char)boards[move][EP_STATUS] >= 0) {
17602             *p++ = boards[move][EP_STATUS] + AAA;
17603             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17604         } else {
17605             *p++ = '-';
17606         }
17607     } else {
17608         *p++ = '-';
17609     }
17610     *p++ = ' ';
17611   }
17612   }
17613
17614     if(moveCounts)
17615     {   int i = 0, j=move;
17616
17617         /* [HGM] find reversible plies */
17618         if (appData.debugMode) { int k;
17619             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17620             for(k=backwardMostMove; k<=forwardMostMove; k++)
17621                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17622
17623         }
17624
17625         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17626         if( j == backwardMostMove ) i += initialRulePlies;
17627         sprintf(p, "%d ", i);
17628         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17629
17630         /* Fullmove number */
17631         sprintf(p, "%d", (move / 2) + 1);
17632     } else *--p = NULLCHAR;
17633
17634     return StrSave(buf);
17635 }
17636
17637 Boolean
17638 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17639 {
17640     int i, j, k, w=0;
17641     char *p, c;
17642     int emptycount, virgin[BOARD_FILES];
17643     ChessSquare piece;
17644
17645     p = fen;
17646
17647     /* Piece placement data */
17648     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17649         j = 0;
17650         for (;;) {
17651             if (*p == '/' || *p == ' ' || *p == '[' ) {
17652                 if(j > w) w = j;
17653                 emptycount = gameInfo.boardWidth - j;
17654                 while (emptycount--)
17655                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17656                 if (*p == '/') p++;
17657                 else if(autoSize) { // we stumbled unexpectedly into end of board
17658                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17659                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17660                     }
17661                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17662                 }
17663                 break;
17664 #if(BOARD_FILES >= 10)
17665             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17666                 p++; emptycount=10;
17667                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17668                 while (emptycount--)
17669                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17670 #endif
17671             } else if (*p == '*') {
17672                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17673             } else if (isdigit(*p)) {
17674                 emptycount = *p++ - '0';
17675                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17676                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17677                 while (emptycount--)
17678                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17679             } else if (*p == '+' || isalpha(*p)) {
17680                 if (j >= gameInfo.boardWidth) return FALSE;
17681                 if(*p=='+') {
17682                     piece = CharToPiece(*++p);
17683                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17684                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17685                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17686                 } else piece = CharToPiece(*p++);
17687
17688                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17689                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17690                     piece = (ChessSquare) (PROMOTED piece);
17691                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17692                     p++;
17693                 }
17694                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17695             } else {
17696                 return FALSE;
17697             }
17698         }
17699     }
17700     while (*p == '/' || *p == ' ') p++;
17701
17702     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17703
17704     /* [HGM] by default clear Crazyhouse holdings, if present */
17705     if(gameInfo.holdingsWidth) {
17706        for(i=0; i<BOARD_HEIGHT; i++) {
17707            board[i][0]             = EmptySquare; /* black holdings */
17708            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17709            board[i][1]             = (ChessSquare) 0; /* black counts */
17710            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17711        }
17712     }
17713
17714     /* [HGM] look for Crazyhouse holdings here */
17715     while(*p==' ') p++;
17716     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17717         if(*p == '[') p++;
17718         if(*p == '-' ) p++; /* empty holdings */ else {
17719             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17720             /* if we would allow FEN reading to set board size, we would   */
17721             /* have to add holdings and shift the board read so far here   */
17722             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17723                 p++;
17724                 if((int) piece >= (int) BlackPawn ) {
17725                     i = (int)piece - (int)BlackPawn;
17726                     i = PieceToNumber((ChessSquare)i);
17727                     if( i >= gameInfo.holdingsSize ) return FALSE;
17728                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17729                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17730                 } else {
17731                     i = (int)piece - (int)WhitePawn;
17732                     i = PieceToNumber((ChessSquare)i);
17733                     if( i >= gameInfo.holdingsSize ) return FALSE;
17734                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17735                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17736                 }
17737             }
17738         }
17739         if(*p == ']') p++;
17740     }
17741
17742     while(*p == ' ') p++;
17743
17744     /* Active color */
17745     c = *p++;
17746     if(appData.colorNickNames) {
17747       if( c == appData.colorNickNames[0] ) c = 'w'; else
17748       if( c == appData.colorNickNames[1] ) c = 'b';
17749     }
17750     switch (c) {
17751       case 'w':
17752         *blackPlaysFirst = FALSE;
17753         break;
17754       case 'b':
17755         *blackPlaysFirst = TRUE;
17756         break;
17757       default:
17758         return FALSE;
17759     }
17760
17761     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17762     /* return the extra info in global variiables             */
17763
17764     /* set defaults in case FEN is incomplete */
17765     board[EP_STATUS] = EP_UNKNOWN;
17766     for(i=0; i<nrCastlingRights; i++ ) {
17767         board[CASTLING][i] =
17768             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17769     }   /* assume possible unless obviously impossible */
17770     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17771     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17772     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17773                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17774     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17775     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17776     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17777                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17778     FENrulePlies = 0;
17779
17780     while(*p==' ') p++;
17781     if(nrCastlingRights) {
17782       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17783       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17784           /* castling indicator present, so default becomes no castlings */
17785           for(i=0; i<nrCastlingRights; i++ ) {
17786                  board[CASTLING][i] = NoRights;
17787           }
17788       }
17789       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17790              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17791              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17792              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17793         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17794
17795         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17796             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17797             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17798         }
17799         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17800             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17801         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17802                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17803         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17804                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17805         switch(c) {
17806           case'K':
17807               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17808               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17809               board[CASTLING][2] = whiteKingFile;
17810               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17811               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17812               break;
17813           case'Q':
17814               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17815               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17816               board[CASTLING][2] = whiteKingFile;
17817               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17818               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17819               break;
17820           case'k':
17821               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17822               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17823               board[CASTLING][5] = blackKingFile;
17824               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17825               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17826               break;
17827           case'q':
17828               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17829               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17830               board[CASTLING][5] = blackKingFile;
17831               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17832               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17833           case '-':
17834               break;
17835           default: /* FRC castlings */
17836               if(c >= 'a') { /* black rights */
17837                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17838                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17839                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17840                   if(i == BOARD_RGHT) break;
17841                   board[CASTLING][5] = i;
17842                   c -= AAA;
17843                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17844                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17845                   if(c > i)
17846                       board[CASTLING][3] = c;
17847                   else
17848                       board[CASTLING][4] = c;
17849               } else { /* white rights */
17850                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17851                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17852                     if(board[0][i] == WhiteKing) break;
17853                   if(i == BOARD_RGHT) break;
17854                   board[CASTLING][2] = i;
17855                   c -= AAA - 'a' + 'A';
17856                   if(board[0][c] >= WhiteKing) break;
17857                   if(c > i)
17858                       board[CASTLING][0] = c;
17859                   else
17860                       board[CASTLING][1] = c;
17861               }
17862         }
17863       }
17864       for(i=0; i<nrCastlingRights; i++)
17865         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17866       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17867     if (appData.debugMode) {
17868         fprintf(debugFP, "FEN castling rights:");
17869         for(i=0; i<nrCastlingRights; i++)
17870         fprintf(debugFP, " %d", board[CASTLING][i]);
17871         fprintf(debugFP, "\n");
17872     }
17873
17874       while(*p==' ') p++;
17875     }
17876
17877     /* read e.p. field in games that know e.p. capture */
17878     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17879        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17880        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17881       if(*p=='-') {
17882         p++; board[EP_STATUS] = EP_NONE;
17883       } else {
17884          char c = *p++ - AAA;
17885
17886          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17887          if(*p >= '0' && *p <='9') p++;
17888          board[EP_STATUS] = c;
17889       }
17890     }
17891
17892
17893     if(sscanf(p, "%d", &i) == 1) {
17894         FENrulePlies = i; /* 50-move ply counter */
17895         /* (The move number is still ignored)    */
17896     }
17897
17898     return TRUE;
17899 }
17900
17901 void
17902 EditPositionPasteFEN (char *fen)
17903 {
17904   if (fen != NULL) {
17905     Board initial_position;
17906
17907     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17908       DisplayError(_("Bad FEN position in clipboard"), 0);
17909       return ;
17910     } else {
17911       int savedBlackPlaysFirst = blackPlaysFirst;
17912       EditPositionEvent();
17913       blackPlaysFirst = savedBlackPlaysFirst;
17914       CopyBoard(boards[0], initial_position);
17915       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17916       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17917       DisplayBothClocks();
17918       DrawPosition(FALSE, boards[currentMove]);
17919     }
17920   }
17921 }
17922
17923 static char cseq[12] = "\\   ";
17924
17925 Boolean
17926 set_cont_sequence (char *new_seq)
17927 {
17928     int len;
17929     Boolean ret;
17930
17931     // handle bad attempts to set the sequence
17932         if (!new_seq)
17933                 return 0; // acceptable error - no debug
17934
17935     len = strlen(new_seq);
17936     ret = (len > 0) && (len < sizeof(cseq));
17937     if (ret)
17938       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17939     else if (appData.debugMode)
17940       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17941     return ret;
17942 }
17943
17944 /*
17945     reformat a source message so words don't cross the width boundary.  internal
17946     newlines are not removed.  returns the wrapped size (no null character unless
17947     included in source message).  If dest is NULL, only calculate the size required
17948     for the dest buffer.  lp argument indicats line position upon entry, and it's
17949     passed back upon exit.
17950 */
17951 int
17952 wrap (char *dest, char *src, int count, int width, int *lp)
17953 {
17954     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17955
17956     cseq_len = strlen(cseq);
17957     old_line = line = *lp;
17958     ansi = len = clen = 0;
17959
17960     for (i=0; i < count; i++)
17961     {
17962         if (src[i] == '\033')
17963             ansi = 1;
17964
17965         // if we hit the width, back up
17966         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17967         {
17968             // store i & len in case the word is too long
17969             old_i = i, old_len = len;
17970
17971             // find the end of the last word
17972             while (i && src[i] != ' ' && src[i] != '\n')
17973             {
17974                 i--;
17975                 len--;
17976             }
17977
17978             // word too long?  restore i & len before splitting it
17979             if ((old_i-i+clen) >= width)
17980             {
17981                 i = old_i;
17982                 len = old_len;
17983             }
17984
17985             // extra space?
17986             if (i && src[i-1] == ' ')
17987                 len--;
17988
17989             if (src[i] != ' ' && src[i] != '\n')
17990             {
17991                 i--;
17992                 if (len)
17993                     len--;
17994             }
17995
17996             // now append the newline and continuation sequence
17997             if (dest)
17998                 dest[len] = '\n';
17999             len++;
18000             if (dest)
18001                 strncpy(dest+len, cseq, cseq_len);
18002             len += cseq_len;
18003             line = cseq_len;
18004             clen = cseq_len;
18005             continue;
18006         }
18007
18008         if (dest)
18009             dest[len] = src[i];
18010         len++;
18011         if (!ansi)
18012             line++;
18013         if (src[i] == '\n')
18014             line = 0;
18015         if (src[i] == 'm')
18016             ansi = 0;
18017     }
18018     if (dest && appData.debugMode)
18019     {
18020         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18021             count, width, line, len, *lp);
18022         show_bytes(debugFP, src, count);
18023         fprintf(debugFP, "\ndest: ");
18024         show_bytes(debugFP, dest, len);
18025         fprintf(debugFP, "\n");
18026     }
18027     *lp = dest ? line : old_line;
18028
18029     return len;
18030 }
18031
18032 // [HGM] vari: routines for shelving variations
18033 Boolean modeRestore = FALSE;
18034
18035 void
18036 PushInner (int firstMove, int lastMove)
18037 {
18038         int i, j, nrMoves = lastMove - firstMove;
18039
18040         // push current tail of game on stack
18041         savedResult[storedGames] = gameInfo.result;
18042         savedDetails[storedGames] = gameInfo.resultDetails;
18043         gameInfo.resultDetails = NULL;
18044         savedFirst[storedGames] = firstMove;
18045         savedLast [storedGames] = lastMove;
18046         savedFramePtr[storedGames] = framePtr;
18047         framePtr -= nrMoves; // reserve space for the boards
18048         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18049             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18050             for(j=0; j<MOVE_LEN; j++)
18051                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18052             for(j=0; j<2*MOVE_LEN; j++)
18053                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18054             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18055             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18056             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18057             pvInfoList[firstMove+i-1].depth = 0;
18058             commentList[framePtr+i] = commentList[firstMove+i];
18059             commentList[firstMove+i] = NULL;
18060         }
18061
18062         storedGames++;
18063         forwardMostMove = firstMove; // truncate game so we can start variation
18064 }
18065
18066 void
18067 PushTail (int firstMove, int lastMove)
18068 {
18069         if(appData.icsActive) { // only in local mode
18070                 forwardMostMove = currentMove; // mimic old ICS behavior
18071                 return;
18072         }
18073         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18074
18075         PushInner(firstMove, lastMove);
18076         if(storedGames == 1) GreyRevert(FALSE);
18077         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18078 }
18079
18080 void
18081 PopInner (Boolean annotate)
18082 {
18083         int i, j, nrMoves;
18084         char buf[8000], moveBuf[20];
18085
18086         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18087         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18088         nrMoves = savedLast[storedGames] - currentMove;
18089         if(annotate) {
18090                 int cnt = 10;
18091                 if(!WhiteOnMove(currentMove))
18092                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18093                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18094                 for(i=currentMove; i<forwardMostMove; i++) {
18095                         if(WhiteOnMove(i))
18096                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18097                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18098                         strcat(buf, moveBuf);
18099                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18100                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18101                 }
18102                 strcat(buf, ")");
18103         }
18104         for(i=1; i<=nrMoves; i++) { // copy last variation back
18105             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18106             for(j=0; j<MOVE_LEN; j++)
18107                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18108             for(j=0; j<2*MOVE_LEN; j++)
18109                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18110             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18111             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18112             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18113             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18114             commentList[currentMove+i] = commentList[framePtr+i];
18115             commentList[framePtr+i] = NULL;
18116         }
18117         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18118         framePtr = savedFramePtr[storedGames];
18119         gameInfo.result = savedResult[storedGames];
18120         if(gameInfo.resultDetails != NULL) {
18121             free(gameInfo.resultDetails);
18122       }
18123         gameInfo.resultDetails = savedDetails[storedGames];
18124         forwardMostMove = currentMove + nrMoves;
18125 }
18126
18127 Boolean
18128 PopTail (Boolean annotate)
18129 {
18130         if(appData.icsActive) return FALSE; // only in local mode
18131         if(!storedGames) return FALSE; // sanity
18132         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18133
18134         PopInner(annotate);
18135         if(currentMove < forwardMostMove) ForwardEvent(); else
18136         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18137
18138         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18139         return TRUE;
18140 }
18141
18142 void
18143 CleanupTail ()
18144 {       // remove all shelved variations
18145         int i;
18146         for(i=0; i<storedGames; i++) {
18147             if(savedDetails[i])
18148                 free(savedDetails[i]);
18149             savedDetails[i] = NULL;
18150         }
18151         for(i=framePtr; i<MAX_MOVES; i++) {
18152                 if(commentList[i]) free(commentList[i]);
18153                 commentList[i] = NULL;
18154         }
18155         framePtr = MAX_MOVES-1;
18156         storedGames = 0;
18157 }
18158
18159 void
18160 LoadVariation (int index, char *text)
18161 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18162         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18163         int level = 0, move;
18164
18165         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18166         // first find outermost bracketing variation
18167         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18168             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18169                 if(*p == '{') wait = '}'; else
18170                 if(*p == '[') wait = ']'; else
18171                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18172                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18173             }
18174             if(*p == wait) wait = NULLCHAR; // closing ]} found
18175             p++;
18176         }
18177         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18178         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18179         end[1] = NULLCHAR; // clip off comment beyond variation
18180         ToNrEvent(currentMove-1);
18181         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18182         // kludge: use ParsePV() to append variation to game
18183         move = currentMove;
18184         ParsePV(start, TRUE, TRUE);
18185         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18186         ClearPremoveHighlights();
18187         CommentPopDown();
18188         ToNrEvent(currentMove+1);
18189 }
18190
18191 void
18192 LoadTheme ()
18193 {
18194     char *p, *q, buf[MSG_SIZ];
18195     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18196         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18197         ParseArgsFromString(buf);
18198         ActivateTheme(TRUE); // also redo colors
18199         return;
18200     }
18201     p = nickName;
18202     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18203     {
18204         int len;
18205         q = appData.themeNames;
18206         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18207       if(appData.useBitmaps) {
18208         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18209                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18210                 appData.liteBackTextureMode,
18211                 appData.darkBackTextureMode );
18212       } else {
18213         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18214                 Col2Text(2),   // lightSquareColor
18215                 Col2Text(3) ); // darkSquareColor
18216       }
18217       if(appData.useBorder) {
18218         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18219                 appData.border);
18220       } else {
18221         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18222       }
18223       if(appData.useFont) {
18224         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18225                 appData.renderPiecesWithFont,
18226                 appData.fontToPieceTable,
18227                 Col2Text(9),    // appData.fontBackColorWhite
18228                 Col2Text(10) ); // appData.fontForeColorBlack
18229       } else {
18230         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18231                 appData.pieceDirectory);
18232         if(!appData.pieceDirectory[0])
18233           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18234                 Col2Text(0),   // whitePieceColor
18235                 Col2Text(1) ); // blackPieceColor
18236       }
18237       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18238                 Col2Text(4),   // highlightSquareColor
18239                 Col2Text(5) ); // premoveHighlightColor
18240         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18241         if(insert != q) insert[-1] = NULLCHAR;
18242         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18243         if(q)   free(q);
18244     }
18245     ActivateTheme(FALSE);
18246 }