a9abaa853b7239cc626f103cdb24d7e6ecb5eda0
[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     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5317     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5318     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5319     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5320     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5321     do {
5322         if(step && !Partner(&promoSweep)) promoSweep -= step;
5323         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5324         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5325         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5326         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5327         if(!step) step = -1;
5328     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5329             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion));
5330     if(toX >= 0) {
5331         int victim = boards[currentMove][toY][toX];
5332         boards[currentMove][toY][toX] = promoSweep;
5333         DrawPosition(FALSE, boards[currentMove]);
5334         boards[currentMove][toY][toX] = victim;
5335     } else
5336     ChangeDragPiece(promoSweep);
5337 }
5338
5339 int
5340 PromoScroll (int x, int y)
5341 {
5342   int step = 0;
5343
5344   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5345   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5346   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5347   if(!step) return FALSE;
5348   lastX = x; lastY = y;
5349   if((promoSweep < BlackPawn) == flipView) step = -step;
5350   if(step > 0) selectFlag = 1;
5351   if(!selectFlag) Sweep(step);
5352   return FALSE;
5353 }
5354
5355 void
5356 NextPiece (int step)
5357 {
5358     ChessSquare piece = boards[currentMove][toY][toX];
5359     do {
5360         pieceSweep -= step;
5361         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5362         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5363         if(!step) step = -1;
5364     } while(PieceToChar(pieceSweep) == '.');
5365     boards[currentMove][toY][toX] = pieceSweep;
5366     DrawPosition(FALSE, boards[currentMove]);
5367     boards[currentMove][toY][toX] = piece;
5368 }
5369 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5370 void
5371 AlphaRank (char *move, int n)
5372 {
5373 //    char *p = move, c; int x, y;
5374
5375     if (appData.debugMode) {
5376         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5377     }
5378
5379     if(move[1]=='*' &&
5380        move[2]>='0' && move[2]<='9' &&
5381        move[3]>='a' && move[3]<='x'    ) {
5382         move[1] = '@';
5383         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5384         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5385     } else
5386     if(move[0]>='0' && move[0]<='9' &&
5387        move[1]>='a' && move[1]<='x' &&
5388        move[2]>='0' && move[2]<='9' &&
5389        move[3]>='a' && move[3]<='x'    ) {
5390         /* input move, Shogi -> normal */
5391         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5392         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5393         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5394         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5395     } else
5396     if(move[1]=='@' &&
5397        move[3]>='0' && move[3]<='9' &&
5398        move[2]>='a' && move[2]<='x'    ) {
5399         move[1] = '*';
5400         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5401         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5402     } else
5403     if(
5404        move[0]>='a' && move[0]<='x' &&
5405        move[3]>='0' && move[3]<='9' &&
5406        move[2]>='a' && move[2]<='x'    ) {
5407          /* output move, normal -> Shogi */
5408         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5409         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5410         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5411         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5412         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5413     }
5414     if (appData.debugMode) {
5415         fprintf(debugFP, "   out = '%s'\n", move);
5416     }
5417 }
5418
5419 char yy_textstr[8000];
5420
5421 /* Parser for moves from gnuchess, ICS, or user typein box */
5422 Boolean
5423 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5424 {
5425     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5426
5427     switch (*moveType) {
5428       case WhitePromotion:
5429       case BlackPromotion:
5430       case WhiteNonPromotion:
5431       case BlackNonPromotion:
5432       case NormalMove:
5433       case FirstLeg:
5434       case WhiteCapturesEnPassant:
5435       case BlackCapturesEnPassant:
5436       case WhiteKingSideCastle:
5437       case WhiteQueenSideCastle:
5438       case BlackKingSideCastle:
5439       case BlackQueenSideCastle:
5440       case WhiteKingSideCastleWild:
5441       case WhiteQueenSideCastleWild:
5442       case BlackKingSideCastleWild:
5443       case BlackQueenSideCastleWild:
5444       /* Code added by Tord: */
5445       case WhiteHSideCastleFR:
5446       case WhiteASideCastleFR:
5447       case BlackHSideCastleFR:
5448       case BlackASideCastleFR:
5449       /* End of code added by Tord */
5450       case IllegalMove:         /* bug or odd chess variant */
5451         *fromX = currentMoveString[0] - AAA;
5452         *fromY = currentMoveString[1] - ONE;
5453         *toX = currentMoveString[2] - AAA;
5454         *toY = currentMoveString[3] - ONE;
5455         *promoChar = currentMoveString[4];
5456         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5457             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5458     if (appData.debugMode) {
5459         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5460     }
5461             *fromX = *fromY = *toX = *toY = 0;
5462             return FALSE;
5463         }
5464         if (appData.testLegality) {
5465           return (*moveType != IllegalMove);
5466         } else {
5467           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5468                          // [HGM] lion: if this is a double move we are less critical
5469                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5470         }
5471
5472       case WhiteDrop:
5473       case BlackDrop:
5474         *fromX = *moveType == WhiteDrop ?
5475           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5476           (int) CharToPiece(ToLower(currentMoveString[0]));
5477         *fromY = DROP_RANK;
5478         *toX = currentMoveString[2] - AAA;
5479         *toY = currentMoveString[3] - ONE;
5480         *promoChar = NULLCHAR;
5481         return TRUE;
5482
5483       case AmbiguousMove:
5484       case ImpossibleMove:
5485       case EndOfFile:
5486       case ElapsedTime:
5487       case Comment:
5488       case PGNTag:
5489       case NAG:
5490       case WhiteWins:
5491       case BlackWins:
5492       case GameIsDrawn:
5493       default:
5494     if (appData.debugMode) {
5495         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5496     }
5497         /* bug? */
5498         *fromX = *fromY = *toX = *toY = 0;
5499         *promoChar = NULLCHAR;
5500         return FALSE;
5501     }
5502 }
5503
5504 Boolean pushed = FALSE;
5505 char *lastParseAttempt;
5506
5507 void
5508 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5509 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5510   int fromX, fromY, toX, toY; char promoChar;
5511   ChessMove moveType;
5512   Boolean valid;
5513   int nr = 0;
5514
5515   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5516   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5517     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5518     pushed = TRUE;
5519   }
5520   endPV = forwardMostMove;
5521   do {
5522     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5523     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5524     lastParseAttempt = pv;
5525     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5526     if(!valid && nr == 0 &&
5527        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5528         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5529         // Hande case where played move is different from leading PV move
5530         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5531         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5532         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5533         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5534           endPV += 2; // if position different, keep this
5535           moveList[endPV-1][0] = fromX + AAA;
5536           moveList[endPV-1][1] = fromY + ONE;
5537           moveList[endPV-1][2] = toX + AAA;
5538           moveList[endPV-1][3] = toY + ONE;
5539           parseList[endPV-1][0] = NULLCHAR;
5540           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5541         }
5542       }
5543     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5544     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5545     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5546     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5547         valid++; // allow comments in PV
5548         continue;
5549     }
5550     nr++;
5551     if(endPV+1 > framePtr) break; // no space, truncate
5552     if(!valid) break;
5553     endPV++;
5554     CopyBoard(boards[endPV], boards[endPV-1]);
5555     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5556     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5557     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5558     CoordsToAlgebraic(boards[endPV - 1],
5559                              PosFlags(endPV - 1),
5560                              fromY, fromX, toY, toX, promoChar,
5561                              parseList[endPV - 1]);
5562   } while(valid);
5563   if(atEnd == 2) return; // used hidden, for PV conversion
5564   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5565   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5566   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5567                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5568   DrawPosition(TRUE, boards[currentMove]);
5569 }
5570
5571 int
5572 MultiPV (ChessProgramState *cps)
5573 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5574         int i;
5575         for(i=0; i<cps->nrOptions; i++)
5576             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5577                 return i;
5578         return -1;
5579 }
5580
5581 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5582
5583 Boolean
5584 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5585 {
5586         int startPV, multi, lineStart, origIndex = index;
5587         char *p, buf2[MSG_SIZ];
5588         ChessProgramState *cps = (pane ? &second : &first);
5589
5590         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5591         lastX = x; lastY = y;
5592         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5593         lineStart = startPV = index;
5594         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5595         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5596         index = startPV;
5597         do{ while(buf[index] && buf[index] != '\n') index++;
5598         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5599         buf[index] = 0;
5600         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5601                 int n = cps->option[multi].value;
5602                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5603                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5604                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5605                 cps->option[multi].value = n;
5606                 *start = *end = 0;
5607                 return FALSE;
5608         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5609                 ExcludeClick(origIndex - lineStart);
5610                 return FALSE;
5611         }
5612         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5613         *start = startPV; *end = index-1;
5614         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5615         return TRUE;
5616 }
5617
5618 char *
5619 PvToSAN (char *pv)
5620 {
5621         static char buf[10*MSG_SIZ];
5622         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5623         *buf = NULLCHAR;
5624         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5625         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5626         for(i = forwardMostMove; i<endPV; i++){
5627             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5628             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5629             k += strlen(buf+k);
5630         }
5631         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5632         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5633         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5634         endPV = savedEnd;
5635         return buf;
5636 }
5637
5638 Boolean
5639 LoadPV (int x, int y)
5640 { // called on right mouse click to load PV
5641   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5642   lastX = x; lastY = y;
5643   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5644   extendGame = FALSE;
5645   return TRUE;
5646 }
5647
5648 void
5649 UnLoadPV ()
5650 {
5651   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5652   if(endPV < 0) return;
5653   if(appData.autoCopyPV) CopyFENToClipboard();
5654   endPV = -1;
5655   if(extendGame && currentMove > forwardMostMove) {
5656         Boolean saveAnimate = appData.animate;
5657         if(pushed) {
5658             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5659                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5660             } else storedGames--; // abandon shelved tail of original game
5661         }
5662         pushed = FALSE;
5663         forwardMostMove = currentMove;
5664         currentMove = oldFMM;
5665         appData.animate = FALSE;
5666         ToNrEvent(forwardMostMove);
5667         appData.animate = saveAnimate;
5668   }
5669   currentMove = forwardMostMove;
5670   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5671   ClearPremoveHighlights();
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 void
5676 MovePV (int x, int y, int h)
5677 { // step through PV based on mouse coordinates (called on mouse move)
5678   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5679
5680   // we must somehow check if right button is still down (might be released off board!)
5681   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5682   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5683   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5684   if(!step) return;
5685   lastX = x; lastY = y;
5686
5687   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5688   if(endPV < 0) return;
5689   if(y < margin) step = 1; else
5690   if(y > h - margin) step = -1;
5691   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5692   currentMove += step;
5693   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5694   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5695                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5696   DrawPosition(FALSE, boards[currentMove]);
5697 }
5698
5699
5700 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5701 // All positions will have equal probability, but the current method will not provide a unique
5702 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5703 #define DARK 1
5704 #define LITE 2
5705 #define ANY 3
5706
5707 int squaresLeft[4];
5708 int piecesLeft[(int)BlackPawn];
5709 int seed, nrOfShuffles;
5710
5711 void
5712 GetPositionNumber ()
5713 {       // sets global variable seed
5714         int i;
5715
5716         seed = appData.defaultFrcPosition;
5717         if(seed < 0) { // randomize based on time for negative FRC position numbers
5718                 for(i=0; i<50; i++) seed += random();
5719                 seed = random() ^ random() >> 8 ^ random() << 8;
5720                 if(seed<0) seed = -seed;
5721         }
5722 }
5723
5724 int
5725 put (Board board, int pieceType, int rank, int n, int shade)
5726 // put the piece on the (n-1)-th empty squares of the given shade
5727 {
5728         int i;
5729
5730         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5731                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5732                         board[rank][i] = (ChessSquare) pieceType;
5733                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5734                         squaresLeft[ANY]--;
5735                         piecesLeft[pieceType]--;
5736                         return i;
5737                 }
5738         }
5739         return -1;
5740 }
5741
5742
5743 void
5744 AddOnePiece (Board board, int pieceType, int rank, int shade)
5745 // calculate where the next piece goes, (any empty square), and put it there
5746 {
5747         int i;
5748
5749         i = seed % squaresLeft[shade];
5750         nrOfShuffles *= squaresLeft[shade];
5751         seed /= squaresLeft[shade];
5752         put(board, pieceType, rank, i, shade);
5753 }
5754
5755 void
5756 AddTwoPieces (Board board, int pieceType, int rank)
5757 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5758 {
5759         int i, n=squaresLeft[ANY], j=n-1, k;
5760
5761         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5762         i = seed % k;  // pick one
5763         nrOfShuffles *= k;
5764         seed /= k;
5765         while(i >= j) i -= j--;
5766         j = n - 1 - j; i += j;
5767         put(board, pieceType, rank, j, ANY);
5768         put(board, pieceType, rank, i, ANY);
5769 }
5770
5771 void
5772 SetUpShuffle (Board board, int number)
5773 {
5774         int i, p, first=1;
5775
5776         GetPositionNumber(); nrOfShuffles = 1;
5777
5778         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5779         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5780         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5781
5782         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5783
5784         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5785             p = (int) board[0][i];
5786             if(p < (int) BlackPawn) piecesLeft[p] ++;
5787             board[0][i] = EmptySquare;
5788         }
5789
5790         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5791             // shuffles restricted to allow normal castling put KRR first
5792             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5793                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5794             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5795                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5796             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5797                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5798             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5799                 put(board, WhiteRook, 0, 0, ANY);
5800             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5801         }
5802
5803         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5804             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5805             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5806                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5807                 while(piecesLeft[p] >= 2) {
5808                     AddOnePiece(board, p, 0, LITE);
5809                     AddOnePiece(board, p, 0, DARK);
5810                 }
5811                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5812             }
5813
5814         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5815             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5816             // but we leave King and Rooks for last, to possibly obey FRC restriction
5817             if(p == (int)WhiteRook) continue;
5818             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5819             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5820         }
5821
5822         // now everything is placed, except perhaps King (Unicorn) and Rooks
5823
5824         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5825             // Last King gets castling rights
5826             while(piecesLeft[(int)WhiteUnicorn]) {
5827                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5828                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5829             }
5830
5831             while(piecesLeft[(int)WhiteKing]) {
5832                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5833                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5834             }
5835
5836
5837         } else {
5838             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5839             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5840         }
5841
5842         // Only Rooks can be left; simply place them all
5843         while(piecesLeft[(int)WhiteRook]) {
5844                 i = put(board, WhiteRook, 0, 0, ANY);
5845                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5846                         if(first) {
5847                                 first=0;
5848                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5849                         }
5850                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5851                 }
5852         }
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5854             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5855         }
5856
5857         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5858 }
5859
5860 int
5861 SetCharTable (char *table, const char * map)
5862 /* [HGM] moved here from winboard.c because of its general usefulness */
5863 /*       Basically a safe strcpy that uses the last character as King */
5864 {
5865     int result = FALSE; int NrPieces;
5866
5867     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5868                     && NrPieces >= 12 && !(NrPieces&1)) {
5869         int i; /* [HGM] Accept even length from 12 to 34 */
5870
5871         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5872         for( i=0; i<NrPieces/2-1; i++ ) {
5873             table[i] = map[i];
5874             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5875         }
5876         table[(int) WhiteKing]  = map[NrPieces/2-1];
5877         table[(int) BlackKing]  = map[NrPieces-1];
5878
5879         result = TRUE;
5880     }
5881
5882     return result;
5883 }
5884
5885 void
5886 Prelude (Board board)
5887 {       // [HGM] superchess: random selection of exo-pieces
5888         int i, j, k; ChessSquare p;
5889         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5890
5891         GetPositionNumber(); // use FRC position number
5892
5893         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5894             SetCharTable(pieceToChar, appData.pieceToCharTable);
5895             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5896                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5897         }
5898
5899         j = seed%4;                 seed /= 4;
5900         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5901         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5902         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5903         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5908         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5916         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5917         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5918         put(board, exoPieces[0],    0, 0, ANY);
5919         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5920 }
5921
5922 void
5923 InitPosition (int redraw)
5924 {
5925     ChessSquare (* pieces)[BOARD_FILES];
5926     int i, j, pawnRow=1, pieceRows=1, overrule,
5927     oldx = gameInfo.boardWidth,
5928     oldy = gameInfo.boardHeight,
5929     oldh = gameInfo.holdingsWidth;
5930     static int oldv;
5931
5932     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5933
5934     /* [AS] Initialize pv info list [HGM] and game status */
5935     {
5936         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5937             pvInfoList[i].depth = 0;
5938             boards[i][EP_STATUS] = EP_NONE;
5939             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5940         }
5941
5942         initialRulePlies = 0; /* 50-move counter start */
5943
5944         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5945         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5946     }
5947
5948
5949     /* [HGM] logic here is completely changed. In stead of full positions */
5950     /* the initialized data only consist of the two backranks. The switch */
5951     /* selects which one we will use, which is than copied to the Board   */
5952     /* initialPosition, which for the rest is initialized by Pawns and    */
5953     /* empty squares. This initial position is then copied to boards[0],  */
5954     /* possibly after shuffling, so that it remains available.            */
5955
5956     gameInfo.holdingsWidth = 0; /* default board sizes */
5957     gameInfo.boardWidth    = 8;
5958     gameInfo.boardHeight   = 8;
5959     gameInfo.holdingsSize  = 0;
5960     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5961     for(i=0; i<BOARD_FILES-2; i++)
5962       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5963     initialPosition[EP_STATUS] = EP_NONE;
5964     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5965     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5966          SetCharTable(pieceNickName, appData.pieceNickNames);
5967     else SetCharTable(pieceNickName, "............");
5968     pieces = FIDEArray;
5969
5970     switch (gameInfo.variant) {
5971     case VariantFischeRandom:
5972       shuffleOpenings = TRUE;
5973     default:
5974       break;
5975     case VariantShatranj:
5976       pieces = ShatranjArray;
5977       nrCastlingRights = 0;
5978       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5979       break;
5980     case VariantMakruk:
5981       pieces = makrukArray;
5982       nrCastlingRights = 0;
5983       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5984       break;
5985     case VariantASEAN:
5986       pieces = aseanArray;
5987       nrCastlingRights = 0;
5988       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5989       break;
5990     case VariantTwoKings:
5991       pieces = twoKingsArray;
5992       break;
5993     case VariantGrand:
5994       pieces = GrandArray;
5995       nrCastlingRights = 0;
5996       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5997       gameInfo.boardWidth = 10;
5998       gameInfo.boardHeight = 10;
5999       gameInfo.holdingsSize = 7;
6000       break;
6001     case VariantCapaRandom:
6002       shuffleOpenings = TRUE;
6003     case VariantCapablanca:
6004       pieces = CapablancaArray;
6005       gameInfo.boardWidth = 10;
6006       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6007       break;
6008     case VariantGothic:
6009       pieces = GothicArray;
6010       gameInfo.boardWidth = 10;
6011       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6012       break;
6013     case VariantSChess:
6014       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6015       gameInfo.holdingsSize = 7;
6016       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6017       break;
6018     case VariantJanus:
6019       pieces = JanusArray;
6020       gameInfo.boardWidth = 10;
6021       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6022       nrCastlingRights = 6;
6023         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6024         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6025         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6026         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6027         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6028         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6029       break;
6030     case VariantFalcon:
6031       pieces = FalconArray;
6032       gameInfo.boardWidth = 10;
6033       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6034       break;
6035     case VariantXiangqi:
6036       pieces = XiangqiArray;
6037       gameInfo.boardWidth  = 9;
6038       gameInfo.boardHeight = 10;
6039       nrCastlingRights = 0;
6040       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6041       break;
6042     case VariantShogi:
6043       pieces = ShogiArray;
6044       gameInfo.boardWidth  = 9;
6045       gameInfo.boardHeight = 9;
6046       gameInfo.holdingsSize = 7;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6049       break;
6050     case VariantChu:
6051       pieces = ChuArray; pieceRows = 3;
6052       gameInfo.boardWidth  = 12;
6053       gameInfo.boardHeight = 12;
6054       nrCastlingRights = 0;
6055       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6056                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6057       break;
6058     case VariantCourier:
6059       pieces = CourierArray;
6060       gameInfo.boardWidth  = 12;
6061       nrCastlingRights = 0;
6062       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6063       break;
6064     case VariantKnightmate:
6065       pieces = KnightmateArray;
6066       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6067       break;
6068     case VariantSpartan:
6069       pieces = SpartanArray;
6070       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6071       break;
6072     case VariantLion:
6073       pieces = lionArray;
6074       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6075       break;
6076     case VariantChuChess:
6077       pieces = ChuChessArray;
6078       gameInfo.boardWidth = 10;
6079       gameInfo.boardHeight = 10;
6080       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6081       break;
6082     case VariantFairy:
6083       pieces = fairyArray;
6084       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6085       break;
6086     case VariantGreat:
6087       pieces = GreatArray;
6088       gameInfo.boardWidth = 10;
6089       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6090       gameInfo.holdingsSize = 8;
6091       break;
6092     case VariantSuper:
6093       pieces = FIDEArray;
6094       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6095       gameInfo.holdingsSize = 8;
6096       startedFromSetupPosition = TRUE;
6097       break;
6098     case VariantCrazyhouse:
6099     case VariantBughouse:
6100       pieces = FIDEArray;
6101       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6102       gameInfo.holdingsSize = 5;
6103       break;
6104     case VariantWildCastle:
6105       pieces = FIDEArray;
6106       /* !!?shuffle with kings guaranteed to be on d or e file */
6107       shuffleOpenings = 1;
6108       break;
6109     case VariantNoCastle:
6110       pieces = FIDEArray;
6111       nrCastlingRights = 0;
6112       /* !!?unconstrained back-rank shuffle */
6113       shuffleOpenings = 1;
6114       break;
6115     }
6116
6117     overrule = 0;
6118     if(appData.NrFiles >= 0) {
6119         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6120         gameInfo.boardWidth = appData.NrFiles;
6121     }
6122     if(appData.NrRanks >= 0) {
6123         gameInfo.boardHeight = appData.NrRanks;
6124     }
6125     if(appData.holdingsSize >= 0) {
6126         i = appData.holdingsSize;
6127         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6128         gameInfo.holdingsSize = i;
6129     }
6130     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6131     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6132         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6133
6134     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6135     if(pawnRow < 1) pawnRow = 1;
6136     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6137        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6138     if(gameInfo.variant == VariantChu) pawnRow = 3;
6139
6140     /* User pieceToChar list overrules defaults */
6141     if(appData.pieceToCharTable != NULL)
6142         SetCharTable(pieceToChar, appData.pieceToCharTable);
6143
6144     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6145
6146         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6147             s = (ChessSquare) 0; /* account holding counts in guard band */
6148         for( i=0; i<BOARD_HEIGHT; i++ )
6149             initialPosition[i][j] = s;
6150
6151         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6152         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6153         initialPosition[pawnRow][j] = WhitePawn;
6154         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6155         if(gameInfo.variant == VariantXiangqi) {
6156             if(j&1) {
6157                 initialPosition[pawnRow][j] =
6158                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6159                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6160                    initialPosition[2][j] = WhiteCannon;
6161                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6162                 }
6163             }
6164         }
6165         if(gameInfo.variant == VariantChu) {
6166              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6167                initialPosition[pawnRow+1][j] = WhiteCobra,
6168                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6169              for(i=1; i<pieceRows; i++) {
6170                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6171                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6172              }
6173         }
6174         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6175             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6176                initialPosition[0][j] = WhiteRook;
6177                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6178             }
6179         }
6180         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6181     }
6182     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6183     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6184
6185             j=BOARD_LEFT+1;
6186             initialPosition[1][j] = WhiteBishop;
6187             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6188             j=BOARD_RGHT-2;
6189             initialPosition[1][j] = WhiteRook;
6190             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6191     }
6192
6193     if( nrCastlingRights == -1) {
6194         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6195         /*       This sets default castling rights from none to normal corners   */
6196         /* Variants with other castling rights must set them themselves above    */
6197         nrCastlingRights = 6;
6198
6199         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6200         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6201         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6202         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6203         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6204         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6205      }
6206
6207      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6208      if(gameInfo.variant == VariantGreat) { // promotion commoners
6209         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6210         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6211         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6212         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6213      }
6214      if( gameInfo.variant == VariantSChess ) {
6215       initialPosition[1][0] = BlackMarshall;
6216       initialPosition[2][0] = BlackAngel;
6217       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6218       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6219       initialPosition[1][1] = initialPosition[2][1] =
6220       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6221      }
6222   if (appData.debugMode) {
6223     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6224   }
6225     if(shuffleOpenings) {
6226         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6227         startedFromSetupPosition = TRUE;
6228     }
6229     if(startedFromPositionFile) {
6230       /* [HGM] loadPos: use PositionFile for every new game */
6231       CopyBoard(initialPosition, filePosition);
6232       for(i=0; i<nrCastlingRights; i++)
6233           initialRights[i] = filePosition[CASTLING][i];
6234       startedFromSetupPosition = TRUE;
6235     }
6236
6237     CopyBoard(boards[0], initialPosition);
6238
6239     if(oldx != gameInfo.boardWidth ||
6240        oldy != gameInfo.boardHeight ||
6241        oldv != gameInfo.variant ||
6242        oldh != gameInfo.holdingsWidth
6243                                          )
6244             InitDrawingSizes(-2 ,0);
6245
6246     oldv = gameInfo.variant;
6247     if (redraw)
6248       DrawPosition(TRUE, boards[currentMove]);
6249 }
6250
6251 void
6252 SendBoard (ChessProgramState *cps, int moveNum)
6253 {
6254     char message[MSG_SIZ];
6255
6256     if (cps->useSetboard) {
6257       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6258       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6259       SendToProgram(message, cps);
6260       free(fen);
6261
6262     } else {
6263       ChessSquare *bp;
6264       int i, j, left=0, right=BOARD_WIDTH;
6265       /* Kludge to set black to move, avoiding the troublesome and now
6266        * deprecated "black" command.
6267        */
6268       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6269         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6270
6271       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6272
6273       SendToProgram("edit\n", cps);
6274       SendToProgram("#\n", cps);
6275       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6276         bp = &boards[moveNum][i][left];
6277         for (j = left; j < right; j++, bp++) {
6278           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6279           if ((int) *bp < (int) BlackPawn) {
6280             if(j == BOARD_RGHT+1)
6281                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6282             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6283             if(message[0] == '+' || message[0] == '~') {
6284               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6285                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6286                         AAA + j, ONE + i);
6287             }
6288             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6289                 message[1] = BOARD_RGHT   - 1 - j + '1';
6290                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6291             }
6292             SendToProgram(message, cps);
6293           }
6294         }
6295       }
6296
6297       SendToProgram("c\n", cps);
6298       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6299         bp = &boards[moveNum][i][left];
6300         for (j = left; j < right; j++, bp++) {
6301           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6302           if (((int) *bp != (int) EmptySquare)
6303               && ((int) *bp >= (int) BlackPawn)) {
6304             if(j == BOARD_LEFT-2)
6305                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6306             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6307                     AAA + j, ONE + i);
6308             if(message[0] == '+' || message[0] == '~') {
6309               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6310                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6311                         AAA + j, ONE + i);
6312             }
6313             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6314                 message[1] = BOARD_RGHT   - 1 - j + '1';
6315                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6316             }
6317             SendToProgram(message, cps);
6318           }
6319         }
6320       }
6321
6322       SendToProgram(".\n", cps);
6323     }
6324     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6325 }
6326
6327 char exclusionHeader[MSG_SIZ];
6328 int exCnt, excludePtr;
6329 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6330 static Exclusion excluTab[200];
6331 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6332
6333 static void
6334 WriteMap (int s)
6335 {
6336     int j;
6337     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6338     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6339 }
6340
6341 static void
6342 ClearMap ()
6343 {
6344     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6345     excludePtr = 24; exCnt = 0;
6346     WriteMap(0);
6347 }
6348
6349 static void
6350 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6351 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6352     char buf[2*MOVE_LEN], *p;
6353     Exclusion *e = excluTab;
6354     int i;
6355     for(i=0; i<exCnt; i++)
6356         if(e[i].ff == fromX && e[i].fr == fromY &&
6357            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6358     if(i == exCnt) { // was not in exclude list; add it
6359         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6360         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6361             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6362             return; // abort
6363         }
6364         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6365         excludePtr++; e[i].mark = excludePtr++;
6366         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6367         exCnt++;
6368     }
6369     exclusionHeader[e[i].mark] = state;
6370 }
6371
6372 static int
6373 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6374 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6375     char buf[MSG_SIZ];
6376     int j, k;
6377     ChessMove moveType;
6378     if((signed char)promoChar == -1) { // kludge to indicate best move
6379         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6380             return 1; // if unparsable, abort
6381     }
6382     // update exclusion map (resolving toggle by consulting existing state)
6383     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6384     j = k%8; k >>= 3;
6385     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6386     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6387          excludeMap[k] |=   1<<j;
6388     else excludeMap[k] &= ~(1<<j);
6389     // update header
6390     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6391     // inform engine
6392     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6393     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6394     SendToBoth(buf);
6395     return (state == '+');
6396 }
6397
6398 static void
6399 ExcludeClick (int index)
6400 {
6401     int i, j;
6402     Exclusion *e = excluTab;
6403     if(index < 25) { // none, best or tail clicked
6404         if(index < 13) { // none: include all
6405             WriteMap(0); // clear map
6406             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6407             SendToBoth("include all\n"); // and inform engine
6408         } else if(index > 18) { // tail
6409             if(exclusionHeader[19] == '-') { // tail was excluded
6410                 SendToBoth("include all\n");
6411                 WriteMap(0); // clear map completely
6412                 // now re-exclude selected moves
6413                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6414                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6415             } else { // tail was included or in mixed state
6416                 SendToBoth("exclude all\n");
6417                 WriteMap(0xFF); // fill map completely
6418                 // now re-include selected moves
6419                 j = 0; // count them
6420                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6421                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6422                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6423             }
6424         } else { // best
6425             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6426         }
6427     } else {
6428         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6429             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6430             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6431             break;
6432         }
6433     }
6434 }
6435
6436 ChessSquare
6437 DefaultPromoChoice (int white)
6438 {
6439     ChessSquare result;
6440     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6441        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6442         result = WhiteFerz; // no choice
6443     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6444         result= WhiteKing; // in Suicide Q is the last thing we want
6445     else if(gameInfo.variant == VariantSpartan)
6446         result = white ? WhiteQueen : WhiteAngel;
6447     else result = WhiteQueen;
6448     if(!white) result = WHITE_TO_BLACK result;
6449     return result;
6450 }
6451
6452 static int autoQueen; // [HGM] oneclick
6453
6454 int
6455 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6456 {
6457     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6458     /* [HGM] add Shogi promotions */
6459     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6460     ChessSquare piece;
6461     ChessMove moveType;
6462     Boolean premove;
6463
6464     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6465     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6466
6467     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6468       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6469         return FALSE;
6470
6471     piece = boards[currentMove][fromY][fromX];
6472     if(gameInfo.variant == VariantChu) {
6473         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6474         promotionZoneSize = BOARD_HEIGHT/3;
6475         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6476     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6477         promotionZoneSize = BOARD_HEIGHT/3;
6478         highestPromotingPiece = (int)WhiteAlfil;
6479     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6480         promotionZoneSize = 3;
6481     }
6482
6483     // Treat Lance as Pawn when it is not representing Amazon
6484     if(gameInfo.variant != VariantSuper) {
6485         if(piece == WhiteLance) piece = WhitePawn; else
6486         if(piece == BlackLance) piece = BlackPawn;
6487     }
6488
6489     // next weed out all moves that do not touch the promotion zone at all
6490     if((int)piece >= BlackPawn) {
6491         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6492              return FALSE;
6493         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6494         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6495     } else {
6496         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6497            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6498         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6499              return FALSE;
6500     }
6501
6502     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6503
6504     // weed out mandatory Shogi promotions
6505     if(gameInfo.variant == VariantShogi) {
6506         if(piece >= BlackPawn) {
6507             if(toY == 0 && piece == BlackPawn ||
6508                toY == 0 && piece == BlackQueen ||
6509                toY <= 1 && piece == BlackKnight) {
6510                 *promoChoice = '+';
6511                 return FALSE;
6512             }
6513         } else {
6514             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6515                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6516                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6517                 *promoChoice = '+';
6518                 return FALSE;
6519             }
6520         }
6521     }
6522
6523     // weed out obviously illegal Pawn moves
6524     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6525         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6526         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6527         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6528         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6529         // note we are not allowed to test for valid (non-)capture, due to premove
6530     }
6531
6532     // we either have a choice what to promote to, or (in Shogi) whether to promote
6533     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6534        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6535         *promoChoice = PieceToChar(BlackFerz);  // no choice
6536         return FALSE;
6537     }
6538     // no sense asking what we must promote to if it is going to explode...
6539     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6540         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6541         return FALSE;
6542     }
6543     // give caller the default choice even if we will not make it
6544     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6545     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6546     if(gameInfo.variant == VariantChuChess) *promoChoice = (piece == WhitePawn || piece == BlackPawn ? 'q' : '+');
6547     if(        sweepSelect && gameInfo.variant != VariantGreat
6548                            && gameInfo.variant != VariantGrand
6549                            && gameInfo.variant != VariantSuper) return FALSE;
6550     if(autoQueen) return FALSE; // predetermined
6551
6552     // suppress promotion popup on illegal moves that are not premoves
6553     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6554               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6555     if(appData.testLegality && !premove) {
6556         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6557                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6558         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6559             return FALSE;
6560     }
6561
6562     return TRUE;
6563 }
6564
6565 int
6566 InPalace (int row, int column)
6567 {   /* [HGM] for Xiangqi */
6568     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6569          column < (BOARD_WIDTH + 4)/2 &&
6570          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6571     return FALSE;
6572 }
6573
6574 int
6575 PieceForSquare (int x, int y)
6576 {
6577   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6578      return -1;
6579   else
6580      return boards[currentMove][y][x];
6581 }
6582
6583 int
6584 OKToStartUserMove (int x, int y)
6585 {
6586     ChessSquare from_piece;
6587     int white_piece;
6588
6589     if (matchMode) return FALSE;
6590     if (gameMode == EditPosition) return TRUE;
6591
6592     if (x >= 0 && y >= 0)
6593       from_piece = boards[currentMove][y][x];
6594     else
6595       from_piece = EmptySquare;
6596
6597     if (from_piece == EmptySquare) return FALSE;
6598
6599     white_piece = (int)from_piece >= (int)WhitePawn &&
6600       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6601
6602     switch (gameMode) {
6603       case AnalyzeFile:
6604       case TwoMachinesPlay:
6605       case EndOfGame:
6606         return FALSE;
6607
6608       case IcsObserving:
6609       case IcsIdle:
6610         return FALSE;
6611
6612       case MachinePlaysWhite:
6613       case IcsPlayingBlack:
6614         if (appData.zippyPlay) return FALSE;
6615         if (white_piece) {
6616             DisplayMoveError(_("You are playing Black"));
6617             return FALSE;
6618         }
6619         break;
6620
6621       case MachinePlaysBlack:
6622       case IcsPlayingWhite:
6623         if (appData.zippyPlay) return FALSE;
6624         if (!white_piece) {
6625             DisplayMoveError(_("You are playing White"));
6626             return FALSE;
6627         }
6628         break;
6629
6630       case PlayFromGameFile:
6631             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6632       case EditGame:
6633         if (!white_piece && WhiteOnMove(currentMove)) {
6634             DisplayMoveError(_("It is White's turn"));
6635             return FALSE;
6636         }
6637         if (white_piece && !WhiteOnMove(currentMove)) {
6638             DisplayMoveError(_("It is Black's turn"));
6639             return FALSE;
6640         }
6641         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6642             /* Editing correspondence game history */
6643             /* Could disallow this or prompt for confirmation */
6644             cmailOldMove = -1;
6645         }
6646         break;
6647
6648       case BeginningOfGame:
6649         if (appData.icsActive) return FALSE;
6650         if (!appData.noChessProgram) {
6651             if (!white_piece) {
6652                 DisplayMoveError(_("You are playing White"));
6653                 return FALSE;
6654             }
6655         }
6656         break;
6657
6658       case Training:
6659         if (!white_piece && WhiteOnMove(currentMove)) {
6660             DisplayMoveError(_("It is White's turn"));
6661             return FALSE;
6662         }
6663         if (white_piece && !WhiteOnMove(currentMove)) {
6664             DisplayMoveError(_("It is Black's turn"));
6665             return FALSE;
6666         }
6667         break;
6668
6669       default:
6670       case IcsExamining:
6671         break;
6672     }
6673     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6674         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6675         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6676         && gameMode != AnalyzeFile && gameMode != Training) {
6677         DisplayMoveError(_("Displayed position is not current"));
6678         return FALSE;
6679     }
6680     return TRUE;
6681 }
6682
6683 Boolean
6684 OnlyMove (int *x, int *y, Boolean captures)
6685 {
6686     DisambiguateClosure cl;
6687     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6688     switch(gameMode) {
6689       case MachinePlaysBlack:
6690       case IcsPlayingWhite:
6691       case BeginningOfGame:
6692         if(!WhiteOnMove(currentMove)) return FALSE;
6693         break;
6694       case MachinePlaysWhite:
6695       case IcsPlayingBlack:
6696         if(WhiteOnMove(currentMove)) return FALSE;
6697         break;
6698       case EditGame:
6699         break;
6700       default:
6701         return FALSE;
6702     }
6703     cl.pieceIn = EmptySquare;
6704     cl.rfIn = *y;
6705     cl.ffIn = *x;
6706     cl.rtIn = -1;
6707     cl.ftIn = -1;
6708     cl.promoCharIn = NULLCHAR;
6709     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6710     if( cl.kind == NormalMove ||
6711         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6712         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6713         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6714       fromX = cl.ff;
6715       fromY = cl.rf;
6716       *x = cl.ft;
6717       *y = cl.rt;
6718       return TRUE;
6719     }
6720     if(cl.kind != ImpossibleMove) return FALSE;
6721     cl.pieceIn = EmptySquare;
6722     cl.rfIn = -1;
6723     cl.ffIn = -1;
6724     cl.rtIn = *y;
6725     cl.ftIn = *x;
6726     cl.promoCharIn = NULLCHAR;
6727     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6728     if( cl.kind == NormalMove ||
6729         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6730         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6731         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6732       fromX = cl.ff;
6733       fromY = cl.rf;
6734       *x = cl.ft;
6735       *y = cl.rt;
6736       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6737       return TRUE;
6738     }
6739     return FALSE;
6740 }
6741
6742 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6743 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6744 int lastLoadGameUseList = FALSE;
6745 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6746 ChessMove lastLoadGameStart = EndOfFile;
6747 int doubleClick;
6748
6749 void
6750 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6751 {
6752     ChessMove moveType;
6753     ChessSquare pup;
6754     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6755
6756     /* Check if the user is playing in turn.  This is complicated because we
6757        let the user "pick up" a piece before it is his turn.  So the piece he
6758        tried to pick up may have been captured by the time he puts it down!
6759        Therefore we use the color the user is supposed to be playing in this
6760        test, not the color of the piece that is currently on the starting
6761        square---except in EditGame mode, where the user is playing both
6762        sides; fortunately there the capture race can't happen.  (It can
6763        now happen in IcsExamining mode, but that's just too bad.  The user
6764        will get a somewhat confusing message in that case.)
6765        */
6766
6767     switch (gameMode) {
6768       case AnalyzeFile:
6769       case TwoMachinesPlay:
6770       case EndOfGame:
6771       case IcsObserving:
6772       case IcsIdle:
6773         /* We switched into a game mode where moves are not accepted,
6774            perhaps while the mouse button was down. */
6775         return;
6776
6777       case MachinePlaysWhite:
6778         /* User is moving for Black */
6779         if (WhiteOnMove(currentMove)) {
6780             DisplayMoveError(_("It is White's turn"));
6781             return;
6782         }
6783         break;
6784
6785       case MachinePlaysBlack:
6786         /* User is moving for White */
6787         if (!WhiteOnMove(currentMove)) {
6788             DisplayMoveError(_("It is Black's turn"));
6789             return;
6790         }
6791         break;
6792
6793       case PlayFromGameFile:
6794             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6795       case EditGame:
6796       case IcsExamining:
6797       case BeginningOfGame:
6798       case AnalyzeMode:
6799       case Training:
6800         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6801         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6802             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6803             /* User is moving for Black */
6804             if (WhiteOnMove(currentMove)) {
6805                 DisplayMoveError(_("It is White's turn"));
6806                 return;
6807             }
6808         } else {
6809             /* User is moving for White */
6810             if (!WhiteOnMove(currentMove)) {
6811                 DisplayMoveError(_("It is Black's turn"));
6812                 return;
6813             }
6814         }
6815         break;
6816
6817       case IcsPlayingBlack:
6818         /* User is moving for Black */
6819         if (WhiteOnMove(currentMove)) {
6820             if (!appData.premove) {
6821                 DisplayMoveError(_("It is White's turn"));
6822             } else if (toX >= 0 && toY >= 0) {
6823                 premoveToX = toX;
6824                 premoveToY = toY;
6825                 premoveFromX = fromX;
6826                 premoveFromY = fromY;
6827                 premovePromoChar = promoChar;
6828                 gotPremove = 1;
6829                 if (appData.debugMode)
6830                     fprintf(debugFP, "Got premove: fromX %d,"
6831                             "fromY %d, toX %d, toY %d\n",
6832                             fromX, fromY, toX, toY);
6833             }
6834             return;
6835         }
6836         break;
6837
6838       case IcsPlayingWhite:
6839         /* User is moving for White */
6840         if (!WhiteOnMove(currentMove)) {
6841             if (!appData.premove) {
6842                 DisplayMoveError(_("It is Black's turn"));
6843             } else if (toX >= 0 && toY >= 0) {
6844                 premoveToX = toX;
6845                 premoveToY = toY;
6846                 premoveFromX = fromX;
6847                 premoveFromY = fromY;
6848                 premovePromoChar = promoChar;
6849                 gotPremove = 1;
6850                 if (appData.debugMode)
6851                     fprintf(debugFP, "Got premove: fromX %d,"
6852                             "fromY %d, toX %d, toY %d\n",
6853                             fromX, fromY, toX, toY);
6854             }
6855             return;
6856         }
6857         break;
6858
6859       default:
6860         break;
6861
6862       case EditPosition:
6863         /* EditPosition, empty square, or different color piece;
6864            click-click move is possible */
6865         if (toX == -2 || toY == -2) {
6866             boards[0][fromY][fromX] = EmptySquare;
6867             DrawPosition(FALSE, boards[currentMove]);
6868             return;
6869         } else if (toX >= 0 && toY >= 0) {
6870             boards[0][toY][toX] = boards[0][fromY][fromX];
6871             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6872                 if(boards[0][fromY][0] != EmptySquare) {
6873                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6874                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6875                 }
6876             } else
6877             if(fromX == BOARD_RGHT+1) {
6878                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6879                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6880                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6881                 }
6882             } else
6883             boards[0][fromY][fromX] = gatingPiece;
6884             DrawPosition(FALSE, boards[currentMove]);
6885             return;
6886         }
6887         return;
6888     }
6889
6890     if(toX < 0 || toY < 0) return;
6891     pup = boards[currentMove][toY][toX];
6892
6893     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6894     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6895          if( pup != EmptySquare ) return;
6896          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6897            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6898                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6899            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6900            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6901            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6902            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6903          fromY = DROP_RANK;
6904     }
6905
6906     /* [HGM] always test for legality, to get promotion info */
6907     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6908                                          fromY, fromX, toY, toX, promoChar);
6909
6910     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6911
6912     /* [HGM] but possibly ignore an IllegalMove result */
6913     if (appData.testLegality) {
6914         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6915             DisplayMoveError(_("Illegal move"));
6916             return;
6917         }
6918     }
6919
6920     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6921         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6922              ClearPremoveHighlights(); // was included
6923         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6924         return;
6925     }
6926
6927     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6928 }
6929
6930 /* Common tail of UserMoveEvent and DropMenuEvent */
6931 int
6932 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6933 {
6934     char *bookHit = 0;
6935
6936     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6937         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6938         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6939         if(WhiteOnMove(currentMove)) {
6940             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6941         } else {
6942             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6943         }
6944     }
6945
6946     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6947        move type in caller when we know the move is a legal promotion */
6948     if(moveType == NormalMove && promoChar)
6949         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6950
6951     /* [HGM] <popupFix> The following if has been moved here from
6952        UserMoveEvent(). Because it seemed to belong here (why not allow
6953        piece drops in training games?), and because it can only be
6954        performed after it is known to what we promote. */
6955     if (gameMode == Training) {
6956       /* compare the move played on the board to the next move in the
6957        * game. If they match, display the move and the opponent's response.
6958        * If they don't match, display an error message.
6959        */
6960       int saveAnimate;
6961       Board testBoard;
6962       CopyBoard(testBoard, boards[currentMove]);
6963       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6964
6965       if (CompareBoards(testBoard, boards[currentMove+1])) {
6966         ForwardInner(currentMove+1);
6967
6968         /* Autoplay the opponent's response.
6969          * if appData.animate was TRUE when Training mode was entered,
6970          * the response will be animated.
6971          */
6972         saveAnimate = appData.animate;
6973         appData.animate = animateTraining;
6974         ForwardInner(currentMove+1);
6975         appData.animate = saveAnimate;
6976
6977         /* check for the end of the game */
6978         if (currentMove >= forwardMostMove) {
6979           gameMode = PlayFromGameFile;
6980           ModeHighlight();
6981           SetTrainingModeOff();
6982           DisplayInformation(_("End of game"));
6983         }
6984       } else {
6985         DisplayError(_("Incorrect move"), 0);
6986       }
6987       return 1;
6988     }
6989
6990   /* Ok, now we know that the move is good, so we can kill
6991      the previous line in Analysis Mode */
6992   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6993                                 && currentMove < forwardMostMove) {
6994     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6995     else forwardMostMove = currentMove;
6996   }
6997
6998   ClearMap();
6999
7000   /* If we need the chess program but it's dead, restart it */
7001   ResurrectChessProgram();
7002
7003   /* A user move restarts a paused game*/
7004   if (pausing)
7005     PauseEvent();
7006
7007   thinkOutput[0] = NULLCHAR;
7008
7009   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7010
7011   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7012     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7013     return 1;
7014   }
7015
7016   if (gameMode == BeginningOfGame) {
7017     if (appData.noChessProgram) {
7018       gameMode = EditGame;
7019       SetGameInfo();
7020     } else {
7021       char buf[MSG_SIZ];
7022       gameMode = MachinePlaysBlack;
7023       StartClocks();
7024       SetGameInfo();
7025       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7026       DisplayTitle(buf);
7027       if (first.sendName) {
7028         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7029         SendToProgram(buf, &first);
7030       }
7031       StartClocks();
7032     }
7033     ModeHighlight();
7034   }
7035
7036   /* Relay move to ICS or chess engine */
7037   if (appData.icsActive) {
7038     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7039         gameMode == IcsExamining) {
7040       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7041         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7042         SendToICS("draw ");
7043         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7044       }
7045       // also send plain move, in case ICS does not understand atomic claims
7046       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7047       ics_user_moved = 1;
7048     }
7049   } else {
7050     if (first.sendTime && (gameMode == BeginningOfGame ||
7051                            gameMode == MachinePlaysWhite ||
7052                            gameMode == MachinePlaysBlack)) {
7053       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7054     }
7055     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7056          // [HGM] book: if program might be playing, let it use book
7057         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7058         first.maybeThinking = TRUE;
7059     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7060         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7061         SendBoard(&first, currentMove+1);
7062         if(second.analyzing) {
7063             if(!second.useSetboard) SendToProgram("undo\n", &second);
7064             SendBoard(&second, currentMove+1);
7065         }
7066     } else {
7067         SendMoveToProgram(forwardMostMove-1, &first);
7068         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7069     }
7070     if (currentMove == cmailOldMove + 1) {
7071       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7072     }
7073   }
7074
7075   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7076
7077   switch (gameMode) {
7078   case EditGame:
7079     if(appData.testLegality)
7080     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7081     case MT_NONE:
7082     case MT_CHECK:
7083       break;
7084     case MT_CHECKMATE:
7085     case MT_STAINMATE:
7086       if (WhiteOnMove(currentMove)) {
7087         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7088       } else {
7089         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7090       }
7091       break;
7092     case MT_STALEMATE:
7093       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7094       break;
7095     }
7096     break;
7097
7098   case MachinePlaysBlack:
7099   case MachinePlaysWhite:
7100     /* disable certain menu options while machine is thinking */
7101     SetMachineThinkingEnables();
7102     break;
7103
7104   default:
7105     break;
7106   }
7107
7108   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7109   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7110
7111   if(bookHit) { // [HGM] book: simulate book reply
7112         static char bookMove[MSG_SIZ]; // a bit generous?
7113
7114         programStats.nodes = programStats.depth = programStats.time =
7115         programStats.score = programStats.got_only_move = 0;
7116         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7117
7118         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7119         strcat(bookMove, bookHit);
7120         HandleMachineMove(bookMove, &first);
7121   }
7122   return 1;
7123 }
7124
7125 void
7126 MarkByFEN(char *fen)
7127 {
7128         int r, f;
7129         if(!appData.markers || !appData.highlightDragging) return;
7130         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7131         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7132         while(*fen) {
7133             int s = 0;
7134             marker[r][f] = 0;
7135             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7136             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7137             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7138             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7139             if(*fen == 'T') marker[r][f++] = 0; else
7140             if(*fen == 'Y') marker[r][f++] = 1; else
7141             if(*fen == 'G') marker[r][f++] = 3; else
7142             if(*fen == 'B') marker[r][f++] = 4; else
7143             if(*fen == 'C') marker[r][f++] = 5; else
7144             if(*fen == 'M') marker[r][f++] = 6; else
7145             if(*fen == 'W') marker[r][f++] = 7; else
7146             if(*fen == 'D') marker[r][f++] = 8; else
7147             if(*fen == 'R') marker[r][f++] = 2; else {
7148                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7149               f += s; fen -= s>0;
7150             }
7151             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7152             if(r < 0) break;
7153             fen++;
7154         }
7155         DrawPosition(TRUE, NULL);
7156 }
7157
7158 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7159
7160 void
7161 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7162 {
7163     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7164     Markers *m = (Markers *) closure;
7165     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7166         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7167                          || kind == WhiteCapturesEnPassant
7168                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7169     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7170 }
7171
7172 void
7173 MarkTargetSquares (int clear)
7174 {
7175   int x, y, sum=0;
7176   if(clear) { // no reason to ever suppress clearing
7177     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7178     if(!sum) return; // nothing was cleared,no redraw needed
7179   } else {
7180     int capt = 0;
7181     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7182        !appData.testLegality || gameMode == EditPosition) return;
7183     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7184     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7185       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7186       if(capt)
7187       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7188     }
7189   }
7190   DrawPosition(FALSE, NULL);
7191 }
7192
7193 int
7194 Explode (Board board, int fromX, int fromY, int toX, int toY)
7195 {
7196     if(gameInfo.variant == VariantAtomic &&
7197        (board[toY][toX] != EmptySquare ||                     // capture?
7198         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7199                          board[fromY][fromX] == BlackPawn   )
7200       )) {
7201         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7202         return TRUE;
7203     }
7204     return FALSE;
7205 }
7206
7207 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7208
7209 int
7210 CanPromote (ChessSquare piece, int y)
7211 {
7212         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7213         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7214         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7215            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7216            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7217          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7218         return (piece == BlackPawn && y == 1 ||
7219                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7220                 piece == BlackLance && y == 1 ||
7221                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7222 }
7223
7224 void
7225 HoverEvent (int xPix, int yPix, int x, int y)
7226 {
7227         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7228         int r, f;
7229         if(!first.highlight) return;
7230         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7231         if(x == oldX && y == oldY) return; // only do something if we enter new square
7232         oldFromX = fromX; oldFromY = fromY;
7233         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7234           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7235             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7236         else if(oldX != x || oldY != y) {
7237           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7238           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7239             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7240           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7241             char buf[MSG_SIZ];
7242             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7243             SendToProgram(buf, &first);
7244           }
7245           oldX = x; oldY = y;
7246 //        SetHighlights(fromX, fromY, x, y);
7247         }
7248 }
7249
7250 void ReportClick(char *action, int x, int y)
7251 {
7252         char buf[MSG_SIZ]; // Inform engine of what user does
7253         int r, f;
7254         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7255           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7256         if(!first.highlight || gameMode == EditPosition) return;
7257         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7258         SendToProgram(buf, &first);
7259 }
7260
7261 void
7262 LeftClick (ClickType clickType, int xPix, int yPix)
7263 {
7264     int x, y;
7265     Boolean saveAnimate;
7266     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7267     char promoChoice = NULLCHAR;
7268     ChessSquare piece;
7269     static TimeMark lastClickTime, prevClickTime;
7270
7271     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7272
7273     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7274
7275     if (clickType == Press) ErrorPopDown();
7276     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7277
7278     x = EventToSquare(xPix, BOARD_WIDTH);
7279     y = EventToSquare(yPix, BOARD_HEIGHT);
7280     if (!flipView && y >= 0) {
7281         y = BOARD_HEIGHT - 1 - y;
7282     }
7283     if (flipView && x >= 0) {
7284         x = BOARD_WIDTH - 1 - x;
7285     }
7286
7287     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7288         defaultPromoChoice = promoSweep;
7289         promoSweep = EmptySquare;   // terminate sweep
7290         promoDefaultAltered = TRUE;
7291         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7292     }
7293
7294     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7295         if(clickType == Release) return; // ignore upclick of click-click destination
7296         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7297         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7298         if(gameInfo.holdingsWidth &&
7299                 (WhiteOnMove(currentMove)
7300                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7301                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7302             // click in right holdings, for determining promotion piece
7303             ChessSquare p = boards[currentMove][y][x];
7304             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7305             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7306             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7307                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7308                 fromX = fromY = -1;
7309                 return;
7310             }
7311         }
7312         DrawPosition(FALSE, boards[currentMove]);
7313         return;
7314     }
7315
7316     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7317     if(clickType == Press
7318             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7319               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7320               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7321         return;
7322
7323     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7324         // could be static click on premove from-square: abort premove
7325         gotPremove = 0;
7326         ClearPremoveHighlights();
7327     }
7328
7329     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7330         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7331
7332     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7333         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7334                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7335         defaultPromoChoice = DefaultPromoChoice(side);
7336     }
7337
7338     autoQueen = appData.alwaysPromoteToQueen;
7339
7340     if (fromX == -1) {
7341       int originalY = y;
7342       gatingPiece = EmptySquare;
7343       if (clickType != Press) {
7344         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7345             DragPieceEnd(xPix, yPix); dragging = 0;
7346             DrawPosition(FALSE, NULL);
7347         }
7348         return;
7349       }
7350       doubleClick = FALSE;
7351       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7352         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7353       }
7354       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7355       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7356          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7357          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7358             /* First square */
7359             if (OKToStartUserMove(fromX, fromY)) {
7360                 second = 0;
7361                 ReportClick("lift", x, y);
7362                 MarkTargetSquares(0);
7363                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7364                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7365                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7366                     promoSweep = defaultPromoChoice;
7367                     selectFlag = 0; lastX = xPix; lastY = yPix;
7368                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7369                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7370                 }
7371                 if (appData.highlightDragging) {
7372                     SetHighlights(fromX, fromY, -1, -1);
7373                 } else {
7374                     ClearHighlights();
7375                 }
7376             } else fromX = fromY = -1;
7377             return;
7378         }
7379     }
7380
7381     /* fromX != -1 */
7382     if (clickType == Press && gameMode != EditPosition) {
7383         ChessSquare fromP;
7384         ChessSquare toP;
7385         int frc;
7386
7387         // ignore off-board to clicks
7388         if(y < 0 || x < 0) return;
7389
7390         /* Check if clicking again on the same color piece */
7391         fromP = boards[currentMove][fromY][fromX];
7392         toP = boards[currentMove][y][x];
7393         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7394         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7395            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7396              WhitePawn <= toP && toP <= WhiteKing &&
7397              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7398              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7399             (BlackPawn <= fromP && fromP <= BlackKing &&
7400              BlackPawn <= toP && toP <= BlackKing &&
7401              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7402              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7403             /* Clicked again on same color piece -- changed his mind */
7404             second = (x == fromX && y == fromY);
7405             killX = killY = -1;
7406             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7407                 second = FALSE; // first double-click rather than scond click
7408                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7409             }
7410             promoDefaultAltered = FALSE;
7411             MarkTargetSquares(1);
7412            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7413             if (appData.highlightDragging) {
7414                 SetHighlights(x, y, -1, -1);
7415             } else {
7416                 ClearHighlights();
7417             }
7418             if (OKToStartUserMove(x, y)) {
7419                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7420                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7421                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7422                  gatingPiece = boards[currentMove][fromY][fromX];
7423                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7424                 fromX = x;
7425                 fromY = y; dragging = 1;
7426                 ReportClick("lift", x, y);
7427                 MarkTargetSquares(0);
7428                 DragPieceBegin(xPix, yPix, FALSE);
7429                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7430                     promoSweep = defaultPromoChoice;
7431                     selectFlag = 0; lastX = xPix; lastY = yPix;
7432                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7433                 }
7434             }
7435            }
7436            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7437            second = FALSE;
7438         }
7439         // ignore clicks on holdings
7440         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7441     }
7442
7443     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7444         DragPieceEnd(xPix, yPix); dragging = 0;
7445         if(clearFlag) {
7446             // a deferred attempt to click-click move an empty square on top of a piece
7447             boards[currentMove][y][x] = EmptySquare;
7448             ClearHighlights();
7449             DrawPosition(FALSE, boards[currentMove]);
7450             fromX = fromY = -1; clearFlag = 0;
7451             return;
7452         }
7453         if (appData.animateDragging) {
7454             /* Undo animation damage if any */
7455             DrawPosition(FALSE, NULL);
7456         }
7457         if (second || sweepSelecting) {
7458             /* Second up/down in same square; just abort move */
7459             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7460             second = sweepSelecting = 0;
7461             fromX = fromY = -1;
7462             gatingPiece = EmptySquare;
7463             MarkTargetSquares(1);
7464             ClearHighlights();
7465             gotPremove = 0;
7466             ClearPremoveHighlights();
7467         } else {
7468             /* First upclick in same square; start click-click mode */
7469             SetHighlights(x, y, -1, -1);
7470         }
7471         return;
7472     }
7473
7474     clearFlag = 0;
7475
7476     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7477         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7478         DisplayMessage(_("only marked squares are legal"),"");
7479         DrawPosition(TRUE, NULL);
7480         return; // ignore to-click
7481     }
7482
7483     /* we now have a different from- and (possibly off-board) to-square */
7484     /* Completed move */
7485     if(!sweepSelecting) {
7486         toX = x;
7487         toY = y;
7488     }
7489
7490     saveAnimate = appData.animate;
7491     if (clickType == Press) {
7492         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7493             // must be Edit Position mode with empty-square selected
7494             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7495             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7496             return;
7497         }
7498         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7499             return;
7500         }
7501         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7502             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7503         } else
7504         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7505         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7506           if(appData.sweepSelect) {
7507             ChessSquare piece = boards[currentMove][fromY][fromX];
7508             promoSweep = defaultPromoChoice;
7509             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7510             selectFlag = 0; lastX = xPix; lastY = yPix;
7511             Sweep(0); // Pawn that is going to promote: preview promotion piece
7512             sweepSelecting = 1;
7513             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7514             MarkTargetSquares(1);
7515           }
7516           return; // promo popup appears on up-click
7517         }
7518         /* Finish clickclick move */
7519         if (appData.animate || appData.highlightLastMove) {
7520             SetHighlights(fromX, fromY, toX, toY);
7521         } else {
7522             ClearHighlights();
7523         }
7524     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7525         sweepSelecting = 0;
7526         if (appData.animate || appData.highlightLastMove) {
7527             SetHighlights(fromX, fromY, toX, toY);
7528         } else {
7529             ClearHighlights();
7530         }
7531     } else {
7532 #if 0
7533 // [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
7534         /* Finish drag move */
7535         if (appData.highlightLastMove) {
7536             SetHighlights(fromX, fromY, toX, toY);
7537         } else {
7538             ClearHighlights();
7539         }
7540 #endif
7541         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7542           dragging *= 2;            // flag button-less dragging if we are dragging
7543           MarkTargetSquares(1);
7544           if(x == killX && y == killY) killX = killY = -1; else {
7545             killX = x; killY = y;     //remeber this square as intermediate
7546             ReportClick("put", x, y); // and inform engine
7547             ReportClick("lift", x, y);
7548             MarkTargetSquares(0);
7549             return;
7550           }
7551         }
7552         DragPieceEnd(xPix, yPix); dragging = 0;
7553         /* Don't animate move and drag both */
7554         appData.animate = FALSE;
7555     }
7556
7557     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7558     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7559         ChessSquare piece = boards[currentMove][fromY][fromX];
7560         if(gameMode == EditPosition && piece != EmptySquare &&
7561            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7562             int n;
7563
7564             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7565                 n = PieceToNumber(piece - (int)BlackPawn);
7566                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7567                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7568                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7569             } else
7570             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7571                 n = PieceToNumber(piece);
7572                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7573                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7574                 boards[currentMove][n][BOARD_WIDTH-2]++;
7575             }
7576             boards[currentMove][fromY][fromX] = EmptySquare;
7577         }
7578         ClearHighlights();
7579         fromX = fromY = -1;
7580         MarkTargetSquares(1);
7581         DrawPosition(TRUE, boards[currentMove]);
7582         return;
7583     }
7584
7585     // off-board moves should not be highlighted
7586     if(x < 0 || y < 0) ClearHighlights();
7587     else ReportClick("put", x, y);
7588
7589     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7590
7591     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7592         SetHighlights(fromX, fromY, toX, toY);
7593         MarkTargetSquares(1);
7594         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7595             // [HGM] super: promotion to captured piece selected from holdings
7596             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7597             promotionChoice = TRUE;
7598             // kludge follows to temporarily execute move on display, without promoting yet
7599             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7600             boards[currentMove][toY][toX] = p;
7601             DrawPosition(FALSE, boards[currentMove]);
7602             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7603             boards[currentMove][toY][toX] = q;
7604             DisplayMessage("Click in holdings to choose piece", "");
7605             return;
7606         }
7607         PromotionPopUp();
7608     } else {
7609         int oldMove = currentMove;
7610         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7611         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7612         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7613         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7614            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7615             DrawPosition(TRUE, boards[currentMove]);
7616         MarkTargetSquares(1);
7617         fromX = fromY = -1;
7618     }
7619     appData.animate = saveAnimate;
7620     if (appData.animate || appData.animateDragging) {
7621         /* Undo animation damage if needed */
7622         DrawPosition(FALSE, NULL);
7623     }
7624 }
7625
7626 int
7627 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7628 {   // front-end-free part taken out of PieceMenuPopup
7629     int whichMenu; int xSqr, ySqr;
7630
7631     if(seekGraphUp) { // [HGM] seekgraph
7632         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7633         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7634         return -2;
7635     }
7636
7637     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7638          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7639         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7640         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7641         if(action == Press)   {
7642             originalFlip = flipView;
7643             flipView = !flipView; // temporarily flip board to see game from partners perspective
7644             DrawPosition(TRUE, partnerBoard);
7645             DisplayMessage(partnerStatus, "");
7646             partnerUp = TRUE;
7647         } else if(action == Release) {
7648             flipView = originalFlip;
7649             DrawPosition(TRUE, boards[currentMove]);
7650             partnerUp = FALSE;
7651         }
7652         return -2;
7653     }
7654
7655     xSqr = EventToSquare(x, BOARD_WIDTH);
7656     ySqr = EventToSquare(y, BOARD_HEIGHT);
7657     if (action == Release) {
7658         if(pieceSweep != EmptySquare) {
7659             EditPositionMenuEvent(pieceSweep, toX, toY);
7660             pieceSweep = EmptySquare;
7661         } else UnLoadPV(); // [HGM] pv
7662     }
7663     if (action != Press) return -2; // return code to be ignored
7664     switch (gameMode) {
7665       case IcsExamining:
7666         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7667       case EditPosition:
7668         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7669         if (xSqr < 0 || ySqr < 0) return -1;
7670         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7671         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7672         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7673         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7674         NextPiece(0);
7675         return 2; // grab
7676       case IcsObserving:
7677         if(!appData.icsEngineAnalyze) return -1;
7678       case IcsPlayingWhite:
7679       case IcsPlayingBlack:
7680         if(!appData.zippyPlay) goto noZip;
7681       case AnalyzeMode:
7682       case AnalyzeFile:
7683       case MachinePlaysWhite:
7684       case MachinePlaysBlack:
7685       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7686         if (!appData.dropMenu) {
7687           LoadPV(x, y);
7688           return 2; // flag front-end to grab mouse events
7689         }
7690         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7691            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7692       case EditGame:
7693       noZip:
7694         if (xSqr < 0 || ySqr < 0) return -1;
7695         if (!appData.dropMenu || appData.testLegality &&
7696             gameInfo.variant != VariantBughouse &&
7697             gameInfo.variant != VariantCrazyhouse) return -1;
7698         whichMenu = 1; // drop menu
7699         break;
7700       default:
7701         return -1;
7702     }
7703
7704     if (((*fromX = xSqr) < 0) ||
7705         ((*fromY = ySqr) < 0)) {
7706         *fromX = *fromY = -1;
7707         return -1;
7708     }
7709     if (flipView)
7710       *fromX = BOARD_WIDTH - 1 - *fromX;
7711     else
7712       *fromY = BOARD_HEIGHT - 1 - *fromY;
7713
7714     return whichMenu;
7715 }
7716
7717 void
7718 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7719 {
7720 //    char * hint = lastHint;
7721     FrontEndProgramStats stats;
7722
7723     stats.which = cps == &first ? 0 : 1;
7724     stats.depth = cpstats->depth;
7725     stats.nodes = cpstats->nodes;
7726     stats.score = cpstats->score;
7727     stats.time = cpstats->time;
7728     stats.pv = cpstats->movelist;
7729     stats.hint = lastHint;
7730     stats.an_move_index = 0;
7731     stats.an_move_count = 0;
7732
7733     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7734         stats.hint = cpstats->move_name;
7735         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7736         stats.an_move_count = cpstats->nr_moves;
7737     }
7738
7739     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
7740
7741     SetProgramStats( &stats );
7742 }
7743
7744 void
7745 ClearEngineOutputPane (int which)
7746 {
7747     static FrontEndProgramStats dummyStats;
7748     dummyStats.which = which;
7749     dummyStats.pv = "#";
7750     SetProgramStats( &dummyStats );
7751 }
7752
7753 #define MAXPLAYERS 500
7754
7755 char *
7756 TourneyStandings (int display)
7757 {
7758     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7759     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7760     char result, *p, *names[MAXPLAYERS];
7761
7762     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7763         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7764     names[0] = p = strdup(appData.participants);
7765     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7766
7767     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7768
7769     while(result = appData.results[nr]) {
7770         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7771         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7772         wScore = bScore = 0;
7773         switch(result) {
7774           case '+': wScore = 2; break;
7775           case '-': bScore = 2; break;
7776           case '=': wScore = bScore = 1; break;
7777           case ' ':
7778           case '*': return strdup("busy"); // tourney not finished
7779         }
7780         score[w] += wScore;
7781         score[b] += bScore;
7782         games[w]++;
7783         games[b]++;
7784         nr++;
7785     }
7786     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7787     for(w=0; w<nPlayers; w++) {
7788         bScore = -1;
7789         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7790         ranking[w] = b; points[w] = bScore; score[b] = -2;
7791     }
7792     p = malloc(nPlayers*34+1);
7793     for(w=0; w<nPlayers && w<display; w++)
7794         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7795     free(names[0]);
7796     return p;
7797 }
7798
7799 void
7800 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7801 {       // count all piece types
7802         int p, f, r;
7803         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7804         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7805         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7806                 p = board[r][f];
7807                 pCnt[p]++;
7808                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7809                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7810                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7811                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7812                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7813                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7814         }
7815 }
7816
7817 int
7818 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7819 {
7820         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7821         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7822
7823         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7824         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7825         if(myPawns == 2 && nMine == 3) // KPP
7826             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7827         if(myPawns == 1 && nMine == 2) // KP
7828             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7829         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7830             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7831         if(myPawns) return FALSE;
7832         if(pCnt[WhiteRook+side])
7833             return pCnt[BlackRook-side] ||
7834                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7835                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7836                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7837         if(pCnt[WhiteCannon+side]) {
7838             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7839             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7840         }
7841         if(pCnt[WhiteKnight+side])
7842             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7843         return FALSE;
7844 }
7845
7846 int
7847 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7848 {
7849         VariantClass v = gameInfo.variant;
7850
7851         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7852         if(v == VariantShatranj) return TRUE; // always winnable through baring
7853         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7854         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7855
7856         if(v == VariantXiangqi) {
7857                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7858
7859                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7860                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7861                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7862                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7863                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7864                 if(stale) // we have at least one last-rank P plus perhaps C
7865                     return majors // KPKX
7866                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7867                 else // KCA*E*
7868                     return pCnt[WhiteFerz+side] // KCAK
7869                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7870                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7871                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7872
7873         } else if(v == VariantKnightmate) {
7874                 if(nMine == 1) return FALSE;
7875                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7876         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7877                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7878
7879                 if(nMine == 1) return FALSE; // bare King
7880                 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
7881                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7882                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7883                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7884                 if(pCnt[WhiteKnight+side])
7885                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7886                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7887                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7888                 if(nBishops)
7889                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7890                 if(pCnt[WhiteAlfil+side])
7891                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7892                 if(pCnt[WhiteWazir+side])
7893                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7894         }
7895
7896         return TRUE;
7897 }
7898
7899 int
7900 CompareWithRights (Board b1, Board b2)
7901 {
7902     int rights = 0;
7903     if(!CompareBoards(b1, b2)) return FALSE;
7904     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7905     /* compare castling rights */
7906     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7907            rights++; /* King lost rights, while rook still had them */
7908     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7909         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7910            rights++; /* but at least one rook lost them */
7911     }
7912     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7913            rights++;
7914     if( b1[CASTLING][5] != NoRights ) {
7915         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7916            rights++;
7917     }
7918     return rights == 0;
7919 }
7920
7921 int
7922 Adjudicate (ChessProgramState *cps)
7923 {       // [HGM] some adjudications useful with buggy engines
7924         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7925         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7926         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7927         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7928         int k, drop, count = 0; static int bare = 1;
7929         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7930         Boolean canAdjudicate = !appData.icsActive;
7931
7932         // most tests only when we understand the game, i.e. legality-checking on
7933             if( appData.testLegality )
7934             {   /* [HGM] Some more adjudications for obstinate engines */
7935                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7936                 static int moveCount = 6;
7937                 ChessMove result;
7938                 char *reason = NULL;
7939
7940                 /* Count what is on board. */
7941                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7942
7943                 /* Some material-based adjudications that have to be made before stalemate test */
7944                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7945                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7946                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7947                      if(canAdjudicate && appData.checkMates) {
7948                          if(engineOpponent)
7949                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7950                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7951                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7952                          return 1;
7953                      }
7954                 }
7955
7956                 /* Bare King in Shatranj (loses) or Losers (wins) */
7957                 if( nrW == 1 || nrB == 1) {
7958                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7959                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7960                      if(canAdjudicate && appData.checkMates) {
7961                          if(engineOpponent)
7962                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7963                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7964                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7965                          return 1;
7966                      }
7967                   } else
7968                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7969                   {    /* bare King */
7970                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7971                         if(canAdjudicate && appData.checkMates) {
7972                             /* but only adjudicate if adjudication enabled */
7973                             if(engineOpponent)
7974                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7975                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7976                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7977                             return 1;
7978                         }
7979                   }
7980                 } else bare = 1;
7981
7982
7983             // don't wait for engine to announce game end if we can judge ourselves
7984             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7985               case MT_CHECK:
7986                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7987                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7988                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7989                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7990                             checkCnt++;
7991                         if(checkCnt >= 2) {
7992                             reason = "Xboard adjudication: 3rd check";
7993                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7994                             break;
7995                         }
7996                     }
7997                 }
7998               case MT_NONE:
7999               default:
8000                 break;
8001               case MT_STALEMATE:
8002               case MT_STAINMATE:
8003                 reason = "Xboard adjudication: Stalemate";
8004                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8005                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8006                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8007                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8008                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8009                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8010                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8011                                                                         EP_CHECKMATE : EP_WINS);
8012                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8013                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8014                 }
8015                 break;
8016               case MT_CHECKMATE:
8017                 reason = "Xboard adjudication: Checkmate";
8018                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8019                 if(gameInfo.variant == VariantShogi) {
8020                     if(forwardMostMove > backwardMostMove
8021                        && moveList[forwardMostMove-1][1] == '@'
8022                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8023                         reason = "XBoard adjudication: pawn-drop mate";
8024                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8025                     }
8026                 }
8027                 break;
8028             }
8029
8030                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8031                     case EP_STALEMATE:
8032                         result = GameIsDrawn; break;
8033                     case EP_CHECKMATE:
8034                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8035                     case EP_WINS:
8036                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8037                     default:
8038                         result = EndOfFile;
8039                 }
8040                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8041                     if(engineOpponent)
8042                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8043                     GameEnds( result, reason, GE_XBOARD );
8044                     return 1;
8045                 }
8046
8047                 /* Next absolutely insufficient mating material. */
8048                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8049                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8050                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8051
8052                      /* always flag draws, for judging claims */
8053                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8054
8055                      if(canAdjudicate && appData.materialDraws) {
8056                          /* but only adjudicate them if adjudication enabled */
8057                          if(engineOpponent) {
8058                            SendToProgram("force\n", engineOpponent); // suppress reply
8059                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8060                          }
8061                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8062                          return 1;
8063                      }
8064                 }
8065
8066                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8067                 if(gameInfo.variant == VariantXiangqi ?
8068                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8069                  : nrW + nrB == 4 &&
8070                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8071                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8072                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8073                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8074                    ) ) {
8075                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8076                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8077                           if(engineOpponent) {
8078                             SendToProgram("force\n", engineOpponent); // suppress reply
8079                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8080                           }
8081                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8082                           return 1;
8083                      }
8084                 } else moveCount = 6;
8085             }
8086
8087         // Repetition draws and 50-move rule can be applied independently of legality testing
8088
8089                 /* Check for rep-draws */
8090                 count = 0;
8091                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8092                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8093                 for(k = forwardMostMove-2;
8094                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8095                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8096                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8097                     k-=2)
8098                 {   int rights=0;
8099                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8100                         /* compare castling rights */
8101                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8102                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8103                                 rights++; /* King lost rights, while rook still had them */
8104                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8105                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8106                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8107                                    rights++; /* but at least one rook lost them */
8108                         }
8109                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8110                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8111                                 rights++;
8112                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8113                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8114                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8115                                    rights++;
8116                         }
8117                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8118                             && appData.drawRepeats > 1) {
8119                              /* adjudicate after user-specified nr of repeats */
8120                              int result = GameIsDrawn;
8121                              char *details = "XBoard adjudication: repetition draw";
8122                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8123                                 // [HGM] xiangqi: check for forbidden perpetuals
8124                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8125                                 for(m=forwardMostMove; m>k; m-=2) {
8126                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8127                                         ourPerpetual = 0; // the current mover did not always check
8128                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8129                                         hisPerpetual = 0; // the opponent did not always check
8130                                 }
8131                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8132                                                                         ourPerpetual, hisPerpetual);
8133                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8134                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8135                                     details = "Xboard adjudication: perpetual checking";
8136                                 } else
8137                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8138                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8139                                 } else
8140                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8141                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8142                                         result = BlackWins;
8143                                         details = "Xboard adjudication: repetition";
8144                                     }
8145                                 } else // it must be XQ
8146                                 // Now check for perpetual chases
8147                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8148                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8149                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8150                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8151                                         static char resdet[MSG_SIZ];
8152                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8153                                         details = resdet;
8154                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8155                                     } else
8156                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8157                                         break; // Abort repetition-checking loop.
8158                                 }
8159                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8160                              }
8161                              if(engineOpponent) {
8162                                SendToProgram("force\n", engineOpponent); // suppress reply
8163                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8164                              }
8165                              GameEnds( result, details, GE_XBOARD );
8166                              return 1;
8167                         }
8168                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8169                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8170                     }
8171                 }
8172
8173                 /* Now we test for 50-move draws. Determine ply count */
8174                 count = forwardMostMove;
8175                 /* look for last irreversble move */
8176                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8177                     count--;
8178                 /* if we hit starting position, add initial plies */
8179                 if( count == backwardMostMove )
8180                     count -= initialRulePlies;
8181                 count = forwardMostMove - count;
8182                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8183                         // adjust reversible move counter for checks in Xiangqi
8184                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8185                         if(i < backwardMostMove) i = backwardMostMove;
8186                         while(i <= forwardMostMove) {
8187                                 lastCheck = inCheck; // check evasion does not count
8188                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8189                                 if(inCheck || lastCheck) count--; // check does not count
8190                                 i++;
8191                         }
8192                 }
8193                 if( count >= 100)
8194                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8195                          /* this is used to judge if draw claims are legal */
8196                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8197                          if(engineOpponent) {
8198                            SendToProgram("force\n", engineOpponent); // suppress reply
8199                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8200                          }
8201                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8202                          return 1;
8203                 }
8204
8205                 /* if draw offer is pending, treat it as a draw claim
8206                  * when draw condition present, to allow engines a way to
8207                  * claim draws before making their move to avoid a race
8208                  * condition occurring after their move
8209                  */
8210                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8211                          char *p = NULL;
8212                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8213                              p = "Draw claim: 50-move rule";
8214                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8215                              p = "Draw claim: 3-fold repetition";
8216                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8217                              p = "Draw claim: insufficient mating material";
8218                          if( p != NULL && canAdjudicate) {
8219                              if(engineOpponent) {
8220                                SendToProgram("force\n", engineOpponent); // suppress reply
8221                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8222                              }
8223                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8224                              return 1;
8225                          }
8226                 }
8227
8228                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8229                     if(engineOpponent) {
8230                       SendToProgram("force\n", engineOpponent); // suppress reply
8231                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8232                     }
8233                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8234                     return 1;
8235                 }
8236         return 0;
8237 }
8238
8239 char *
8240 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8241 {   // [HGM] book: this routine intercepts moves to simulate book replies
8242     char *bookHit = NULL;
8243
8244     //first determine if the incoming move brings opponent into his book
8245     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8246         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8247     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8248     if(bookHit != NULL && !cps->bookSuspend) {
8249         // make sure opponent is not going to reply after receiving move to book position
8250         SendToProgram("force\n", cps);
8251         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8252     }
8253     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8254     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8255     // now arrange restart after book miss
8256     if(bookHit) {
8257         // after a book hit we never send 'go', and the code after the call to this routine
8258         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8259         char buf[MSG_SIZ], *move = bookHit;
8260         if(cps->useSAN) {
8261             int fromX, fromY, toX, toY;
8262             char promoChar;
8263             ChessMove moveType;
8264             move = buf + 30;
8265             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8266                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8267                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8268                                     PosFlags(forwardMostMove),
8269                                     fromY, fromX, toY, toX, promoChar, move);
8270             } else {
8271                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8272                 bookHit = NULL;
8273             }
8274         }
8275         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8276         SendToProgram(buf, cps);
8277         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8278     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8279         SendToProgram("go\n", cps);
8280         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8281     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8282         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8283             SendToProgram("go\n", cps);
8284         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8285     }
8286     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8287 }
8288
8289 int
8290 LoadError (char *errmess, ChessProgramState *cps)
8291 {   // unloads engine and switches back to -ncp mode if it was first
8292     if(cps->initDone) return FALSE;
8293     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8294     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8295     cps->pr = NoProc;
8296     if(cps == &first) {
8297         appData.noChessProgram = TRUE;
8298         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8299         gameMode = BeginningOfGame; ModeHighlight();
8300         SetNCPMode();
8301     }
8302     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8303     DisplayMessage("", ""); // erase waiting message
8304     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8305     return TRUE;
8306 }
8307
8308 char *savedMessage;
8309 ChessProgramState *savedState;
8310 void
8311 DeferredBookMove (void)
8312 {
8313         if(savedState->lastPing != savedState->lastPong)
8314                     ScheduleDelayedEvent(DeferredBookMove, 10);
8315         else
8316         HandleMachineMove(savedMessage, savedState);
8317 }
8318
8319 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8320 static ChessProgramState *stalledEngine;
8321 static char stashedInputMove[MSG_SIZ];
8322
8323 void
8324 HandleMachineMove (char *message, ChessProgramState *cps)
8325 {
8326     static char firstLeg[20];
8327     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8328     char realname[MSG_SIZ];
8329     int fromX, fromY, toX, toY;
8330     ChessMove moveType;
8331     char promoChar, roar;
8332     char *p, *pv=buf1;
8333     int machineWhite, oldError;
8334     char *bookHit;
8335
8336     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8337         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8338         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8339             DisplayError(_("Invalid pairing from pairing engine"), 0);
8340             return;
8341         }
8342         pairingReceived = 1;
8343         NextMatchGame();
8344         return; // Skim the pairing messages here.
8345     }
8346
8347     oldError = cps->userError; cps->userError = 0;
8348
8349 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8350     /*
8351      * Kludge to ignore BEL characters
8352      */
8353     while (*message == '\007') message++;
8354
8355     /*
8356      * [HGM] engine debug message: ignore lines starting with '#' character
8357      */
8358     if(cps->debug && *message == '#') return;
8359
8360     /*
8361      * Look for book output
8362      */
8363     if (cps == &first && bookRequested) {
8364         if (message[0] == '\t' || message[0] == ' ') {
8365             /* Part of the book output is here; append it */
8366             strcat(bookOutput, message);
8367             strcat(bookOutput, "  \n");
8368             return;
8369         } else if (bookOutput[0] != NULLCHAR) {
8370             /* All of book output has arrived; display it */
8371             char *p = bookOutput;
8372             while (*p != NULLCHAR) {
8373                 if (*p == '\t') *p = ' ';
8374                 p++;
8375             }
8376             DisplayInformation(bookOutput);
8377             bookRequested = FALSE;
8378             /* Fall through to parse the current output */
8379         }
8380     }
8381
8382     /*
8383      * Look for machine move.
8384      */
8385     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8386         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8387     {
8388         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8389             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8390             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8391             stalledEngine = cps;
8392             if(appData.ponderNextMove) { // bring opponent out of ponder
8393                 if(gameMode == TwoMachinesPlay) {
8394                     if(cps->other->pause)
8395                         PauseEngine(cps->other);
8396                     else
8397                         SendToProgram("easy\n", cps->other);
8398                 }
8399             }
8400             StopClocks();
8401             return;
8402         }
8403
8404         /* This method is only useful on engines that support ping */
8405         if (cps->lastPing != cps->lastPong) {
8406           if (gameMode == BeginningOfGame) {
8407             /* Extra move from before last new; ignore */
8408             if (appData.debugMode) {
8409                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8410             }
8411           } else {
8412             if (appData.debugMode) {
8413                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8414                         cps->which, gameMode);
8415             }
8416
8417             SendToProgram("undo\n", cps);
8418           }
8419           return;
8420         }
8421
8422         switch (gameMode) {
8423           case BeginningOfGame:
8424             /* Extra move from before last reset; ignore */
8425             if (appData.debugMode) {
8426                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8427             }
8428             return;
8429
8430           case EndOfGame:
8431           case IcsIdle:
8432           default:
8433             /* Extra move after we tried to stop.  The mode test is
8434                not a reliable way of detecting this problem, but it's
8435                the best we can do on engines that don't support ping.
8436             */
8437             if (appData.debugMode) {
8438                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8439                         cps->which, gameMode);
8440             }
8441             SendToProgram("undo\n", cps);
8442             return;
8443
8444           case MachinePlaysWhite:
8445           case IcsPlayingWhite:
8446             machineWhite = TRUE;
8447             break;
8448
8449           case MachinePlaysBlack:
8450           case IcsPlayingBlack:
8451             machineWhite = FALSE;
8452             break;
8453
8454           case TwoMachinesPlay:
8455             machineWhite = (cps->twoMachinesColor[0] == 'w');
8456             break;
8457         }
8458         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8459             if (appData.debugMode) {
8460                 fprintf(debugFP,
8461                         "Ignoring move out of turn by %s, gameMode %d"
8462                         ", forwardMost %d\n",
8463                         cps->which, gameMode, forwardMostMove);
8464             }
8465             return;
8466         }
8467
8468         if(cps->alphaRank) AlphaRank(machineMove, 4);
8469
8470         // [HGM] lion: (some very limited) support for Alien protocol
8471         killX = killY = -1;
8472         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8473             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8474             return;
8475         } else if(firstLeg[0]) { // there was a previous leg;
8476             // only support case where same piece makes two step (and don't even test that!)
8477             char buf[20], *p = machineMove+1, *q = buf+1, f;
8478             safeStrCpy(buf, machineMove, 20);
8479             while(isdigit(*q)) q++; // find start of to-square
8480             safeStrCpy(machineMove, firstLeg, 20);
8481             while(isdigit(*p)) p++;
8482             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8483             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8484             firstLeg[0] = NULLCHAR;
8485         }
8486
8487         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8488                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8489             /* Machine move could not be parsed; ignore it. */
8490           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8491                     machineMove, _(cps->which));
8492             DisplayMoveError(buf1);
8493             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8494                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8495             if (gameMode == TwoMachinesPlay) {
8496               GameEnds(machineWhite ? BlackWins : WhiteWins,
8497                        buf1, GE_XBOARD);
8498             }
8499             return;
8500         }
8501
8502         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8503         /* So we have to redo legality test with true e.p. status here,  */
8504         /* to make sure an illegal e.p. capture does not slip through,   */
8505         /* to cause a forfeit on a justified illegal-move complaint      */
8506         /* of the opponent.                                              */
8507         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8508            ChessMove moveType;
8509            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8510                              fromY, fromX, toY, toX, promoChar);
8511             if(moveType == IllegalMove) {
8512               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8513                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8514                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8515                            buf1, GE_XBOARD);
8516                 return;
8517            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8518            /* [HGM] Kludge to handle engines that send FRC-style castling
8519               when they shouldn't (like TSCP-Gothic) */
8520            switch(moveType) {
8521              case WhiteASideCastleFR:
8522              case BlackASideCastleFR:
8523                toX+=2;
8524                currentMoveString[2]++;
8525                break;
8526              case WhiteHSideCastleFR:
8527              case BlackHSideCastleFR:
8528                toX--;
8529                currentMoveString[2]--;
8530                break;
8531              default: ; // nothing to do, but suppresses warning of pedantic compilers
8532            }
8533         }
8534         hintRequested = FALSE;
8535         lastHint[0] = NULLCHAR;
8536         bookRequested = FALSE;
8537         /* Program may be pondering now */
8538         cps->maybeThinking = TRUE;
8539         if (cps->sendTime == 2) cps->sendTime = 1;
8540         if (cps->offeredDraw) cps->offeredDraw--;
8541
8542         /* [AS] Save move info*/
8543         pvInfoList[ forwardMostMove ].score = programStats.score;
8544         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8545         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8546
8547         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8548
8549         /* Test suites abort the 'game' after one move */
8550         if(*appData.finger) {
8551            static FILE *f;
8552            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8553            if(!f) f = fopen(appData.finger, "w");
8554            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8555            else { DisplayFatalError("Bad output file", errno, 0); return; }
8556            free(fen);
8557            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8558         }
8559
8560         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8561         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8562             int count = 0;
8563
8564             while( count < adjudicateLossPlies ) {
8565                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8566
8567                 if( count & 1 ) {
8568                     score = -score; /* Flip score for winning side */
8569                 }
8570
8571                 if( score > adjudicateLossThreshold ) {
8572                     break;
8573                 }
8574
8575                 count++;
8576             }
8577
8578             if( count >= adjudicateLossPlies ) {
8579                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8580
8581                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8582                     "Xboard adjudication",
8583                     GE_XBOARD );
8584
8585                 return;
8586             }
8587         }
8588
8589         if(Adjudicate(cps)) {
8590             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8591             return; // [HGM] adjudicate: for all automatic game ends
8592         }
8593
8594 #if ZIPPY
8595         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8596             first.initDone) {
8597           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8598                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8599                 SendToICS("draw ");
8600                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8601           }
8602           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8603           ics_user_moved = 1;
8604           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8605                 char buf[3*MSG_SIZ];
8606
8607                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8608                         programStats.score / 100.,
8609                         programStats.depth,
8610                         programStats.time / 100.,
8611                         (unsigned int)programStats.nodes,
8612                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8613                         programStats.movelist);
8614                 SendToICS(buf);
8615 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8616           }
8617         }
8618 #endif
8619
8620         /* [AS] Clear stats for next move */
8621         ClearProgramStats();
8622         thinkOutput[0] = NULLCHAR;
8623         hiddenThinkOutputState = 0;
8624
8625         bookHit = NULL;
8626         if (gameMode == TwoMachinesPlay) {
8627             /* [HGM] relaying draw offers moved to after reception of move */
8628             /* and interpreting offer as claim if it brings draw condition */
8629             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8630                 SendToProgram("draw\n", cps->other);
8631             }
8632             if (cps->other->sendTime) {
8633                 SendTimeRemaining(cps->other,
8634                                   cps->other->twoMachinesColor[0] == 'w');
8635             }
8636             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8637             if (firstMove && !bookHit) {
8638                 firstMove = FALSE;
8639                 if (cps->other->useColors) {
8640                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8641                 }
8642                 SendToProgram("go\n", cps->other);
8643             }
8644             cps->other->maybeThinking = TRUE;
8645         }
8646
8647         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8648
8649         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8650
8651         if (!pausing && appData.ringBellAfterMoves) {
8652             if(!roar) RingBell();
8653         }
8654
8655         /*
8656          * Reenable menu items that were disabled while
8657          * machine was thinking
8658          */
8659         if (gameMode != TwoMachinesPlay)
8660             SetUserThinkingEnables();
8661
8662         // [HGM] book: after book hit opponent has received move and is now in force mode
8663         // force the book reply into it, and then fake that it outputted this move by jumping
8664         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8665         if(bookHit) {
8666                 static char bookMove[MSG_SIZ]; // a bit generous?
8667
8668                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8669                 strcat(bookMove, bookHit);
8670                 message = bookMove;
8671                 cps = cps->other;
8672                 programStats.nodes = programStats.depth = programStats.time =
8673                 programStats.score = programStats.got_only_move = 0;
8674                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8675
8676                 if(cps->lastPing != cps->lastPong) {
8677                     savedMessage = message; // args for deferred call
8678                     savedState = cps;
8679                     ScheduleDelayedEvent(DeferredBookMove, 10);
8680                     return;
8681                 }
8682                 goto FakeBookMove;
8683         }
8684
8685         return;
8686     }
8687
8688     /* Set special modes for chess engines.  Later something general
8689      *  could be added here; for now there is just one kludge feature,
8690      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8691      *  when "xboard" is given as an interactive command.
8692      */
8693     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8694         cps->useSigint = FALSE;
8695         cps->useSigterm = FALSE;
8696     }
8697     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8698       ParseFeatures(message+8, cps);
8699       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8700     }
8701
8702     if (!strncmp(message, "setup ", 6) && 
8703         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8704           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8705                                         ) { // [HGM] allow first engine to define opening position
8706       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8707       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8708       *buf = NULLCHAR;
8709       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8710       if(startedFromSetupPosition) return;
8711       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8712       if(dummy >= 3) {
8713         while(message[s] && message[s++] != ' ');
8714         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8715            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8716             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8717             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8718           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8719           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8720         }
8721       }
8722       ParseFEN(boards[0], &dummy, message+s, FALSE);
8723       DrawPosition(TRUE, boards[0]);
8724       startedFromSetupPosition = TRUE;
8725       return;
8726     }
8727     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8728      * want this, I was asked to put it in, and obliged.
8729      */
8730     if (!strncmp(message, "setboard ", 9)) {
8731         Board initial_position;
8732
8733         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8734
8735         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8736             DisplayError(_("Bad FEN received from engine"), 0);
8737             return ;
8738         } else {
8739            Reset(TRUE, FALSE);
8740            CopyBoard(boards[0], initial_position);
8741            initialRulePlies = FENrulePlies;
8742            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8743            else gameMode = MachinePlaysBlack;
8744            DrawPosition(FALSE, boards[currentMove]);
8745         }
8746         return;
8747     }
8748
8749     /*
8750      * Look for communication commands
8751      */
8752     if (!strncmp(message, "telluser ", 9)) {
8753         if(message[9] == '\\' && message[10] == '\\')
8754             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8755         PlayTellSound();
8756         DisplayNote(message + 9);
8757         return;
8758     }
8759     if (!strncmp(message, "tellusererror ", 14)) {
8760         cps->userError = 1;
8761         if(message[14] == '\\' && message[15] == '\\')
8762             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8763         PlayTellSound();
8764         DisplayError(message + 14, 0);
8765         return;
8766     }
8767     if (!strncmp(message, "tellopponent ", 13)) {
8768       if (appData.icsActive) {
8769         if (loggedOn) {
8770           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8771           SendToICS(buf1);
8772         }
8773       } else {
8774         DisplayNote(message + 13);
8775       }
8776       return;
8777     }
8778     if (!strncmp(message, "tellothers ", 11)) {
8779       if (appData.icsActive) {
8780         if (loggedOn) {
8781           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8782           SendToICS(buf1);
8783         }
8784       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8785       return;
8786     }
8787     if (!strncmp(message, "tellall ", 8)) {
8788       if (appData.icsActive) {
8789         if (loggedOn) {
8790           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8791           SendToICS(buf1);
8792         }
8793       } else {
8794         DisplayNote(message + 8);
8795       }
8796       return;
8797     }
8798     if (strncmp(message, "warning", 7) == 0) {
8799         /* Undocumented feature, use tellusererror in new code */
8800         DisplayError(message, 0);
8801         return;
8802     }
8803     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8804         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8805         strcat(realname, " query");
8806         AskQuestion(realname, buf2, buf1, cps->pr);
8807         return;
8808     }
8809     /* Commands from the engine directly to ICS.  We don't allow these to be
8810      *  sent until we are logged on. Crafty kibitzes have been known to
8811      *  interfere with the login process.
8812      */
8813     if (loggedOn) {
8814         if (!strncmp(message, "tellics ", 8)) {
8815             SendToICS(message + 8);
8816             SendToICS("\n");
8817             return;
8818         }
8819         if (!strncmp(message, "tellicsnoalias ", 15)) {
8820             SendToICS(ics_prefix);
8821             SendToICS(message + 15);
8822             SendToICS("\n");
8823             return;
8824         }
8825         /* The following are for backward compatibility only */
8826         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8827             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8828             SendToICS(ics_prefix);
8829             SendToICS(message);
8830             SendToICS("\n");
8831             return;
8832         }
8833     }
8834     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8835         if(initPing == cps->lastPong) {
8836             if(gameInfo.variant == VariantUnknown) {
8837                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8838                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8839                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8840             }
8841             initPing = -1;
8842         }
8843         return;
8844     }
8845     if(!strncmp(message, "highlight ", 10)) {
8846         if(appData.testLegality && appData.markers) return;
8847         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8848         return;
8849     }
8850     if(!strncmp(message, "click ", 6)) {
8851         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8852         if(appData.testLegality || !appData.oneClick) return;
8853         sscanf(message+6, "%c%d%c", &f, &y, &c);
8854         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8855         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8856         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8857         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8858         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8859         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8860             LeftClick(Release, lastLeftX, lastLeftY);
8861         controlKey  = (c == ',');
8862         LeftClick(Press, x, y);
8863         LeftClick(Release, x, y);
8864         first.highlight = f;
8865         return;
8866     }
8867     /*
8868      * If the move is illegal, cancel it and redraw the board.
8869      * Also deal with other error cases.  Matching is rather loose
8870      * here to accommodate engines written before the spec.
8871      */
8872     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8873         strncmp(message, "Error", 5) == 0) {
8874         if (StrStr(message, "name") ||
8875             StrStr(message, "rating") || StrStr(message, "?") ||
8876             StrStr(message, "result") || StrStr(message, "board") ||
8877             StrStr(message, "bk") || StrStr(message, "computer") ||
8878             StrStr(message, "variant") || StrStr(message, "hint") ||
8879             StrStr(message, "random") || StrStr(message, "depth") ||
8880             StrStr(message, "accepted")) {
8881             return;
8882         }
8883         if (StrStr(message, "protover")) {
8884           /* Program is responding to input, so it's apparently done
8885              initializing, and this error message indicates it is
8886              protocol version 1.  So we don't need to wait any longer
8887              for it to initialize and send feature commands. */
8888           FeatureDone(cps, 1);
8889           cps->protocolVersion = 1;
8890           return;
8891         }
8892         cps->maybeThinking = FALSE;
8893
8894         if (StrStr(message, "draw")) {
8895             /* Program doesn't have "draw" command */
8896             cps->sendDrawOffers = 0;
8897             return;
8898         }
8899         if (cps->sendTime != 1 &&
8900             (StrStr(message, "time") || StrStr(message, "otim"))) {
8901           /* Program apparently doesn't have "time" or "otim" command */
8902           cps->sendTime = 0;
8903           return;
8904         }
8905         if (StrStr(message, "analyze")) {
8906             cps->analysisSupport = FALSE;
8907             cps->analyzing = FALSE;
8908 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8909             EditGameEvent(); // [HGM] try to preserve loaded game
8910             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8911             DisplayError(buf2, 0);
8912             return;
8913         }
8914         if (StrStr(message, "(no matching move)st")) {
8915           /* Special kludge for GNU Chess 4 only */
8916           cps->stKludge = TRUE;
8917           SendTimeControl(cps, movesPerSession, timeControl,
8918                           timeIncrement, appData.searchDepth,
8919                           searchTime);
8920           return;
8921         }
8922         if (StrStr(message, "(no matching move)sd")) {
8923           /* Special kludge for GNU Chess 4 only */
8924           cps->sdKludge = TRUE;
8925           SendTimeControl(cps, movesPerSession, timeControl,
8926                           timeIncrement, appData.searchDepth,
8927                           searchTime);
8928           return;
8929         }
8930         if (!StrStr(message, "llegal")) {
8931             return;
8932         }
8933         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8934             gameMode == IcsIdle) return;
8935         if (forwardMostMove <= backwardMostMove) return;
8936         if (pausing) PauseEvent();
8937       if(appData.forceIllegal) {
8938             // [HGM] illegal: machine refused move; force position after move into it
8939           SendToProgram("force\n", cps);
8940           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8941                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8942                 // when black is to move, while there might be nothing on a2 or black
8943                 // might already have the move. So send the board as if white has the move.
8944                 // But first we must change the stm of the engine, as it refused the last move
8945                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8946                 if(WhiteOnMove(forwardMostMove)) {
8947                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8948                     SendBoard(cps, forwardMostMove); // kludgeless board
8949                 } else {
8950                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8951                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8952                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8953                 }
8954           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8955             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8956                  gameMode == TwoMachinesPlay)
8957               SendToProgram("go\n", cps);
8958             return;
8959       } else
8960         if (gameMode == PlayFromGameFile) {
8961             /* Stop reading this game file */
8962             gameMode = EditGame;
8963             ModeHighlight();
8964         }
8965         /* [HGM] illegal-move claim should forfeit game when Xboard */
8966         /* only passes fully legal moves                            */
8967         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8968             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8969                                 "False illegal-move claim", GE_XBOARD );
8970             return; // do not take back move we tested as valid
8971         }
8972         currentMove = forwardMostMove-1;
8973         DisplayMove(currentMove-1); /* before DisplayMoveError */
8974         SwitchClocks(forwardMostMove-1); // [HGM] race
8975         DisplayBothClocks();
8976         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8977                 parseList[currentMove], _(cps->which));
8978         DisplayMoveError(buf1);
8979         DrawPosition(FALSE, boards[currentMove]);
8980
8981         SetUserThinkingEnables();
8982         return;
8983     }
8984     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8985         /* Program has a broken "time" command that
8986            outputs a string not ending in newline.
8987            Don't use it. */
8988         cps->sendTime = 0;
8989     }
8990
8991     /*
8992      * If chess program startup fails, exit with an error message.
8993      * Attempts to recover here are futile. [HGM] Well, we try anyway
8994      */
8995     if ((StrStr(message, "unknown host") != NULL)
8996         || (StrStr(message, "No remote directory") != NULL)
8997         || (StrStr(message, "not found") != NULL)
8998         || (StrStr(message, "No such file") != NULL)
8999         || (StrStr(message, "can't alloc") != NULL)
9000         || (StrStr(message, "Permission denied") != NULL)) {
9001
9002         cps->maybeThinking = FALSE;
9003         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9004                 _(cps->which), cps->program, cps->host, message);
9005         RemoveInputSource(cps->isr);
9006         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9007             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9008             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9009         }
9010         return;
9011     }
9012
9013     /*
9014      * Look for hint output
9015      */
9016     if (sscanf(message, "Hint: %s", buf1) == 1) {
9017         if (cps == &first && hintRequested) {
9018             hintRequested = FALSE;
9019             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9020                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9021                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9022                                     PosFlags(forwardMostMove),
9023                                     fromY, fromX, toY, toX, promoChar, buf1);
9024                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9025                 DisplayInformation(buf2);
9026             } else {
9027                 /* Hint move could not be parsed!? */
9028               snprintf(buf2, sizeof(buf2),
9029                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9030                         buf1, _(cps->which));
9031                 DisplayError(buf2, 0);
9032             }
9033         } else {
9034           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9035         }
9036         return;
9037     }
9038
9039     /*
9040      * Ignore other messages if game is not in progress
9041      */
9042     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9043         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9044
9045     /*
9046      * look for win, lose, draw, or draw offer
9047      */
9048     if (strncmp(message, "1-0", 3) == 0) {
9049         char *p, *q, *r = "";
9050         p = strchr(message, '{');
9051         if (p) {
9052             q = strchr(p, '}');
9053             if (q) {
9054                 *q = NULLCHAR;
9055                 r = p + 1;
9056             }
9057         }
9058         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9059         return;
9060     } else if (strncmp(message, "0-1", 3) == 0) {
9061         char *p, *q, *r = "";
9062         p = strchr(message, '{');
9063         if (p) {
9064             q = strchr(p, '}');
9065             if (q) {
9066                 *q = NULLCHAR;
9067                 r = p + 1;
9068             }
9069         }
9070         /* Kludge for Arasan 4.1 bug */
9071         if (strcmp(r, "Black resigns") == 0) {
9072             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9073             return;
9074         }
9075         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9076         return;
9077     } else if (strncmp(message, "1/2", 3) == 0) {
9078         char *p, *q, *r = "";
9079         p = strchr(message, '{');
9080         if (p) {
9081             q = strchr(p, '}');
9082             if (q) {
9083                 *q = NULLCHAR;
9084                 r = p + 1;
9085             }
9086         }
9087
9088         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9089         return;
9090
9091     } else if (strncmp(message, "White resign", 12) == 0) {
9092         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9093         return;
9094     } else if (strncmp(message, "Black resign", 12) == 0) {
9095         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9096         return;
9097     } else if (strncmp(message, "White matches", 13) == 0 ||
9098                strncmp(message, "Black matches", 13) == 0   ) {
9099         /* [HGM] ignore GNUShogi noises */
9100         return;
9101     } else if (strncmp(message, "White", 5) == 0 &&
9102                message[5] != '(' &&
9103                StrStr(message, "Black") == NULL) {
9104         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9105         return;
9106     } else if (strncmp(message, "Black", 5) == 0 &&
9107                message[5] != '(') {
9108         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9109         return;
9110     } else if (strcmp(message, "resign") == 0 ||
9111                strcmp(message, "computer resigns") == 0) {
9112         switch (gameMode) {
9113           case MachinePlaysBlack:
9114           case IcsPlayingBlack:
9115             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9116             break;
9117           case MachinePlaysWhite:
9118           case IcsPlayingWhite:
9119             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9120             break;
9121           case TwoMachinesPlay:
9122             if (cps->twoMachinesColor[0] == 'w')
9123               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9124             else
9125               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9126             break;
9127           default:
9128             /* can't happen */
9129             break;
9130         }
9131         return;
9132     } else if (strncmp(message, "opponent mates", 14) == 0) {
9133         switch (gameMode) {
9134           case MachinePlaysBlack:
9135           case IcsPlayingBlack:
9136             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9137             break;
9138           case MachinePlaysWhite:
9139           case IcsPlayingWhite:
9140             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9141             break;
9142           case TwoMachinesPlay:
9143             if (cps->twoMachinesColor[0] == 'w')
9144               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9145             else
9146               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9147             break;
9148           default:
9149             /* can't happen */
9150             break;
9151         }
9152         return;
9153     } else if (strncmp(message, "computer mates", 14) == 0) {
9154         switch (gameMode) {
9155           case MachinePlaysBlack:
9156           case IcsPlayingBlack:
9157             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9158             break;
9159           case MachinePlaysWhite:
9160           case IcsPlayingWhite:
9161             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9162             break;
9163           case TwoMachinesPlay:
9164             if (cps->twoMachinesColor[0] == 'w')
9165               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9166             else
9167               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9168             break;
9169           default:
9170             /* can't happen */
9171             break;
9172         }
9173         return;
9174     } else if (strncmp(message, "checkmate", 9) == 0) {
9175         if (WhiteOnMove(forwardMostMove)) {
9176             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9177         } else {
9178             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9179         }
9180         return;
9181     } else if (strstr(message, "Draw") != NULL ||
9182                strstr(message, "game is a draw") != NULL) {
9183         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9184         return;
9185     } else if (strstr(message, "offer") != NULL &&
9186                strstr(message, "draw") != NULL) {
9187 #if ZIPPY
9188         if (appData.zippyPlay && first.initDone) {
9189             /* Relay offer to ICS */
9190             SendToICS(ics_prefix);
9191             SendToICS("draw\n");
9192         }
9193 #endif
9194         cps->offeredDraw = 2; /* valid until this engine moves twice */
9195         if (gameMode == TwoMachinesPlay) {
9196             if (cps->other->offeredDraw) {
9197                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9198             /* [HGM] in two-machine mode we delay relaying draw offer      */
9199             /* until after we also have move, to see if it is really claim */
9200             }
9201         } else if (gameMode == MachinePlaysWhite ||
9202                    gameMode == MachinePlaysBlack) {
9203           if (userOfferedDraw) {
9204             DisplayInformation(_("Machine accepts your draw offer"));
9205             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9206           } else {
9207             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9208           }
9209         }
9210     }
9211
9212
9213     /*
9214      * Look for thinking output
9215      */
9216     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9217           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9218                                 ) {
9219         int plylev, mvleft, mvtot, curscore, time;
9220         char mvname[MOVE_LEN];
9221         u64 nodes; // [DM]
9222         char plyext;
9223         int ignore = FALSE;
9224         int prefixHint = FALSE;
9225         mvname[0] = NULLCHAR;
9226
9227         switch (gameMode) {
9228           case MachinePlaysBlack:
9229           case IcsPlayingBlack:
9230             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9231             break;
9232           case MachinePlaysWhite:
9233           case IcsPlayingWhite:
9234             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9235             break;
9236           case AnalyzeMode:
9237           case AnalyzeFile:
9238             break;
9239           case IcsObserving: /* [DM] icsEngineAnalyze */
9240             if (!appData.icsEngineAnalyze) ignore = TRUE;
9241             break;
9242           case TwoMachinesPlay:
9243             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9244                 ignore = TRUE;
9245             }
9246             break;
9247           default:
9248             ignore = TRUE;
9249             break;
9250         }
9251
9252         if (!ignore) {
9253             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9254             buf1[0] = NULLCHAR;
9255             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9256                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9257
9258                 if (plyext != ' ' && plyext != '\t') {
9259                     time *= 100;
9260                 }
9261
9262                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9263                 if( cps->scoreIsAbsolute &&
9264                     ( gameMode == MachinePlaysBlack ||
9265                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9266                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9267                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9268                      !WhiteOnMove(currentMove)
9269                     ) )
9270                 {
9271                     curscore = -curscore;
9272                 }
9273
9274                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9275
9276                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9277                         char buf[MSG_SIZ];
9278                         FILE *f;
9279                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9280                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9281                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9282                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9283                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9284                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9285                                 fclose(f);
9286                         }
9287                         else
9288                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9289                           DisplayError(_("failed writing PV"), 0);
9290                 }
9291
9292                 tempStats.depth = plylev;
9293                 tempStats.nodes = nodes;
9294                 tempStats.time = time;
9295                 tempStats.score = curscore;
9296                 tempStats.got_only_move = 0;
9297
9298                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9299                         int ticklen;
9300
9301                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9302                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9303                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9304                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9305                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9306                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9307                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9308                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9309                 }
9310
9311                 /* Buffer overflow protection */
9312                 if (pv[0] != NULLCHAR) {
9313                     if (strlen(pv) >= sizeof(tempStats.movelist)
9314                         && appData.debugMode) {
9315                         fprintf(debugFP,
9316                                 "PV is too long; using the first %u bytes.\n",
9317                                 (unsigned) sizeof(tempStats.movelist) - 1);
9318                     }
9319
9320                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9321                 } else {
9322                     sprintf(tempStats.movelist, " no PV\n");
9323                 }
9324
9325                 if (tempStats.seen_stat) {
9326                     tempStats.ok_to_send = 1;
9327                 }
9328
9329                 if (strchr(tempStats.movelist, '(') != NULL) {
9330                     tempStats.line_is_book = 1;
9331                     tempStats.nr_moves = 0;
9332                     tempStats.moves_left = 0;
9333                 } else {
9334                     tempStats.line_is_book = 0;
9335                 }
9336
9337                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9338                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9339
9340                 SendProgramStatsToFrontend( cps, &tempStats );
9341
9342                 /*
9343                     [AS] Protect the thinkOutput buffer from overflow... this
9344                     is only useful if buf1 hasn't overflowed first!
9345                 */
9346                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9347                          plylev,
9348                          (gameMode == TwoMachinesPlay ?
9349                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9350                          ((double) curscore) / 100.0,
9351                          prefixHint ? lastHint : "",
9352                          prefixHint ? " " : "" );
9353
9354                 if( buf1[0] != NULLCHAR ) {
9355                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9356
9357                     if( strlen(pv) > max_len ) {
9358                         if( appData.debugMode) {
9359                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9360                         }
9361                         pv[max_len+1] = '\0';
9362                     }
9363
9364                     strcat( thinkOutput, pv);
9365                 }
9366
9367                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9368                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9369                     DisplayMove(currentMove - 1);
9370                 }
9371                 return;
9372
9373             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9374                 /* crafty (9.25+) says "(only move) <move>"
9375                  * if there is only 1 legal move
9376                  */
9377                 sscanf(p, "(only move) %s", buf1);
9378                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9379                 sprintf(programStats.movelist, "%s (only move)", buf1);
9380                 programStats.depth = 1;
9381                 programStats.nr_moves = 1;
9382                 programStats.moves_left = 1;
9383                 programStats.nodes = 1;
9384                 programStats.time = 1;
9385                 programStats.got_only_move = 1;
9386
9387                 /* Not really, but we also use this member to
9388                    mean "line isn't going to change" (Crafty
9389                    isn't searching, so stats won't change) */
9390                 programStats.line_is_book = 1;
9391
9392                 SendProgramStatsToFrontend( cps, &programStats );
9393
9394                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9395                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9396                     DisplayMove(currentMove - 1);
9397                 }
9398                 return;
9399             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9400                               &time, &nodes, &plylev, &mvleft,
9401                               &mvtot, mvname) >= 5) {
9402                 /* The stat01: line is from Crafty (9.29+) in response
9403                    to the "." command */
9404                 programStats.seen_stat = 1;
9405                 cps->maybeThinking = TRUE;
9406
9407                 if (programStats.got_only_move || !appData.periodicUpdates)
9408                   return;
9409
9410                 programStats.depth = plylev;
9411                 programStats.time = time;
9412                 programStats.nodes = nodes;
9413                 programStats.moves_left = mvleft;
9414                 programStats.nr_moves = mvtot;
9415                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9416                 programStats.ok_to_send = 1;
9417                 programStats.movelist[0] = '\0';
9418
9419                 SendProgramStatsToFrontend( cps, &programStats );
9420
9421                 return;
9422
9423             } else if (strncmp(message,"++",2) == 0) {
9424                 /* Crafty 9.29+ outputs this */
9425                 programStats.got_fail = 2;
9426                 return;
9427
9428             } else if (strncmp(message,"--",2) == 0) {
9429                 /* Crafty 9.29+ outputs this */
9430                 programStats.got_fail = 1;
9431                 return;
9432
9433             } else if (thinkOutput[0] != NULLCHAR &&
9434                        strncmp(message, "    ", 4) == 0) {
9435                 unsigned message_len;
9436
9437                 p = message;
9438                 while (*p && *p == ' ') p++;
9439
9440                 message_len = strlen( p );
9441
9442                 /* [AS] Avoid buffer overflow */
9443                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9444                     strcat(thinkOutput, " ");
9445                     strcat(thinkOutput, p);
9446                 }
9447
9448                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9449                     strcat(programStats.movelist, " ");
9450                     strcat(programStats.movelist, p);
9451                 }
9452
9453                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9454                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9455                     DisplayMove(currentMove - 1);
9456                 }
9457                 return;
9458             }
9459         }
9460         else {
9461             buf1[0] = NULLCHAR;
9462
9463             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9464                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9465             {
9466                 ChessProgramStats cpstats;
9467
9468                 if (plyext != ' ' && plyext != '\t') {
9469                     time *= 100;
9470                 }
9471
9472                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9473                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9474                     curscore = -curscore;
9475                 }
9476
9477                 cpstats.depth = plylev;
9478                 cpstats.nodes = nodes;
9479                 cpstats.time = time;
9480                 cpstats.score = curscore;
9481                 cpstats.got_only_move = 0;
9482                 cpstats.movelist[0] = '\0';
9483
9484                 if (buf1[0] != NULLCHAR) {
9485                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9486                 }
9487
9488                 cpstats.ok_to_send = 0;
9489                 cpstats.line_is_book = 0;
9490                 cpstats.nr_moves = 0;
9491                 cpstats.moves_left = 0;
9492
9493                 SendProgramStatsToFrontend( cps, &cpstats );
9494             }
9495         }
9496     }
9497 }
9498
9499
9500 /* Parse a game score from the character string "game", and
9501    record it as the history of the current game.  The game
9502    score is NOT assumed to start from the standard position.
9503    The display is not updated in any way.
9504    */
9505 void
9506 ParseGameHistory (char *game)
9507 {
9508     ChessMove moveType;
9509     int fromX, fromY, toX, toY, boardIndex;
9510     char promoChar;
9511     char *p, *q;
9512     char buf[MSG_SIZ];
9513
9514     if (appData.debugMode)
9515       fprintf(debugFP, "Parsing game history: %s\n", game);
9516
9517     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9518     gameInfo.site = StrSave(appData.icsHost);
9519     gameInfo.date = PGNDate();
9520     gameInfo.round = StrSave("-");
9521
9522     /* Parse out names of players */
9523     while (*game == ' ') game++;
9524     p = buf;
9525     while (*game != ' ') *p++ = *game++;
9526     *p = NULLCHAR;
9527     gameInfo.white = StrSave(buf);
9528     while (*game == ' ') game++;
9529     p = buf;
9530     while (*game != ' ' && *game != '\n') *p++ = *game++;
9531     *p = NULLCHAR;
9532     gameInfo.black = StrSave(buf);
9533
9534     /* Parse moves */
9535     boardIndex = blackPlaysFirst ? 1 : 0;
9536     yynewstr(game);
9537     for (;;) {
9538         yyboardindex = boardIndex;
9539         moveType = (ChessMove) Myylex();
9540         switch (moveType) {
9541           case IllegalMove:             /* maybe suicide chess, etc. */
9542   if (appData.debugMode) {
9543     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9544     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9545     setbuf(debugFP, NULL);
9546   }
9547           case WhitePromotion:
9548           case BlackPromotion:
9549           case WhiteNonPromotion:
9550           case BlackNonPromotion:
9551           case NormalMove:
9552           case FirstLeg:
9553           case WhiteCapturesEnPassant:
9554           case BlackCapturesEnPassant:
9555           case WhiteKingSideCastle:
9556           case WhiteQueenSideCastle:
9557           case BlackKingSideCastle:
9558           case BlackQueenSideCastle:
9559           case WhiteKingSideCastleWild:
9560           case WhiteQueenSideCastleWild:
9561           case BlackKingSideCastleWild:
9562           case BlackQueenSideCastleWild:
9563           /* PUSH Fabien */
9564           case WhiteHSideCastleFR:
9565           case WhiteASideCastleFR:
9566           case BlackHSideCastleFR:
9567           case BlackASideCastleFR:
9568           /* POP Fabien */
9569             fromX = currentMoveString[0] - AAA;
9570             fromY = currentMoveString[1] - ONE;
9571             toX = currentMoveString[2] - AAA;
9572             toY = currentMoveString[3] - ONE;
9573             promoChar = currentMoveString[4];
9574             break;
9575           case WhiteDrop:
9576           case BlackDrop:
9577             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9578             fromX = moveType == WhiteDrop ?
9579               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9580             (int) CharToPiece(ToLower(currentMoveString[0]));
9581             fromY = DROP_RANK;
9582             toX = currentMoveString[2] - AAA;
9583             toY = currentMoveString[3] - ONE;
9584             promoChar = NULLCHAR;
9585             break;
9586           case AmbiguousMove:
9587             /* bug? */
9588             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9589   if (appData.debugMode) {
9590     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9591     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9592     setbuf(debugFP, NULL);
9593   }
9594             DisplayError(buf, 0);
9595             return;
9596           case ImpossibleMove:
9597             /* bug? */
9598             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9599   if (appData.debugMode) {
9600     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9601     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9602     setbuf(debugFP, NULL);
9603   }
9604             DisplayError(buf, 0);
9605             return;
9606           case EndOfFile:
9607             if (boardIndex < backwardMostMove) {
9608                 /* Oops, gap.  How did that happen? */
9609                 DisplayError(_("Gap in move list"), 0);
9610                 return;
9611             }
9612             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9613             if (boardIndex > forwardMostMove) {
9614                 forwardMostMove = boardIndex;
9615             }
9616             return;
9617           case ElapsedTime:
9618             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9619                 strcat(parseList[boardIndex-1], " ");
9620                 strcat(parseList[boardIndex-1], yy_text);
9621             }
9622             continue;
9623           case Comment:
9624           case PGNTag:
9625           case NAG:
9626           default:
9627             /* ignore */
9628             continue;
9629           case WhiteWins:
9630           case BlackWins:
9631           case GameIsDrawn:
9632           case GameUnfinished:
9633             if (gameMode == IcsExamining) {
9634                 if (boardIndex < backwardMostMove) {
9635                     /* Oops, gap.  How did that happen? */
9636                     return;
9637                 }
9638                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9639                 return;
9640             }
9641             gameInfo.result = moveType;
9642             p = strchr(yy_text, '{');
9643             if (p == NULL) p = strchr(yy_text, '(');
9644             if (p == NULL) {
9645                 p = yy_text;
9646                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9647             } else {
9648                 q = strchr(p, *p == '{' ? '}' : ')');
9649                 if (q != NULL) *q = NULLCHAR;
9650                 p++;
9651             }
9652             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9653             gameInfo.resultDetails = StrSave(p);
9654             continue;
9655         }
9656         if (boardIndex >= forwardMostMove &&
9657             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9658             backwardMostMove = blackPlaysFirst ? 1 : 0;
9659             return;
9660         }
9661         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9662                                  fromY, fromX, toY, toX, promoChar,
9663                                  parseList[boardIndex]);
9664         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9665         /* currentMoveString is set as a side-effect of yylex */
9666         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9667         strcat(moveList[boardIndex], "\n");
9668         boardIndex++;
9669         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9670         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9671           case MT_NONE:
9672           case MT_STALEMATE:
9673           default:
9674             break;
9675           case MT_CHECK:
9676             if(gameInfo.variant != VariantShogi)
9677                 strcat(parseList[boardIndex - 1], "+");
9678             break;
9679           case MT_CHECKMATE:
9680           case MT_STAINMATE:
9681             strcat(parseList[boardIndex - 1], "#");
9682             break;
9683         }
9684     }
9685 }
9686
9687
9688 /* Apply a move to the given board  */
9689 void
9690 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9691 {
9692   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9693   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9694
9695     /* [HGM] compute & store e.p. status and castling rights for new position */
9696     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9697
9698       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9699       oldEP = (signed char)board[EP_STATUS];
9700       board[EP_STATUS] = EP_NONE;
9701
9702   if (fromY == DROP_RANK) {
9703         /* must be first */
9704         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9705             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9706             return;
9707         }
9708         piece = board[toY][toX] = (ChessSquare) fromX;
9709   } else {
9710       ChessSquare victim;
9711       int i;
9712
9713       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9714            victim = board[killY][killX],
9715            board[killY][killX] = EmptySquare,
9716            board[EP_STATUS] = EP_CAPTURE;
9717
9718       if( board[toY][toX] != EmptySquare ) {
9719            board[EP_STATUS] = EP_CAPTURE;
9720            if( (fromX != toX || fromY != toY) && // not igui!
9721                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9722                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9723                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9724            }
9725       }
9726
9727       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9728            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9729                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9730       } else
9731       if( board[fromY][fromX] == WhitePawn ) {
9732            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9733                board[EP_STATUS] = EP_PAWN_MOVE;
9734            if( toY-fromY==2) {
9735                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9736                         gameInfo.variant != VariantBerolina || toX < fromX)
9737                       board[EP_STATUS] = toX | berolina;
9738                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9739                         gameInfo.variant != VariantBerolina || toX > fromX)
9740                       board[EP_STATUS] = toX;
9741            }
9742       } else
9743       if( board[fromY][fromX] == BlackPawn ) {
9744            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9745                board[EP_STATUS] = EP_PAWN_MOVE;
9746            if( toY-fromY== -2) {
9747                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9748                         gameInfo.variant != VariantBerolina || toX < fromX)
9749                       board[EP_STATUS] = toX | berolina;
9750                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9751                         gameInfo.variant != VariantBerolina || toX > fromX)
9752                       board[EP_STATUS] = toX;
9753            }
9754        }
9755
9756        for(i=0; i<nrCastlingRights; i++) {
9757            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9758               board[CASTLING][i] == toX   && castlingRank[i] == toY
9759              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9760        }
9761
9762        if(gameInfo.variant == VariantSChess) { // update virginity
9763            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9764            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9765            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9766            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9767        }
9768
9769      if (fromX == toX && fromY == toY) return;
9770
9771      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9772      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9773      if(gameInfo.variant == VariantKnightmate)
9774          king += (int) WhiteUnicorn - (int) WhiteKing;
9775
9776     /* Code added by Tord: */
9777     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9778     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9779         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9780       board[fromY][fromX] = EmptySquare;
9781       board[toY][toX] = EmptySquare;
9782       if((toX > fromX) != (piece == WhiteRook)) {
9783         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9784       } else {
9785         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9786       }
9787     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9788                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9789       board[fromY][fromX] = EmptySquare;
9790       board[toY][toX] = EmptySquare;
9791       if((toX > fromX) != (piece == BlackRook)) {
9792         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9793       } else {
9794         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9795       }
9796     /* End of code added by Tord */
9797
9798     } else if (board[fromY][fromX] == king
9799         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9800         && toY == fromY && toX > fromX+1) {
9801         board[fromY][fromX] = EmptySquare;
9802         board[toY][toX] = king;
9803         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9804         board[fromY][BOARD_RGHT-1] = EmptySquare;
9805     } else if (board[fromY][fromX] == king
9806         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9807                && toY == fromY && toX < fromX-1) {
9808         board[fromY][fromX] = EmptySquare;
9809         board[toY][toX] = king;
9810         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9811         board[fromY][BOARD_LEFT] = EmptySquare;
9812     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9813                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9814                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9815                ) {
9816         /* white pawn promotion */
9817         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9818         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9819             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9820         board[fromY][fromX] = EmptySquare;
9821     } else if ((fromY >= BOARD_HEIGHT>>1)
9822                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9823                && (toX != fromX)
9824                && gameInfo.variant != VariantXiangqi
9825                && gameInfo.variant != VariantBerolina
9826                && (board[fromY][fromX] == WhitePawn)
9827                && (board[toY][toX] == EmptySquare)) {
9828         board[fromY][fromX] = EmptySquare;
9829         board[toY][toX] = WhitePawn;
9830         captured = board[toY - 1][toX];
9831         board[toY - 1][toX] = EmptySquare;
9832     } else if ((fromY == BOARD_HEIGHT-4)
9833                && (toX == fromX)
9834                && gameInfo.variant == VariantBerolina
9835                && (board[fromY][fromX] == WhitePawn)
9836                && (board[toY][toX] == EmptySquare)) {
9837         board[fromY][fromX] = EmptySquare;
9838         board[toY][toX] = WhitePawn;
9839         if(oldEP & EP_BEROLIN_A) {
9840                 captured = board[fromY][fromX-1];
9841                 board[fromY][fromX-1] = EmptySquare;
9842         }else{  captured = board[fromY][fromX+1];
9843                 board[fromY][fromX+1] = EmptySquare;
9844         }
9845     } else if (board[fromY][fromX] == king
9846         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9847                && toY == fromY && toX > fromX+1) {
9848         board[fromY][fromX] = EmptySquare;
9849         board[toY][toX] = king;
9850         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9851         board[fromY][BOARD_RGHT-1] = EmptySquare;
9852     } else if (board[fromY][fromX] == king
9853         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9854                && toY == fromY && toX < fromX-1) {
9855         board[fromY][fromX] = EmptySquare;
9856         board[toY][toX] = king;
9857         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9858         board[fromY][BOARD_LEFT] = EmptySquare;
9859     } else if (fromY == 7 && fromX == 3
9860                && board[fromY][fromX] == BlackKing
9861                && toY == 7 && toX == 5) {
9862         board[fromY][fromX] = EmptySquare;
9863         board[toY][toX] = BlackKing;
9864         board[fromY][7] = EmptySquare;
9865         board[toY][4] = BlackRook;
9866     } else if (fromY == 7 && fromX == 3
9867                && board[fromY][fromX] == BlackKing
9868                && toY == 7 && toX == 1) {
9869         board[fromY][fromX] = EmptySquare;
9870         board[toY][toX] = BlackKing;
9871         board[fromY][0] = EmptySquare;
9872         board[toY][2] = BlackRook;
9873     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9874                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9875                && toY < promoRank && promoChar
9876                ) {
9877         /* black pawn promotion */
9878         board[toY][toX] = CharToPiece(ToLower(promoChar));
9879         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9880             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9881         board[fromY][fromX] = EmptySquare;
9882     } else if ((fromY < BOARD_HEIGHT>>1)
9883                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9884                && (toX != fromX)
9885                && gameInfo.variant != VariantXiangqi
9886                && gameInfo.variant != VariantBerolina
9887                && (board[fromY][fromX] == BlackPawn)
9888                && (board[toY][toX] == EmptySquare)) {
9889         board[fromY][fromX] = EmptySquare;
9890         board[toY][toX] = BlackPawn;
9891         captured = board[toY + 1][toX];
9892         board[toY + 1][toX] = EmptySquare;
9893     } else if ((fromY == 3)
9894                && (toX == fromX)
9895                && gameInfo.variant == VariantBerolina
9896                && (board[fromY][fromX] == BlackPawn)
9897                && (board[toY][toX] == EmptySquare)) {
9898         board[fromY][fromX] = EmptySquare;
9899         board[toY][toX] = BlackPawn;
9900         if(oldEP & EP_BEROLIN_A) {
9901                 captured = board[fromY][fromX-1];
9902                 board[fromY][fromX-1] = EmptySquare;
9903         }else{  captured = board[fromY][fromX+1];
9904                 board[fromY][fromX+1] = EmptySquare;
9905         }
9906     } else {
9907         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9908         board[fromY][fromX] = EmptySquare;
9909         board[toY][toX] = piece;
9910     }
9911   }
9912
9913     if (gameInfo.holdingsWidth != 0) {
9914
9915       /* !!A lot more code needs to be written to support holdings  */
9916       /* [HGM] OK, so I have written it. Holdings are stored in the */
9917       /* penultimate board files, so they are automaticlly stored   */
9918       /* in the game history.                                       */
9919       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9920                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9921         /* Delete from holdings, by decreasing count */
9922         /* and erasing image if necessary            */
9923         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9924         if(p < (int) BlackPawn) { /* white drop */
9925              p -= (int)WhitePawn;
9926                  p = PieceToNumber((ChessSquare)p);
9927              if(p >= gameInfo.holdingsSize) p = 0;
9928              if(--board[p][BOARD_WIDTH-2] <= 0)
9929                   board[p][BOARD_WIDTH-1] = EmptySquare;
9930              if((int)board[p][BOARD_WIDTH-2] < 0)
9931                         board[p][BOARD_WIDTH-2] = 0;
9932         } else {                  /* black drop */
9933              p -= (int)BlackPawn;
9934                  p = PieceToNumber((ChessSquare)p);
9935              if(p >= gameInfo.holdingsSize) p = 0;
9936              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9937                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9938              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9939                         board[BOARD_HEIGHT-1-p][1] = 0;
9940         }
9941       }
9942       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9943           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9944         /* [HGM] holdings: Add to holdings, if holdings exist */
9945         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9946                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9947                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9948         }
9949         p = (int) captured;
9950         if (p >= (int) BlackPawn) {
9951           p -= (int)BlackPawn;
9952           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9953                   /* in Shogi restore piece to its original  first */
9954                   captured = (ChessSquare) (DEMOTED captured);
9955                   p = DEMOTED p;
9956           }
9957           p = PieceToNumber((ChessSquare)p);
9958           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9959           board[p][BOARD_WIDTH-2]++;
9960           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9961         } else {
9962           p -= (int)WhitePawn;
9963           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9964                   captured = (ChessSquare) (DEMOTED captured);
9965                   p = DEMOTED p;
9966           }
9967           p = PieceToNumber((ChessSquare)p);
9968           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9969           board[BOARD_HEIGHT-1-p][1]++;
9970           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9971         }
9972       }
9973     } else if (gameInfo.variant == VariantAtomic) {
9974       if (captured != EmptySquare) {
9975         int y, x;
9976         for (y = toY-1; y <= toY+1; y++) {
9977           for (x = toX-1; x <= toX+1; x++) {
9978             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9979                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9980               board[y][x] = EmptySquare;
9981             }
9982           }
9983         }
9984         board[toY][toX] = EmptySquare;
9985       }
9986     }
9987     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9988         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9989     } else
9990     if(promoChar == '+') {
9991         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9992         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9993         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
9994           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
9995     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9996         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9997         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9998            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9999         board[toY][toX] = newPiece;
10000     }
10001     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10002                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10003         // [HGM] superchess: take promotion piece out of holdings
10004         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10005         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10006             if(!--board[k][BOARD_WIDTH-2])
10007                 board[k][BOARD_WIDTH-1] = EmptySquare;
10008         } else {
10009             if(!--board[BOARD_HEIGHT-1-k][1])
10010                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10011         }
10012     }
10013 }
10014
10015 /* Updates forwardMostMove */
10016 void
10017 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10018 {
10019     int x = toX, y = toY;
10020     char *s = parseList[forwardMostMove];
10021     ChessSquare p = boards[forwardMostMove][toY][toX];
10022 //    forwardMostMove++; // [HGM] bare: moved downstream
10023
10024     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10025     (void) CoordsToAlgebraic(boards[forwardMostMove],
10026                              PosFlags(forwardMostMove),
10027                              fromY, fromX, y, x, promoChar,
10028                              s);
10029     if(killX >= 0 && killY >= 0)
10030         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10031
10032     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10033         int timeLeft; static int lastLoadFlag=0; int king, piece;
10034         piece = boards[forwardMostMove][fromY][fromX];
10035         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10036         if(gameInfo.variant == VariantKnightmate)
10037             king += (int) WhiteUnicorn - (int) WhiteKing;
10038         if(forwardMostMove == 0) {
10039             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10040                 fprintf(serverMoves, "%s;", UserName());
10041             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10042                 fprintf(serverMoves, "%s;", second.tidy);
10043             fprintf(serverMoves, "%s;", first.tidy);
10044             if(gameMode == MachinePlaysWhite)
10045                 fprintf(serverMoves, "%s;", UserName());
10046             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10047                 fprintf(serverMoves, "%s;", second.tidy);
10048         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10049         lastLoadFlag = loadFlag;
10050         // print base move
10051         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10052         // print castling suffix
10053         if( toY == fromY && piece == king ) {
10054             if(toX-fromX > 1)
10055                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10056             if(fromX-toX >1)
10057                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10058         }
10059         // e.p. suffix
10060         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10061              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10062              boards[forwardMostMove][toY][toX] == EmptySquare
10063              && fromX != toX && fromY != toY)
10064                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10065         // promotion suffix
10066         if(promoChar != NULLCHAR) {
10067             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10068                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10069                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10070             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10071         }
10072         if(!loadFlag) {
10073                 char buf[MOVE_LEN*2], *p; int len;
10074             fprintf(serverMoves, "/%d/%d",
10075                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10076             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10077             else                      timeLeft = blackTimeRemaining/1000;
10078             fprintf(serverMoves, "/%d", timeLeft);
10079                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10080                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10081                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10082                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10083             fprintf(serverMoves, "/%s", buf);
10084         }
10085         fflush(serverMoves);
10086     }
10087
10088     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10089         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10090       return;
10091     }
10092     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10093     if (commentList[forwardMostMove+1] != NULL) {
10094         free(commentList[forwardMostMove+1]);
10095         commentList[forwardMostMove+1] = NULL;
10096     }
10097     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10098     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10099     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10100     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10101     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10102     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10103     adjustedClock = FALSE;
10104     gameInfo.result = GameUnfinished;
10105     if (gameInfo.resultDetails != NULL) {
10106         free(gameInfo.resultDetails);
10107         gameInfo.resultDetails = NULL;
10108     }
10109     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10110                               moveList[forwardMostMove - 1]);
10111     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10112       case MT_NONE:
10113       case MT_STALEMATE:
10114       default:
10115         break;
10116       case MT_CHECK:
10117         if(gameInfo.variant != VariantShogi)
10118             strcat(parseList[forwardMostMove - 1], "+");
10119         break;
10120       case MT_CHECKMATE:
10121       case MT_STAINMATE:
10122         strcat(parseList[forwardMostMove - 1], "#");
10123         break;
10124     }
10125 }
10126
10127 /* Updates currentMove if not pausing */
10128 void
10129 ShowMove (int fromX, int fromY, int toX, int toY)
10130 {
10131     int instant = (gameMode == PlayFromGameFile) ?
10132         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10133     if(appData.noGUI) return;
10134     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10135         if (!instant) {
10136             if (forwardMostMove == currentMove + 1) {
10137                 AnimateMove(boards[forwardMostMove - 1],
10138                             fromX, fromY, toX, toY);
10139             }
10140         }
10141         currentMove = forwardMostMove;
10142     }
10143
10144     killX = killY = -1; // [HGM] lion: used up
10145
10146     if (instant) return;
10147
10148     DisplayMove(currentMove - 1);
10149     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10150             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10151                 SetHighlights(fromX, fromY, toX, toY);
10152             }
10153     }
10154     DrawPosition(FALSE, boards[currentMove]);
10155     DisplayBothClocks();
10156     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10157 }
10158
10159 void
10160 SendEgtPath (ChessProgramState *cps)
10161 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10162         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10163
10164         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10165
10166         while(*p) {
10167             char c, *q = name+1, *r, *s;
10168
10169             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10170             while(*p && *p != ',') *q++ = *p++;
10171             *q++ = ':'; *q = 0;
10172             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10173                 strcmp(name, ",nalimov:") == 0 ) {
10174                 // take nalimov path from the menu-changeable option first, if it is defined
10175               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10176                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10177             } else
10178             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10179                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10180                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10181                 s = r = StrStr(s, ":") + 1; // beginning of path info
10182                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10183                 c = *r; *r = 0;             // temporarily null-terminate path info
10184                     *--q = 0;               // strip of trailig ':' from name
10185                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10186                 *r = c;
10187                 SendToProgram(buf,cps);     // send egtbpath command for this format
10188             }
10189             if(*p == ',') p++; // read away comma to position for next format name
10190         }
10191 }
10192
10193 static int
10194 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10195 {
10196       int width = 8, height = 8, holdings = 0;             // most common sizes
10197       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10198       // correct the deviations default for each variant
10199       if( v == VariantXiangqi ) width = 9,  height = 10;
10200       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10201       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10202       if( v == VariantCapablanca || v == VariantCapaRandom ||
10203           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10204                                 width = 10;
10205       if( v == VariantCourier ) width = 12;
10206       if( v == VariantSuper )                            holdings = 8;
10207       if( v == VariantGreat )   width = 10,              holdings = 8;
10208       if( v == VariantSChess )                           holdings = 7;
10209       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10210       if( v == VariantChuChess) width = 10, height = 10;
10211       if( v == VariantChu )     width = 12, height = 12;
10212       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10213              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10214              holdingsSize >= 0 && holdingsSize != holdings;
10215 }
10216
10217 char variantError[MSG_SIZ];
10218
10219 char *
10220 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10221 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10222       char *p, *variant = VariantName(v);
10223       static char b[MSG_SIZ];
10224       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10225            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10226                                                holdingsSize, variant); // cook up sized variant name
10227            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10228            if(StrStr(list, b) == NULL) {
10229                // specific sized variant not known, check if general sizing allowed
10230                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10231                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10232                             boardWidth, boardHeight, holdingsSize, engine);
10233                    return NULL;
10234                }
10235                /* [HGM] here we really should compare with the maximum supported board size */
10236            }
10237       } else snprintf(b, MSG_SIZ,"%s", variant);
10238       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10239       p = StrStr(list, b);
10240       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10241       if(p == NULL) {
10242           // occurs not at all in list, or only as sub-string
10243           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10244           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10245               int l = strlen(variantError);
10246               char *q;
10247               while(p != list && p[-1] != ',') p--;
10248               q = strchr(p, ',');
10249               if(q) *q = NULLCHAR;
10250               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10251               if(q) *q= ',';
10252           }
10253           return NULL;
10254       }
10255       return b;
10256 }
10257
10258 void
10259 InitChessProgram (ChessProgramState *cps, int setup)
10260 /* setup needed to setup FRC opening position */
10261 {
10262     char buf[MSG_SIZ], *b;
10263     if (appData.noChessProgram) return;
10264     hintRequested = FALSE;
10265     bookRequested = FALSE;
10266
10267     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10268     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10269     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10270     if(cps->memSize) { /* [HGM] memory */
10271       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10272         SendToProgram(buf, cps);
10273     }
10274     SendEgtPath(cps); /* [HGM] EGT */
10275     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10276       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10277         SendToProgram(buf, cps);
10278     }
10279
10280     SendToProgram(cps->initString, cps);
10281     if (gameInfo.variant != VariantNormal &&
10282         gameInfo.variant != VariantLoadable
10283         /* [HGM] also send variant if board size non-standard */
10284         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10285
10286       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10287                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10288       if (b == NULL) {
10289         DisplayFatalError(variantError, 0, 1);
10290         return;
10291       }
10292
10293       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10294       SendToProgram(buf, cps);
10295     }
10296     currentlyInitializedVariant = gameInfo.variant;
10297
10298     /* [HGM] send opening position in FRC to first engine */
10299     if(setup) {
10300           SendToProgram("force\n", cps);
10301           SendBoard(cps, 0);
10302           /* engine is now in force mode! Set flag to wake it up after first move. */
10303           setboardSpoiledMachineBlack = 1;
10304     }
10305
10306     if (cps->sendICS) {
10307       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10308       SendToProgram(buf, cps);
10309     }
10310     cps->maybeThinking = FALSE;
10311     cps->offeredDraw = 0;
10312     if (!appData.icsActive) {
10313         SendTimeControl(cps, movesPerSession, timeControl,
10314                         timeIncrement, appData.searchDepth,
10315                         searchTime);
10316     }
10317     if (appData.showThinking
10318         // [HGM] thinking: four options require thinking output to be sent
10319         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10320                                 ) {
10321         SendToProgram("post\n", cps);
10322     }
10323     SendToProgram("hard\n", cps);
10324     if (!appData.ponderNextMove) {
10325         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10326            it without being sure what state we are in first.  "hard"
10327            is not a toggle, so that one is OK.
10328          */
10329         SendToProgram("easy\n", cps);
10330     }
10331     if (cps->usePing) {
10332       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10333       SendToProgram(buf, cps);
10334     }
10335     cps->initDone = TRUE;
10336     ClearEngineOutputPane(cps == &second);
10337 }
10338
10339
10340 void
10341 ResendOptions (ChessProgramState *cps)
10342 { // send the stored value of the options
10343   int i;
10344   char buf[MSG_SIZ];
10345   Option *opt = cps->option;
10346   for(i=0; i<cps->nrOptions; i++, opt++) {
10347       switch(opt->type) {
10348         case Spin:
10349         case Slider:
10350         case CheckBox:
10351             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10352           break;
10353         case ComboBox:
10354           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10355           break;
10356         default:
10357             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10358           break;
10359         case Button:
10360         case SaveButton:
10361           continue;
10362       }
10363       SendToProgram(buf, cps);
10364   }
10365 }
10366
10367 void
10368 StartChessProgram (ChessProgramState *cps)
10369 {
10370     char buf[MSG_SIZ];
10371     int err;
10372
10373     if (appData.noChessProgram) return;
10374     cps->initDone = FALSE;
10375
10376     if (strcmp(cps->host, "localhost") == 0) {
10377         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10378     } else if (*appData.remoteShell == NULLCHAR) {
10379         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10380     } else {
10381         if (*appData.remoteUser == NULLCHAR) {
10382           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10383                     cps->program);
10384         } else {
10385           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10386                     cps->host, appData.remoteUser, cps->program);
10387         }
10388         err = StartChildProcess(buf, "", &cps->pr);
10389     }
10390
10391     if (err != 0) {
10392       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10393         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10394         if(cps != &first) return;
10395         appData.noChessProgram = TRUE;
10396         ThawUI();
10397         SetNCPMode();
10398 //      DisplayFatalError(buf, err, 1);
10399 //      cps->pr = NoProc;
10400 //      cps->isr = NULL;
10401         return;
10402     }
10403
10404     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10405     if (cps->protocolVersion > 1) {
10406       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10407       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10408         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10409         cps->comboCnt = 0;  //                and values of combo boxes
10410       }
10411       SendToProgram(buf, cps);
10412       if(cps->reload) ResendOptions(cps);
10413     } else {
10414       SendToProgram("xboard\n", cps);
10415     }
10416 }
10417
10418 void
10419 TwoMachinesEventIfReady P((void))
10420 {
10421   static int curMess = 0;
10422   if (first.lastPing != first.lastPong) {
10423     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10424     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10425     return;
10426   }
10427   if (second.lastPing != second.lastPong) {
10428     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10429     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10430     return;
10431   }
10432   DisplayMessage("", ""); curMess = 0;
10433   TwoMachinesEvent();
10434 }
10435
10436 char *
10437 MakeName (char *template)
10438 {
10439     time_t clock;
10440     struct tm *tm;
10441     static char buf[MSG_SIZ];
10442     char *p = buf;
10443     int i;
10444
10445     clock = time((time_t *)NULL);
10446     tm = localtime(&clock);
10447
10448     while(*p++ = *template++) if(p[-1] == '%') {
10449         switch(*template++) {
10450           case 0:   *p = 0; return buf;
10451           case 'Y': i = tm->tm_year+1900; break;
10452           case 'y': i = tm->tm_year-100; break;
10453           case 'M': i = tm->tm_mon+1; break;
10454           case 'd': i = tm->tm_mday; break;
10455           case 'h': i = tm->tm_hour; break;
10456           case 'm': i = tm->tm_min; break;
10457           case 's': i = tm->tm_sec; break;
10458           default:  i = 0;
10459         }
10460         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10461     }
10462     return buf;
10463 }
10464
10465 int
10466 CountPlayers (char *p)
10467 {
10468     int n = 0;
10469     while(p = strchr(p, '\n')) p++, n++; // count participants
10470     return n;
10471 }
10472
10473 FILE *
10474 WriteTourneyFile (char *results, FILE *f)
10475 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10476     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10477     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10478         // create a file with tournament description
10479         fprintf(f, "-participants {%s}\n", appData.participants);
10480         fprintf(f, "-seedBase %d\n", appData.seedBase);
10481         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10482         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10483         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10484         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10485         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10486         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10487         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10488         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10489         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10490         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10491         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10492         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10493         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10494         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10495         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10496         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10497         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10498         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10499         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10500         fprintf(f, "-smpCores %d\n", appData.smpCores);
10501         if(searchTime > 0)
10502                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10503         else {
10504                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10505                 fprintf(f, "-tc %s\n", appData.timeControl);
10506                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10507         }
10508         fprintf(f, "-results \"%s\"\n", results);
10509     }
10510     return f;
10511 }
10512
10513 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10514
10515 void
10516 Substitute (char *participants, int expunge)
10517 {
10518     int i, changed, changes=0, nPlayers=0;
10519     char *p, *q, *r, buf[MSG_SIZ];
10520     if(participants == NULL) return;
10521     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10522     r = p = participants; q = appData.participants;
10523     while(*p && *p == *q) {
10524         if(*p == '\n') r = p+1, nPlayers++;
10525         p++; q++;
10526     }
10527     if(*p) { // difference
10528         while(*p && *p++ != '\n');
10529         while(*q && *q++ != '\n');
10530       changed = nPlayers;
10531         changes = 1 + (strcmp(p, q) != 0);
10532     }
10533     if(changes == 1) { // a single engine mnemonic was changed
10534         q = r; while(*q) nPlayers += (*q++ == '\n');
10535         p = buf; while(*r && (*p = *r++) != '\n') p++;
10536         *p = NULLCHAR;
10537         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10538         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10539         if(mnemonic[i]) { // The substitute is valid
10540             FILE *f;
10541             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10542                 flock(fileno(f), LOCK_EX);
10543                 ParseArgsFromFile(f);
10544                 fseek(f, 0, SEEK_SET);
10545                 FREE(appData.participants); appData.participants = participants;
10546                 if(expunge) { // erase results of replaced engine
10547                     int len = strlen(appData.results), w, b, dummy;
10548                     for(i=0; i<len; i++) {
10549                         Pairing(i, nPlayers, &w, &b, &dummy);
10550                         if((w == changed || b == changed) && appData.results[i] == '*') {
10551                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10552                             fclose(f);
10553                             return;
10554                         }
10555                     }
10556                     for(i=0; i<len; i++) {
10557                         Pairing(i, nPlayers, &w, &b, &dummy);
10558                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10559                     }
10560                 }
10561                 WriteTourneyFile(appData.results, f);
10562                 fclose(f); // release lock
10563                 return;
10564             }
10565         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10566     }
10567     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10568     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10569     free(participants);
10570     return;
10571 }
10572
10573 int
10574 CheckPlayers (char *participants)
10575 {
10576         int i;
10577         char buf[MSG_SIZ], *p;
10578         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10579         while(p = strchr(participants, '\n')) {
10580             *p = NULLCHAR;
10581             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10582             if(!mnemonic[i]) {
10583                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10584                 *p = '\n';
10585                 DisplayError(buf, 0);
10586                 return 1;
10587             }
10588             *p = '\n';
10589             participants = p + 1;
10590         }
10591         return 0;
10592 }
10593
10594 int
10595 CreateTourney (char *name)
10596 {
10597         FILE *f;
10598         if(matchMode && strcmp(name, appData.tourneyFile)) {
10599              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10600         }
10601         if(name[0] == NULLCHAR) {
10602             if(appData.participants[0])
10603                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10604             return 0;
10605         }
10606         f = fopen(name, "r");
10607         if(f) { // file exists
10608             ASSIGN(appData.tourneyFile, name);
10609             ParseArgsFromFile(f); // parse it
10610         } else {
10611             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10612             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10613                 DisplayError(_("Not enough participants"), 0);
10614                 return 0;
10615             }
10616             if(CheckPlayers(appData.participants)) return 0;
10617             ASSIGN(appData.tourneyFile, name);
10618             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10619             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10620         }
10621         fclose(f);
10622         appData.noChessProgram = FALSE;
10623         appData.clockMode = TRUE;
10624         SetGNUMode();
10625         return 1;
10626 }
10627
10628 int
10629 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10630 {
10631     char buf[MSG_SIZ], *p, *q;
10632     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10633     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10634     skip = !all && group[0]; // if group requested, we start in skip mode
10635     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10636         p = names; q = buf; header = 0;
10637         while(*p && *p != '\n') *q++ = *p++;
10638         *q = 0;
10639         if(*p == '\n') p++;
10640         if(buf[0] == '#') {
10641             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10642             depth++; // we must be entering a new group
10643             if(all) continue; // suppress printing group headers when complete list requested
10644             header = 1;
10645             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10646         }
10647         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10648         if(engineList[i]) free(engineList[i]);
10649         engineList[i] = strdup(buf);
10650         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10651         if(engineMnemonic[i]) free(engineMnemonic[i]);
10652         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10653             strcat(buf, " (");
10654             sscanf(q + 8, "%s", buf + strlen(buf));
10655             strcat(buf, ")");
10656         }
10657         engineMnemonic[i] = strdup(buf);
10658         i++;
10659     }
10660     engineList[i] = engineMnemonic[i] = NULL;
10661     return i;
10662 }
10663
10664 // following implemented as macro to avoid type limitations
10665 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10666
10667 void
10668 SwapEngines (int n)
10669 {   // swap settings for first engine and other engine (so far only some selected options)
10670     int h;
10671     char *p;
10672     if(n == 0) return;
10673     SWAP(directory, p)
10674     SWAP(chessProgram, p)
10675     SWAP(isUCI, h)
10676     SWAP(hasOwnBookUCI, h)
10677     SWAP(protocolVersion, h)
10678     SWAP(reuse, h)
10679     SWAP(scoreIsAbsolute, h)
10680     SWAP(timeOdds, h)
10681     SWAP(logo, p)
10682     SWAP(pgnName, p)
10683     SWAP(pvSAN, h)
10684     SWAP(engOptions, p)
10685     SWAP(engInitString, p)
10686     SWAP(computerString, p)
10687     SWAP(features, p)
10688     SWAP(fenOverride, p)
10689     SWAP(NPS, h)
10690     SWAP(accumulateTC, h)
10691     SWAP(host, p)
10692 }
10693
10694 int
10695 GetEngineLine (char *s, int n)
10696 {
10697     int i;
10698     char buf[MSG_SIZ];
10699     extern char *icsNames;
10700     if(!s || !*s) return 0;
10701     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10702     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10703     if(!mnemonic[i]) return 0;
10704     if(n == 11) return 1; // just testing if there was a match
10705     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10706     if(n == 1) SwapEngines(n);
10707     ParseArgsFromString(buf);
10708     if(n == 1) SwapEngines(n);
10709     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10710         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10711         ParseArgsFromString(buf);
10712     }
10713     return 1;
10714 }
10715
10716 int
10717 SetPlayer (int player, char *p)
10718 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10719     int i;
10720     char buf[MSG_SIZ], *engineName;
10721     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10722     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10723     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10724     if(mnemonic[i]) {
10725         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10726         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10727         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10728         ParseArgsFromString(buf);
10729     } else { // no engine with this nickname is installed!
10730         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10731         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10732         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10733         ModeHighlight();
10734         DisplayError(buf, 0);
10735         return 0;
10736     }
10737     free(engineName);
10738     return i;
10739 }
10740
10741 char *recentEngines;
10742
10743 void
10744 RecentEngineEvent (int nr)
10745 {
10746     int n;
10747 //    SwapEngines(1); // bump first to second
10748 //    ReplaceEngine(&second, 1); // and load it there
10749     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10750     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10751     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10752         ReplaceEngine(&first, 0);
10753         FloatToFront(&appData.recentEngineList, command[n]);
10754     }
10755 }
10756
10757 int
10758 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10759 {   // determine players from game number
10760     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10761
10762     if(appData.tourneyType == 0) {
10763         roundsPerCycle = (nPlayers - 1) | 1;
10764         pairingsPerRound = nPlayers / 2;
10765     } else if(appData.tourneyType > 0) {
10766         roundsPerCycle = nPlayers - appData.tourneyType;
10767         pairingsPerRound = appData.tourneyType;
10768     }
10769     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10770     gamesPerCycle = gamesPerRound * roundsPerCycle;
10771     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10772     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10773     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10774     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10775     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10776     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10777
10778     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10779     if(appData.roundSync) *syncInterval = gamesPerRound;
10780
10781     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10782
10783     if(appData.tourneyType == 0) {
10784         if(curPairing == (nPlayers-1)/2 ) {
10785             *whitePlayer = curRound;
10786             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10787         } else {
10788             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10789             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10790             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10791             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10792         }
10793     } else if(appData.tourneyType > 1) {
10794         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10795         *whitePlayer = curRound + appData.tourneyType;
10796     } else if(appData.tourneyType > 0) {
10797         *whitePlayer = curPairing;
10798         *blackPlayer = curRound + appData.tourneyType;
10799     }
10800
10801     // take care of white/black alternation per round.
10802     // For cycles and games this is already taken care of by default, derived from matchGame!
10803     return curRound & 1;
10804 }
10805
10806 int
10807 NextTourneyGame (int nr, int *swapColors)
10808 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10809     char *p, *q;
10810     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10811     FILE *tf;
10812     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10813     tf = fopen(appData.tourneyFile, "r");
10814     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10815     ParseArgsFromFile(tf); fclose(tf);
10816     InitTimeControls(); // TC might be altered from tourney file
10817
10818     nPlayers = CountPlayers(appData.participants); // count participants
10819     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10820     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10821
10822     if(syncInterval) {
10823         p = q = appData.results;
10824         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10825         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10826             DisplayMessage(_("Waiting for other game(s)"),"");
10827             waitingForGame = TRUE;
10828             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10829             return 0;
10830         }
10831         waitingForGame = FALSE;
10832     }
10833
10834     if(appData.tourneyType < 0) {
10835         if(nr>=0 && !pairingReceived) {
10836             char buf[1<<16];
10837             if(pairing.pr == NoProc) {
10838                 if(!appData.pairingEngine[0]) {
10839                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10840                     return 0;
10841                 }
10842                 StartChessProgram(&pairing); // starts the pairing engine
10843             }
10844             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10845             SendToProgram(buf, &pairing);
10846             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10847             SendToProgram(buf, &pairing);
10848             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10849         }
10850         pairingReceived = 0;                              // ... so we continue here
10851         *swapColors = 0;
10852         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10853         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10854         matchGame = 1; roundNr = nr / syncInterval + 1;
10855     }
10856
10857     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10858
10859     // redefine engines, engine dir, etc.
10860     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10861     if(first.pr == NoProc) {
10862       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10863       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10864     }
10865     if(second.pr == NoProc) {
10866       SwapEngines(1);
10867       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10868       SwapEngines(1);         // and make that valid for second engine by swapping
10869       InitEngine(&second, 1);
10870     }
10871     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10872     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10873     return OK;
10874 }
10875
10876 void
10877 NextMatchGame ()
10878 {   // performs game initialization that does not invoke engines, and then tries to start the game
10879     int res, firstWhite, swapColors = 0;
10880     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10881     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
10882         char buf[MSG_SIZ];
10883         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10884         if(strcmp(buf, currentDebugFile)) { // name has changed
10885             FILE *f = fopen(buf, "w");
10886             if(f) { // if opening the new file failed, just keep using the old one
10887                 ASSIGN(currentDebugFile, buf);
10888                 fclose(debugFP);
10889                 debugFP = f;
10890             }
10891             if(appData.serverFileName) {
10892                 if(serverFP) fclose(serverFP);
10893                 serverFP = fopen(appData.serverFileName, "w");
10894                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10895                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10896             }
10897         }
10898     }
10899     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10900     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10901     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10902     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10903     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10904     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10905     Reset(FALSE, first.pr != NoProc);
10906     res = LoadGameOrPosition(matchGame); // setup game
10907     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10908     if(!res) return; // abort when bad game/pos file
10909     TwoMachinesEvent();
10910 }
10911
10912 void
10913 UserAdjudicationEvent (int result)
10914 {
10915     ChessMove gameResult = GameIsDrawn;
10916
10917     if( result > 0 ) {
10918         gameResult = WhiteWins;
10919     }
10920     else if( result < 0 ) {
10921         gameResult = BlackWins;
10922     }
10923
10924     if( gameMode == TwoMachinesPlay ) {
10925         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10926     }
10927 }
10928
10929
10930 // [HGM] save: calculate checksum of game to make games easily identifiable
10931 int
10932 StringCheckSum (char *s)
10933 {
10934         int i = 0;
10935         if(s==NULL) return 0;
10936         while(*s) i = i*259 + *s++;
10937         return i;
10938 }
10939
10940 int
10941 GameCheckSum ()
10942 {
10943         int i, sum=0;
10944         for(i=backwardMostMove; i<forwardMostMove; i++) {
10945                 sum += pvInfoList[i].depth;
10946                 sum += StringCheckSum(parseList[i]);
10947                 sum += StringCheckSum(commentList[i]);
10948                 sum *= 261;
10949         }
10950         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10951         return sum + StringCheckSum(commentList[i]);
10952 } // end of save patch
10953
10954 void
10955 GameEnds (ChessMove result, char *resultDetails, int whosays)
10956 {
10957     GameMode nextGameMode;
10958     int isIcsGame;
10959     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10960
10961     if(endingGame) return; /* [HGM] crash: forbid recursion */
10962     endingGame = 1;
10963     if(twoBoards) { // [HGM] dual: switch back to one board
10964         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10965         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10966     }
10967     if (appData.debugMode) {
10968       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10969               result, resultDetails ? resultDetails : "(null)", whosays);
10970     }
10971
10972     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10973
10974     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10975
10976     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10977         /* If we are playing on ICS, the server decides when the
10978            game is over, but the engine can offer to draw, claim
10979            a draw, or resign.
10980          */
10981 #if ZIPPY
10982         if (appData.zippyPlay && first.initDone) {
10983             if (result == GameIsDrawn) {
10984                 /* In case draw still needs to be claimed */
10985                 SendToICS(ics_prefix);
10986                 SendToICS("draw\n");
10987             } else if (StrCaseStr(resultDetails, "resign")) {
10988                 SendToICS(ics_prefix);
10989                 SendToICS("resign\n");
10990             }
10991         }
10992 #endif
10993         endingGame = 0; /* [HGM] crash */
10994         return;
10995     }
10996
10997     /* If we're loading the game from a file, stop */
10998     if (whosays == GE_FILE) {
10999       (void) StopLoadGameTimer();
11000       gameFileFP = NULL;
11001     }
11002
11003     /* Cancel draw offers */
11004     first.offeredDraw = second.offeredDraw = 0;
11005
11006     /* If this is an ICS game, only ICS can really say it's done;
11007        if not, anyone can. */
11008     isIcsGame = (gameMode == IcsPlayingWhite ||
11009                  gameMode == IcsPlayingBlack ||
11010                  gameMode == IcsObserving    ||
11011                  gameMode == IcsExamining);
11012
11013     if (!isIcsGame || whosays == GE_ICS) {
11014         /* OK -- not an ICS game, or ICS said it was done */
11015         StopClocks();
11016         if (!isIcsGame && !appData.noChessProgram)
11017           SetUserThinkingEnables();
11018
11019         /* [HGM] if a machine claims the game end we verify this claim */
11020         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11021             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11022                 char claimer;
11023                 ChessMove trueResult = (ChessMove) -1;
11024
11025                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11026                                             first.twoMachinesColor[0] :
11027                                             second.twoMachinesColor[0] ;
11028
11029                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11030                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11031                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11032                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11033                 } else
11034                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11035                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11036                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11037                 } else
11038                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11039                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11040                 }
11041
11042                 // now verify win claims, but not in drop games, as we don't understand those yet
11043                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11044                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11045                     (result == WhiteWins && claimer == 'w' ||
11046                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11047                       if (appData.debugMode) {
11048                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11049                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11050                       }
11051                       if(result != trueResult) {
11052                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11053                               result = claimer == 'w' ? BlackWins : WhiteWins;
11054                               resultDetails = buf;
11055                       }
11056                 } else
11057                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11058                     && (forwardMostMove <= backwardMostMove ||
11059                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11060                         (claimer=='b')==(forwardMostMove&1))
11061                                                                                   ) {
11062                       /* [HGM] verify: draws that were not flagged are false claims */
11063                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11064                       result = claimer == 'w' ? BlackWins : WhiteWins;
11065                       resultDetails = buf;
11066                 }
11067                 /* (Claiming a loss is accepted no questions asked!) */
11068             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11069                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11070                 result = GameUnfinished;
11071                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11072             }
11073             /* [HGM] bare: don't allow bare King to win */
11074             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11075                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11076                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11077                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11078                && result != GameIsDrawn)
11079             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11080                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11081                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11082                         if(p >= 0 && p <= (int)WhiteKing) k++;
11083                 }
11084                 if (appData.debugMode) {
11085                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11086                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11087                 }
11088                 if(k <= 1) {
11089                         result = GameIsDrawn;
11090                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11091                         resultDetails = buf;
11092                 }
11093             }
11094         }
11095
11096
11097         if(serverMoves != NULL && !loadFlag) { char c = '=';
11098             if(result==WhiteWins) c = '+';
11099             if(result==BlackWins) c = '-';
11100             if(resultDetails != NULL)
11101                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11102         }
11103         if (resultDetails != NULL) {
11104             gameInfo.result = result;
11105             gameInfo.resultDetails = StrSave(resultDetails);
11106
11107             /* display last move only if game was not loaded from file */
11108             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11109                 DisplayMove(currentMove - 1);
11110
11111             if (forwardMostMove != 0) {
11112                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11113                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11114                                                                 ) {
11115                     if (*appData.saveGameFile != NULLCHAR) {
11116                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11117                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11118                         else
11119                         SaveGameToFile(appData.saveGameFile, TRUE);
11120                     } else if (appData.autoSaveGames) {
11121                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11122                     }
11123                     if (*appData.savePositionFile != NULLCHAR) {
11124                         SavePositionToFile(appData.savePositionFile);
11125                     }
11126                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11127                 }
11128             }
11129
11130             /* Tell program how game ended in case it is learning */
11131             /* [HGM] Moved this to after saving the PGN, just in case */
11132             /* engine died and we got here through time loss. In that */
11133             /* case we will get a fatal error writing the pipe, which */
11134             /* would otherwise lose us the PGN.                       */
11135             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11136             /* output during GameEnds should never be fatal anymore   */
11137             if (gameMode == MachinePlaysWhite ||
11138                 gameMode == MachinePlaysBlack ||
11139                 gameMode == TwoMachinesPlay ||
11140                 gameMode == IcsPlayingWhite ||
11141                 gameMode == IcsPlayingBlack ||
11142                 gameMode == BeginningOfGame) {
11143                 char buf[MSG_SIZ];
11144                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11145                         resultDetails);
11146                 if (first.pr != NoProc) {
11147                     SendToProgram(buf, &first);
11148                 }
11149                 if (second.pr != NoProc &&
11150                     gameMode == TwoMachinesPlay) {
11151                     SendToProgram(buf, &second);
11152                 }
11153             }
11154         }
11155
11156         if (appData.icsActive) {
11157             if (appData.quietPlay &&
11158                 (gameMode == IcsPlayingWhite ||
11159                  gameMode == IcsPlayingBlack)) {
11160                 SendToICS(ics_prefix);
11161                 SendToICS("set shout 1\n");
11162             }
11163             nextGameMode = IcsIdle;
11164             ics_user_moved = FALSE;
11165             /* clean up premove.  It's ugly when the game has ended and the
11166              * premove highlights are still on the board.
11167              */
11168             if (gotPremove) {
11169               gotPremove = FALSE;
11170               ClearPremoveHighlights();
11171               DrawPosition(FALSE, boards[currentMove]);
11172             }
11173             if (whosays == GE_ICS) {
11174                 switch (result) {
11175                 case WhiteWins:
11176                     if (gameMode == IcsPlayingWhite)
11177                         PlayIcsWinSound();
11178                     else if(gameMode == IcsPlayingBlack)
11179                         PlayIcsLossSound();
11180                     break;
11181                 case BlackWins:
11182                     if (gameMode == IcsPlayingBlack)
11183                         PlayIcsWinSound();
11184                     else if(gameMode == IcsPlayingWhite)
11185                         PlayIcsLossSound();
11186                     break;
11187                 case GameIsDrawn:
11188                     PlayIcsDrawSound();
11189                     break;
11190                 default:
11191                     PlayIcsUnfinishedSound();
11192                 }
11193             }
11194             if(appData.quitNext) { ExitEvent(0); return; }
11195         } else if (gameMode == EditGame ||
11196                    gameMode == PlayFromGameFile ||
11197                    gameMode == AnalyzeMode ||
11198                    gameMode == AnalyzeFile) {
11199             nextGameMode = gameMode;
11200         } else {
11201             nextGameMode = EndOfGame;
11202         }
11203         pausing = FALSE;
11204         ModeHighlight();
11205     } else {
11206         nextGameMode = gameMode;
11207     }
11208
11209     if (appData.noChessProgram) {
11210         gameMode = nextGameMode;
11211         ModeHighlight();
11212         endingGame = 0; /* [HGM] crash */
11213         return;
11214     }
11215
11216     if (first.reuse) {
11217         /* Put first chess program into idle state */
11218         if (first.pr != NoProc &&
11219             (gameMode == MachinePlaysWhite ||
11220              gameMode == MachinePlaysBlack ||
11221              gameMode == TwoMachinesPlay ||
11222              gameMode == IcsPlayingWhite ||
11223              gameMode == IcsPlayingBlack ||
11224              gameMode == BeginningOfGame)) {
11225             SendToProgram("force\n", &first);
11226             if (first.usePing) {
11227               char buf[MSG_SIZ];
11228               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11229               SendToProgram(buf, &first);
11230             }
11231         }
11232     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11233         /* Kill off first chess program */
11234         if (first.isr != NULL)
11235           RemoveInputSource(first.isr);
11236         first.isr = NULL;
11237
11238         if (first.pr != NoProc) {
11239             ExitAnalyzeMode();
11240             DoSleep( appData.delayBeforeQuit );
11241             SendToProgram("quit\n", &first);
11242             DoSleep( appData.delayAfterQuit );
11243             DestroyChildProcess(first.pr, first.useSigterm);
11244             first.reload = TRUE;
11245         }
11246         first.pr = NoProc;
11247     }
11248     if (second.reuse) {
11249         /* Put second chess program into idle state */
11250         if (second.pr != NoProc &&
11251             gameMode == TwoMachinesPlay) {
11252             SendToProgram("force\n", &second);
11253             if (second.usePing) {
11254               char buf[MSG_SIZ];
11255               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11256               SendToProgram(buf, &second);
11257             }
11258         }
11259     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11260         /* Kill off second chess program */
11261         if (second.isr != NULL)
11262           RemoveInputSource(second.isr);
11263         second.isr = NULL;
11264
11265         if (second.pr != NoProc) {
11266             DoSleep( appData.delayBeforeQuit );
11267             SendToProgram("quit\n", &second);
11268             DoSleep( appData.delayAfterQuit );
11269             DestroyChildProcess(second.pr, second.useSigterm);
11270             second.reload = TRUE;
11271         }
11272         second.pr = NoProc;
11273     }
11274
11275     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11276         char resChar = '=';
11277         switch (result) {
11278         case WhiteWins:
11279           resChar = '+';
11280           if (first.twoMachinesColor[0] == 'w') {
11281             first.matchWins++;
11282           } else {
11283             second.matchWins++;
11284           }
11285           break;
11286         case BlackWins:
11287           resChar = '-';
11288           if (first.twoMachinesColor[0] == 'b') {
11289             first.matchWins++;
11290           } else {
11291             second.matchWins++;
11292           }
11293           break;
11294         case GameUnfinished:
11295           resChar = ' ';
11296         default:
11297           break;
11298         }
11299
11300         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11301         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11302             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11303             ReserveGame(nextGame, resChar); // sets nextGame
11304             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11305             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11306         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11307
11308         if (nextGame <= appData.matchGames && !abortMatch) {
11309             gameMode = nextGameMode;
11310             matchGame = nextGame; // this will be overruled in tourney mode!
11311             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11312             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11313             endingGame = 0; /* [HGM] crash */
11314             return;
11315         } else {
11316             gameMode = nextGameMode;
11317             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11318                      first.tidy, second.tidy,
11319                      first.matchWins, second.matchWins,
11320                      appData.matchGames - (first.matchWins + second.matchWins));
11321             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11322             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11323             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11324             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11325                 first.twoMachinesColor = "black\n";
11326                 second.twoMachinesColor = "white\n";
11327             } else {
11328                 first.twoMachinesColor = "white\n";
11329                 second.twoMachinesColor = "black\n";
11330             }
11331         }
11332     }
11333     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11334         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11335       ExitAnalyzeMode();
11336     gameMode = nextGameMode;
11337     ModeHighlight();
11338     endingGame = 0;  /* [HGM] crash */
11339     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11340         if(matchMode == TRUE) { // match through command line: exit with or without popup
11341             if(ranking) {
11342                 ToNrEvent(forwardMostMove);
11343                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11344                 else ExitEvent(0);
11345             } else DisplayFatalError(buf, 0, 0);
11346         } else { // match through menu; just stop, with or without popup
11347             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11348             ModeHighlight();
11349             if(ranking){
11350                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11351             } else DisplayNote(buf);
11352       }
11353       if(ranking) free(ranking);
11354     }
11355 }
11356
11357 /* Assumes program was just initialized (initString sent).
11358    Leaves program in force mode. */
11359 void
11360 FeedMovesToProgram (ChessProgramState *cps, int upto)
11361 {
11362     int i;
11363
11364     if (appData.debugMode)
11365       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11366               startedFromSetupPosition ? "position and " : "",
11367               backwardMostMove, upto, cps->which);
11368     if(currentlyInitializedVariant != gameInfo.variant) {
11369       char buf[MSG_SIZ];
11370         // [HGM] variantswitch: make engine aware of new variant
11371         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11372                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11373                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11374         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11375         SendToProgram(buf, cps);
11376         currentlyInitializedVariant = gameInfo.variant;
11377     }
11378     SendToProgram("force\n", cps);
11379     if (startedFromSetupPosition) {
11380         SendBoard(cps, backwardMostMove);
11381     if (appData.debugMode) {
11382         fprintf(debugFP, "feedMoves\n");
11383     }
11384     }
11385     for (i = backwardMostMove; i < upto; i++) {
11386         SendMoveToProgram(i, cps);
11387     }
11388 }
11389
11390
11391 int
11392 ResurrectChessProgram ()
11393 {
11394      /* The chess program may have exited.
11395         If so, restart it and feed it all the moves made so far. */
11396     static int doInit = 0;
11397
11398     if (appData.noChessProgram) return 1;
11399
11400     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11401         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11402         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11403         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11404     } else {
11405         if (first.pr != NoProc) return 1;
11406         StartChessProgram(&first);
11407     }
11408     InitChessProgram(&first, FALSE);
11409     FeedMovesToProgram(&first, currentMove);
11410
11411     if (!first.sendTime) {
11412         /* can't tell gnuchess what its clock should read,
11413            so we bow to its notion. */
11414         ResetClocks();
11415         timeRemaining[0][currentMove] = whiteTimeRemaining;
11416         timeRemaining[1][currentMove] = blackTimeRemaining;
11417     }
11418
11419     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11420                 appData.icsEngineAnalyze) && first.analysisSupport) {
11421       SendToProgram("analyze\n", &first);
11422       first.analyzing = TRUE;
11423     }
11424     return 1;
11425 }
11426
11427 /*
11428  * Button procedures
11429  */
11430 void
11431 Reset (int redraw, int init)
11432 {
11433     int i;
11434
11435     if (appData.debugMode) {
11436         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11437                 redraw, init, gameMode);
11438     }
11439     CleanupTail(); // [HGM] vari: delete any stored variations
11440     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11441     pausing = pauseExamInvalid = FALSE;
11442     startedFromSetupPosition = blackPlaysFirst = FALSE;
11443     firstMove = TRUE;
11444     whiteFlag = blackFlag = FALSE;
11445     userOfferedDraw = FALSE;
11446     hintRequested = bookRequested = FALSE;
11447     first.maybeThinking = FALSE;
11448     second.maybeThinking = FALSE;
11449     first.bookSuspend = FALSE; // [HGM] book
11450     second.bookSuspend = FALSE;
11451     thinkOutput[0] = NULLCHAR;
11452     lastHint[0] = NULLCHAR;
11453     ClearGameInfo(&gameInfo);
11454     gameInfo.variant = StringToVariant(appData.variant);
11455     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11456     ics_user_moved = ics_clock_paused = FALSE;
11457     ics_getting_history = H_FALSE;
11458     ics_gamenum = -1;
11459     white_holding[0] = black_holding[0] = NULLCHAR;
11460     ClearProgramStats();
11461     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11462
11463     ResetFrontEnd();
11464     ClearHighlights();
11465     flipView = appData.flipView;
11466     ClearPremoveHighlights();
11467     gotPremove = FALSE;
11468     alarmSounded = FALSE;
11469     killX = killY = -1; // [HGM] lion
11470
11471     GameEnds(EndOfFile, NULL, GE_PLAYER);
11472     if(appData.serverMovesName != NULL) {
11473         /* [HGM] prepare to make moves file for broadcasting */
11474         clock_t t = clock();
11475         if(serverMoves != NULL) fclose(serverMoves);
11476         serverMoves = fopen(appData.serverMovesName, "r");
11477         if(serverMoves != NULL) {
11478             fclose(serverMoves);
11479             /* delay 15 sec before overwriting, so all clients can see end */
11480             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11481         }
11482         serverMoves = fopen(appData.serverMovesName, "w");
11483     }
11484
11485     ExitAnalyzeMode();
11486     gameMode = BeginningOfGame;
11487     ModeHighlight();
11488     if(appData.icsActive) gameInfo.variant = VariantNormal;
11489     currentMove = forwardMostMove = backwardMostMove = 0;
11490     MarkTargetSquares(1);
11491     InitPosition(redraw);
11492     for (i = 0; i < MAX_MOVES; i++) {
11493         if (commentList[i] != NULL) {
11494             free(commentList[i]);
11495             commentList[i] = NULL;
11496         }
11497     }
11498     ResetClocks();
11499     timeRemaining[0][0] = whiteTimeRemaining;
11500     timeRemaining[1][0] = blackTimeRemaining;
11501
11502     if (first.pr == NoProc) {
11503         StartChessProgram(&first);
11504     }
11505     if (init) {
11506             InitChessProgram(&first, startedFromSetupPosition);
11507     }
11508     DisplayTitle("");
11509     DisplayMessage("", "");
11510     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11511     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11512     ClearMap();        // [HGM] exclude: invalidate map
11513 }
11514
11515 void
11516 AutoPlayGameLoop ()
11517 {
11518     for (;;) {
11519         if (!AutoPlayOneMove())
11520           return;
11521         if (matchMode || appData.timeDelay == 0)
11522           continue;
11523         if (appData.timeDelay < 0)
11524           return;
11525         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11526         break;
11527     }
11528 }
11529
11530 void
11531 AnalyzeNextGame()
11532 {
11533     ReloadGame(1); // next game
11534 }
11535
11536 int
11537 AutoPlayOneMove ()
11538 {
11539     int fromX, fromY, toX, toY;
11540
11541     if (appData.debugMode) {
11542       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11543     }
11544
11545     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11546       return FALSE;
11547
11548     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11549       pvInfoList[currentMove].depth = programStats.depth;
11550       pvInfoList[currentMove].score = programStats.score;
11551       pvInfoList[currentMove].time  = 0;
11552       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11553       else { // append analysis of final position as comment
11554         char buf[MSG_SIZ];
11555         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11556         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11557       }
11558       programStats.depth = 0;
11559     }
11560
11561     if (currentMove >= forwardMostMove) {
11562       if(gameMode == AnalyzeFile) {
11563           if(appData.loadGameIndex == -1) {
11564             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11565           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11566           } else {
11567           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11568         }
11569       }
11570 //      gameMode = EndOfGame;
11571 //      ModeHighlight();
11572
11573       /* [AS] Clear current move marker at the end of a game */
11574       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11575
11576       return FALSE;
11577     }
11578
11579     toX = moveList[currentMove][2] - AAA;
11580     toY = moveList[currentMove][3] - ONE;
11581
11582     if (moveList[currentMove][1] == '@') {
11583         if (appData.highlightLastMove) {
11584             SetHighlights(-1, -1, toX, toY);
11585         }
11586     } else {
11587         fromX = moveList[currentMove][0] - AAA;
11588         fromY = moveList[currentMove][1] - ONE;
11589
11590         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11591
11592         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11593
11594         if (appData.highlightLastMove) {
11595             SetHighlights(fromX, fromY, toX, toY);
11596         }
11597     }
11598     DisplayMove(currentMove);
11599     SendMoveToProgram(currentMove++, &first);
11600     DisplayBothClocks();
11601     DrawPosition(FALSE, boards[currentMove]);
11602     // [HGM] PV info: always display, routine tests if empty
11603     DisplayComment(currentMove - 1, commentList[currentMove]);
11604     return TRUE;
11605 }
11606
11607
11608 int
11609 LoadGameOneMove (ChessMove readAhead)
11610 {
11611     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11612     char promoChar = NULLCHAR;
11613     ChessMove moveType;
11614     char move[MSG_SIZ];
11615     char *p, *q;
11616
11617     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11618         gameMode != AnalyzeMode && gameMode != Training) {
11619         gameFileFP = NULL;
11620         return FALSE;
11621     }
11622
11623     yyboardindex = forwardMostMove;
11624     if (readAhead != EndOfFile) {
11625       moveType = readAhead;
11626     } else {
11627       if (gameFileFP == NULL)
11628           return FALSE;
11629       moveType = (ChessMove) Myylex();
11630     }
11631
11632     done = FALSE;
11633     switch (moveType) {
11634       case Comment:
11635         if (appData.debugMode)
11636           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11637         p = yy_text;
11638
11639         /* append the comment but don't display it */
11640         AppendComment(currentMove, p, FALSE);
11641         return TRUE;
11642
11643       case WhiteCapturesEnPassant:
11644       case BlackCapturesEnPassant:
11645       case WhitePromotion:
11646       case BlackPromotion:
11647       case WhiteNonPromotion:
11648       case BlackNonPromotion:
11649       case NormalMove:
11650       case FirstLeg:
11651       case WhiteKingSideCastle:
11652       case WhiteQueenSideCastle:
11653       case BlackKingSideCastle:
11654       case BlackQueenSideCastle:
11655       case WhiteKingSideCastleWild:
11656       case WhiteQueenSideCastleWild:
11657       case BlackKingSideCastleWild:
11658       case BlackQueenSideCastleWild:
11659       /* PUSH Fabien */
11660       case WhiteHSideCastleFR:
11661       case WhiteASideCastleFR:
11662       case BlackHSideCastleFR:
11663       case BlackASideCastleFR:
11664       /* POP Fabien */
11665         if (appData.debugMode)
11666           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11667         fromX = currentMoveString[0] - AAA;
11668         fromY = currentMoveString[1] - ONE;
11669         toX = currentMoveString[2] - AAA;
11670         toY = currentMoveString[3] - ONE;
11671         promoChar = currentMoveString[4];
11672         if(promoChar == ';') promoChar = NULLCHAR;
11673         break;
11674
11675       case WhiteDrop:
11676       case BlackDrop:
11677         if (appData.debugMode)
11678           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11679         fromX = moveType == WhiteDrop ?
11680           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11681         (int) CharToPiece(ToLower(currentMoveString[0]));
11682         fromY = DROP_RANK;
11683         toX = currentMoveString[2] - AAA;
11684         toY = currentMoveString[3] - ONE;
11685         break;
11686
11687       case WhiteWins:
11688       case BlackWins:
11689       case GameIsDrawn:
11690       case GameUnfinished:
11691         if (appData.debugMode)
11692           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11693         p = strchr(yy_text, '{');
11694         if (p == NULL) p = strchr(yy_text, '(');
11695         if (p == NULL) {
11696             p = yy_text;
11697             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11698         } else {
11699             q = strchr(p, *p == '{' ? '}' : ')');
11700             if (q != NULL) *q = NULLCHAR;
11701             p++;
11702         }
11703         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11704         GameEnds(moveType, p, GE_FILE);
11705         done = TRUE;
11706         if (cmailMsgLoaded) {
11707             ClearHighlights();
11708             flipView = WhiteOnMove(currentMove);
11709             if (moveType == GameUnfinished) flipView = !flipView;
11710             if (appData.debugMode)
11711               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11712         }
11713         break;
11714
11715       case EndOfFile:
11716         if (appData.debugMode)
11717           fprintf(debugFP, "Parser hit end of file\n");
11718         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11719           case MT_NONE:
11720           case MT_CHECK:
11721             break;
11722           case MT_CHECKMATE:
11723           case MT_STAINMATE:
11724             if (WhiteOnMove(currentMove)) {
11725                 GameEnds(BlackWins, "Black mates", GE_FILE);
11726             } else {
11727                 GameEnds(WhiteWins, "White mates", GE_FILE);
11728             }
11729             break;
11730           case MT_STALEMATE:
11731             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11732             break;
11733         }
11734         done = TRUE;
11735         break;
11736
11737       case MoveNumberOne:
11738         if (lastLoadGameStart == GNUChessGame) {
11739             /* GNUChessGames have numbers, but they aren't move numbers */
11740             if (appData.debugMode)
11741               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11742                       yy_text, (int) moveType);
11743             return LoadGameOneMove(EndOfFile); /* tail recursion */
11744         }
11745         /* else fall thru */
11746
11747       case XBoardGame:
11748       case GNUChessGame:
11749       case PGNTag:
11750         /* Reached start of next game in file */
11751         if (appData.debugMode)
11752           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11753         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11754           case MT_NONE:
11755           case MT_CHECK:
11756             break;
11757           case MT_CHECKMATE:
11758           case MT_STAINMATE:
11759             if (WhiteOnMove(currentMove)) {
11760                 GameEnds(BlackWins, "Black mates", GE_FILE);
11761             } else {
11762                 GameEnds(WhiteWins, "White mates", GE_FILE);
11763             }
11764             break;
11765           case MT_STALEMATE:
11766             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11767             break;
11768         }
11769         done = TRUE;
11770         break;
11771
11772       case PositionDiagram:     /* should not happen; ignore */
11773       case ElapsedTime:         /* ignore */
11774       case NAG:                 /* ignore */
11775         if (appData.debugMode)
11776           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11777                   yy_text, (int) moveType);
11778         return LoadGameOneMove(EndOfFile); /* tail recursion */
11779
11780       case IllegalMove:
11781         if (appData.testLegality) {
11782             if (appData.debugMode)
11783               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11784             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11785                     (forwardMostMove / 2) + 1,
11786                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11787             DisplayError(move, 0);
11788             done = TRUE;
11789         } else {
11790             if (appData.debugMode)
11791               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11792                       yy_text, currentMoveString);
11793             fromX = currentMoveString[0] - AAA;
11794             fromY = currentMoveString[1] - ONE;
11795             toX = currentMoveString[2] - AAA;
11796             toY = currentMoveString[3] - ONE;
11797             promoChar = currentMoveString[4];
11798         }
11799         break;
11800
11801       case AmbiguousMove:
11802         if (appData.debugMode)
11803           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11804         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11805                 (forwardMostMove / 2) + 1,
11806                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11807         DisplayError(move, 0);
11808         done = TRUE;
11809         break;
11810
11811       default:
11812       case ImpossibleMove:
11813         if (appData.debugMode)
11814           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11815         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11816                 (forwardMostMove / 2) + 1,
11817                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11818         DisplayError(move, 0);
11819         done = TRUE;
11820         break;
11821     }
11822
11823     if (done) {
11824         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11825             DrawPosition(FALSE, boards[currentMove]);
11826             DisplayBothClocks();
11827             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11828               DisplayComment(currentMove - 1, commentList[currentMove]);
11829         }
11830         (void) StopLoadGameTimer();
11831         gameFileFP = NULL;
11832         cmailOldMove = forwardMostMove;
11833         return FALSE;
11834     } else {
11835         /* currentMoveString is set as a side-effect of yylex */
11836
11837         thinkOutput[0] = NULLCHAR;
11838         MakeMove(fromX, fromY, toX, toY, promoChar);
11839         killX = killY = -1; // [HGM] lion: used up
11840         currentMove = forwardMostMove;
11841         return TRUE;
11842     }
11843 }
11844
11845 /* Load the nth game from the given file */
11846 int
11847 LoadGameFromFile (char *filename, int n, char *title, int useList)
11848 {
11849     FILE *f;
11850     char buf[MSG_SIZ];
11851
11852     if (strcmp(filename, "-") == 0) {
11853         f = stdin;
11854         title = "stdin";
11855     } else {
11856         f = fopen(filename, "rb");
11857         if (f == NULL) {
11858           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11859             DisplayError(buf, errno);
11860             return FALSE;
11861         }
11862     }
11863     if (fseek(f, 0, 0) == -1) {
11864         /* f is not seekable; probably a pipe */
11865         useList = FALSE;
11866     }
11867     if (useList && n == 0) {
11868         int error = GameListBuild(f);
11869         if (error) {
11870             DisplayError(_("Cannot build game list"), error);
11871         } else if (!ListEmpty(&gameList) &&
11872                    ((ListGame *) gameList.tailPred)->number > 1) {
11873             GameListPopUp(f, title);
11874             return TRUE;
11875         }
11876         GameListDestroy();
11877         n = 1;
11878     }
11879     if (n == 0) n = 1;
11880     return LoadGame(f, n, title, FALSE);
11881 }
11882
11883
11884 void
11885 MakeRegisteredMove ()
11886 {
11887     int fromX, fromY, toX, toY;
11888     char promoChar;
11889     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11890         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11891           case CMAIL_MOVE:
11892           case CMAIL_DRAW:
11893             if (appData.debugMode)
11894               fprintf(debugFP, "Restoring %s for game %d\n",
11895                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11896
11897             thinkOutput[0] = NULLCHAR;
11898             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11899             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11900             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11901             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11902             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11903             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11904             MakeMove(fromX, fromY, toX, toY, promoChar);
11905             ShowMove(fromX, fromY, toX, toY);
11906
11907             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11908               case MT_NONE:
11909               case MT_CHECK:
11910                 break;
11911
11912               case MT_CHECKMATE:
11913               case MT_STAINMATE:
11914                 if (WhiteOnMove(currentMove)) {
11915                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11916                 } else {
11917                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11918                 }
11919                 break;
11920
11921               case MT_STALEMATE:
11922                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11923                 break;
11924             }
11925
11926             break;
11927
11928           case CMAIL_RESIGN:
11929             if (WhiteOnMove(currentMove)) {
11930                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11931             } else {
11932                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11933             }
11934             break;
11935
11936           case CMAIL_ACCEPT:
11937             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11938             break;
11939
11940           default:
11941             break;
11942         }
11943     }
11944
11945     return;
11946 }
11947
11948 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11949 int
11950 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11951 {
11952     int retVal;
11953
11954     if (gameNumber > nCmailGames) {
11955         DisplayError(_("No more games in this message"), 0);
11956         return FALSE;
11957     }
11958     if (f == lastLoadGameFP) {
11959         int offset = gameNumber - lastLoadGameNumber;
11960         if (offset == 0) {
11961             cmailMsg[0] = NULLCHAR;
11962             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11963                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11964                 nCmailMovesRegistered--;
11965             }
11966             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11967             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11968                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11969             }
11970         } else {
11971             if (! RegisterMove()) return FALSE;
11972         }
11973     }
11974
11975     retVal = LoadGame(f, gameNumber, title, useList);
11976
11977     /* Make move registered during previous look at this game, if any */
11978     MakeRegisteredMove();
11979
11980     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11981         commentList[currentMove]
11982           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11983         DisplayComment(currentMove - 1, commentList[currentMove]);
11984     }
11985
11986     return retVal;
11987 }
11988
11989 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11990 int
11991 ReloadGame (int offset)
11992 {
11993     int gameNumber = lastLoadGameNumber + offset;
11994     if (lastLoadGameFP == NULL) {
11995         DisplayError(_("No game has been loaded yet"), 0);
11996         return FALSE;
11997     }
11998     if (gameNumber <= 0) {
11999         DisplayError(_("Can't back up any further"), 0);
12000         return FALSE;
12001     }
12002     if (cmailMsgLoaded) {
12003         return CmailLoadGame(lastLoadGameFP, gameNumber,
12004                              lastLoadGameTitle, lastLoadGameUseList);
12005     } else {
12006         return LoadGame(lastLoadGameFP, gameNumber,
12007                         lastLoadGameTitle, lastLoadGameUseList);
12008     }
12009 }
12010
12011 int keys[EmptySquare+1];
12012
12013 int
12014 PositionMatches (Board b1, Board b2)
12015 {
12016     int r, f, sum=0;
12017     switch(appData.searchMode) {
12018         case 1: return CompareWithRights(b1, b2);
12019         case 2:
12020             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12021                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12022             }
12023             return TRUE;
12024         case 3:
12025             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12026               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12027                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12028             }
12029             return sum==0;
12030         case 4:
12031             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12032                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12033             }
12034             return sum==0;
12035     }
12036     return TRUE;
12037 }
12038
12039 #define Q_PROMO  4
12040 #define Q_EP     3
12041 #define Q_BCASTL 2
12042 #define Q_WCASTL 1
12043
12044 int pieceList[256], quickBoard[256];
12045 ChessSquare pieceType[256] = { EmptySquare };
12046 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12047 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12048 int soughtTotal, turn;
12049 Boolean epOK, flipSearch;
12050
12051 typedef struct {
12052     unsigned char piece, to;
12053 } Move;
12054
12055 #define DSIZE (250000)
12056
12057 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12058 Move *moveDatabase = initialSpace;
12059 unsigned int movePtr, dataSize = DSIZE;
12060
12061 int
12062 MakePieceList (Board board, int *counts)
12063 {
12064     int r, f, n=Q_PROMO, total=0;
12065     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12066     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12067         int sq = f + (r<<4);
12068         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12069             quickBoard[sq] = ++n;
12070             pieceList[n] = sq;
12071             pieceType[n] = board[r][f];
12072             counts[board[r][f]]++;
12073             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12074             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12075             total++;
12076         }
12077     }
12078     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12079     return total;
12080 }
12081
12082 void
12083 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12084 {
12085     int sq = fromX + (fromY<<4);
12086     int piece = quickBoard[sq];
12087     quickBoard[sq] = 0;
12088     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12089     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12090         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12091         moveDatabase[movePtr++].piece = Q_WCASTL;
12092         quickBoard[sq] = piece;
12093         piece = quickBoard[from]; quickBoard[from] = 0;
12094         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12095     } else
12096     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12097         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12098         moveDatabase[movePtr++].piece = Q_BCASTL;
12099         quickBoard[sq] = piece;
12100         piece = quickBoard[from]; quickBoard[from] = 0;
12101         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12102     } else
12103     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12104         quickBoard[(fromY<<4)+toX] = 0;
12105         moveDatabase[movePtr].piece = Q_EP;
12106         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12107         moveDatabase[movePtr].to = sq;
12108     } else
12109     if(promoPiece != pieceType[piece]) {
12110         moveDatabase[movePtr++].piece = Q_PROMO;
12111         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12112     }
12113     moveDatabase[movePtr].piece = piece;
12114     quickBoard[sq] = piece;
12115     movePtr++;
12116 }
12117
12118 int
12119 PackGame (Board board)
12120 {
12121     Move *newSpace = NULL;
12122     moveDatabase[movePtr].piece = 0; // terminate previous game
12123     if(movePtr > dataSize) {
12124         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12125         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12126         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12127         if(newSpace) {
12128             int i;
12129             Move *p = moveDatabase, *q = newSpace;
12130             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12131             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12132             moveDatabase = newSpace;
12133         } else { // calloc failed, we must be out of memory. Too bad...
12134             dataSize = 0; // prevent calloc events for all subsequent games
12135             return 0;     // and signal this one isn't cached
12136         }
12137     }
12138     movePtr++;
12139     MakePieceList(board, counts);
12140     return movePtr;
12141 }
12142
12143 int
12144 QuickCompare (Board board, int *minCounts, int *maxCounts)
12145 {   // compare according to search mode
12146     int r, f;
12147     switch(appData.searchMode)
12148     {
12149       case 1: // exact position match
12150         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12151         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12152             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12153         }
12154         break;
12155       case 2: // can have extra material on empty squares
12156         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12157             if(board[r][f] == EmptySquare) continue;
12158             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12159         }
12160         break;
12161       case 3: // material with exact Pawn structure
12162         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12163             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12164             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12165         } // fall through to material comparison
12166       case 4: // exact material
12167         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12168         break;
12169       case 6: // material range with given imbalance
12170         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12171         // fall through to range comparison
12172       case 5: // material range
12173         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12174     }
12175     return TRUE;
12176 }
12177
12178 int
12179 QuickScan (Board board, Move *move)
12180 {   // reconstruct game,and compare all positions in it
12181     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12182     do {
12183         int piece = move->piece;
12184         int to = move->to, from = pieceList[piece];
12185         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12186           if(!piece) return -1;
12187           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12188             piece = (++move)->piece;
12189             from = pieceList[piece];
12190             counts[pieceType[piece]]--;
12191             pieceType[piece] = (ChessSquare) move->to;
12192             counts[move->to]++;
12193           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12194             counts[pieceType[quickBoard[to]]]--;
12195             quickBoard[to] = 0; total--;
12196             move++;
12197             continue;
12198           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12199             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12200             from  = pieceList[piece]; // so this must be King
12201             quickBoard[from] = 0;
12202             pieceList[piece] = to;
12203             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12204             quickBoard[from] = 0; // rook
12205             quickBoard[to] = piece;
12206             to = move->to; piece = move->piece;
12207             goto aftercastle;
12208           }
12209         }
12210         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12211         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12212         quickBoard[from] = 0;
12213       aftercastle:
12214         quickBoard[to] = piece;
12215         pieceList[piece] = to;
12216         cnt++; turn ^= 3;
12217         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12218            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12219            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12220                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12221           ) {
12222             static int lastCounts[EmptySquare+1];
12223             int i;
12224             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12225             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12226         } else stretch = 0;
12227         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12228         move++;
12229     } while(1);
12230 }
12231
12232 void
12233 InitSearch ()
12234 {
12235     int r, f;
12236     flipSearch = FALSE;
12237     CopyBoard(soughtBoard, boards[currentMove]);
12238     soughtTotal = MakePieceList(soughtBoard, maxSought);
12239     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12240     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12241     CopyBoard(reverseBoard, boards[currentMove]);
12242     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12243         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12244         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12245         reverseBoard[r][f] = piece;
12246     }
12247     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12248     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12249     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12250                  || (boards[currentMove][CASTLING][2] == NoRights ||
12251                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12252                  && (boards[currentMove][CASTLING][5] == NoRights ||
12253                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12254       ) {
12255         flipSearch = TRUE;
12256         CopyBoard(flipBoard, soughtBoard);
12257         CopyBoard(rotateBoard, reverseBoard);
12258         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12259             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12260             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12261         }
12262     }
12263     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12264     if(appData.searchMode >= 5) {
12265         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12266         MakePieceList(soughtBoard, minSought);
12267         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12268     }
12269     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12270         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12271 }
12272
12273 GameInfo dummyInfo;
12274 static int creatingBook;
12275
12276 int
12277 GameContainsPosition (FILE *f, ListGame *lg)
12278 {
12279     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12280     int fromX, fromY, toX, toY;
12281     char promoChar;
12282     static int initDone=FALSE;
12283
12284     // weed out games based on numerical tag comparison
12285     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12286     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12287     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12288     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12289     if(!initDone) {
12290         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12291         initDone = TRUE;
12292     }
12293     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12294     else CopyBoard(boards[scratch], initialPosition); // default start position
12295     if(lg->moves) {
12296         turn = btm + 1;
12297         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12298         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12299     }
12300     if(btm) plyNr++;
12301     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12302     fseek(f, lg->offset, 0);
12303     yynewfile(f);
12304     while(1) {
12305         yyboardindex = scratch;
12306         quickFlag = plyNr+1;
12307         next = Myylex();
12308         quickFlag = 0;
12309         switch(next) {
12310             case PGNTag:
12311                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12312             default:
12313                 continue;
12314
12315             case XBoardGame:
12316             case GNUChessGame:
12317                 if(plyNr) return -1; // after we have seen moves, this is for new game
12318               continue;
12319
12320             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12321             case ImpossibleMove:
12322             case WhiteWins: // game ends here with these four
12323             case BlackWins:
12324             case GameIsDrawn:
12325             case GameUnfinished:
12326                 return -1;
12327
12328             case IllegalMove:
12329                 if(appData.testLegality) return -1;
12330             case WhiteCapturesEnPassant:
12331             case BlackCapturesEnPassant:
12332             case WhitePromotion:
12333             case BlackPromotion:
12334             case WhiteNonPromotion:
12335             case BlackNonPromotion:
12336             case NormalMove:
12337             case FirstLeg:
12338             case WhiteKingSideCastle:
12339             case WhiteQueenSideCastle:
12340             case BlackKingSideCastle:
12341             case BlackQueenSideCastle:
12342             case WhiteKingSideCastleWild:
12343             case WhiteQueenSideCastleWild:
12344             case BlackKingSideCastleWild:
12345             case BlackQueenSideCastleWild:
12346             case WhiteHSideCastleFR:
12347             case WhiteASideCastleFR:
12348             case BlackHSideCastleFR:
12349             case BlackASideCastleFR:
12350                 fromX = currentMoveString[0] - AAA;
12351                 fromY = currentMoveString[1] - ONE;
12352                 toX = currentMoveString[2] - AAA;
12353                 toY = currentMoveString[3] - ONE;
12354                 promoChar = currentMoveString[4];
12355                 break;
12356             case WhiteDrop:
12357             case BlackDrop:
12358                 fromX = next == WhiteDrop ?
12359                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12360                   (int) CharToPiece(ToLower(currentMoveString[0]));
12361                 fromY = DROP_RANK;
12362                 toX = currentMoveString[2] - AAA;
12363                 toY = currentMoveString[3] - ONE;
12364                 promoChar = 0;
12365                 break;
12366         }
12367         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12368         plyNr++;
12369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12370         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12371         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12372         if(appData.findMirror) {
12373             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12374             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12375         }
12376     }
12377 }
12378
12379 /* Load the nth game from open file f */
12380 int
12381 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12382 {
12383     ChessMove cm;
12384     char buf[MSG_SIZ];
12385     int gn = gameNumber;
12386     ListGame *lg = NULL;
12387     int numPGNTags = 0;
12388     int err, pos = -1;
12389     GameMode oldGameMode;
12390     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12391
12392     if (appData.debugMode)
12393         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12394
12395     if (gameMode == Training )
12396         SetTrainingModeOff();
12397
12398     oldGameMode = gameMode;
12399     if (gameMode != BeginningOfGame) {
12400       Reset(FALSE, TRUE);
12401     }
12402     killX = killY = -1; // [HGM] lion: in case we did not Reset
12403
12404     gameFileFP = f;
12405     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12406         fclose(lastLoadGameFP);
12407     }
12408
12409     if (useList) {
12410         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12411
12412         if (lg) {
12413             fseek(f, lg->offset, 0);
12414             GameListHighlight(gameNumber);
12415             pos = lg->position;
12416             gn = 1;
12417         }
12418         else {
12419             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12420               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12421             else
12422             DisplayError(_("Game number out of range"), 0);
12423             return FALSE;
12424         }
12425     } else {
12426         GameListDestroy();
12427         if (fseek(f, 0, 0) == -1) {
12428             if (f == lastLoadGameFP ?
12429                 gameNumber == lastLoadGameNumber + 1 :
12430                 gameNumber == 1) {
12431                 gn = 1;
12432             } else {
12433                 DisplayError(_("Can't seek on game file"), 0);
12434                 return FALSE;
12435             }
12436         }
12437     }
12438     lastLoadGameFP = f;
12439     lastLoadGameNumber = gameNumber;
12440     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12441     lastLoadGameUseList = useList;
12442
12443     yynewfile(f);
12444
12445     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12446       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12447                 lg->gameInfo.black);
12448             DisplayTitle(buf);
12449     } else if (*title != NULLCHAR) {
12450         if (gameNumber > 1) {
12451           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12452             DisplayTitle(buf);
12453         } else {
12454             DisplayTitle(title);
12455         }
12456     }
12457
12458     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12459         gameMode = PlayFromGameFile;
12460         ModeHighlight();
12461     }
12462
12463     currentMove = forwardMostMove = backwardMostMove = 0;
12464     CopyBoard(boards[0], initialPosition);
12465     StopClocks();
12466
12467     /*
12468      * Skip the first gn-1 games in the file.
12469      * Also skip over anything that precedes an identifiable
12470      * start of game marker, to avoid being confused by
12471      * garbage at the start of the file.  Currently
12472      * recognized start of game markers are the move number "1",
12473      * the pattern "gnuchess .* game", the pattern
12474      * "^[#;%] [^ ]* game file", and a PGN tag block.
12475      * A game that starts with one of the latter two patterns
12476      * will also have a move number 1, possibly
12477      * following a position diagram.
12478      * 5-4-02: Let's try being more lenient and allowing a game to
12479      * start with an unnumbered move.  Does that break anything?
12480      */
12481     cm = lastLoadGameStart = EndOfFile;
12482     while (gn > 0) {
12483         yyboardindex = forwardMostMove;
12484         cm = (ChessMove) Myylex();
12485         switch (cm) {
12486           case EndOfFile:
12487             if (cmailMsgLoaded) {
12488                 nCmailGames = CMAIL_MAX_GAMES - gn;
12489             } else {
12490                 Reset(TRUE, TRUE);
12491                 DisplayError(_("Game not found in file"), 0);
12492             }
12493             return FALSE;
12494
12495           case GNUChessGame:
12496           case XBoardGame:
12497             gn--;
12498             lastLoadGameStart = cm;
12499             break;
12500
12501           case MoveNumberOne:
12502             switch (lastLoadGameStart) {
12503               case GNUChessGame:
12504               case XBoardGame:
12505               case PGNTag:
12506                 break;
12507               case MoveNumberOne:
12508               case EndOfFile:
12509                 gn--;           /* count this game */
12510                 lastLoadGameStart = cm;
12511                 break;
12512               default:
12513                 /* impossible */
12514                 break;
12515             }
12516             break;
12517
12518           case PGNTag:
12519             switch (lastLoadGameStart) {
12520               case GNUChessGame:
12521               case PGNTag:
12522               case MoveNumberOne:
12523               case EndOfFile:
12524                 gn--;           /* count this game */
12525                 lastLoadGameStart = cm;
12526                 break;
12527               case XBoardGame:
12528                 lastLoadGameStart = cm; /* game counted already */
12529                 break;
12530               default:
12531                 /* impossible */
12532                 break;
12533             }
12534             if (gn > 0) {
12535                 do {
12536                     yyboardindex = forwardMostMove;
12537                     cm = (ChessMove) Myylex();
12538                 } while (cm == PGNTag || cm == Comment);
12539             }
12540             break;
12541
12542           case WhiteWins:
12543           case BlackWins:
12544           case GameIsDrawn:
12545             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12546                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12547                     != CMAIL_OLD_RESULT) {
12548                     nCmailResults ++ ;
12549                     cmailResult[  CMAIL_MAX_GAMES
12550                                 - gn - 1] = CMAIL_OLD_RESULT;
12551                 }
12552             }
12553             break;
12554
12555           case NormalMove:
12556           case FirstLeg:
12557             /* Only a NormalMove can be at the start of a game
12558              * without a position diagram. */
12559             if (lastLoadGameStart == EndOfFile ) {
12560               gn--;
12561               lastLoadGameStart = MoveNumberOne;
12562             }
12563             break;
12564
12565           default:
12566             break;
12567         }
12568     }
12569
12570     if (appData.debugMode)
12571       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12572
12573     if (cm == XBoardGame) {
12574         /* Skip any header junk before position diagram and/or move 1 */
12575         for (;;) {
12576             yyboardindex = forwardMostMove;
12577             cm = (ChessMove) Myylex();
12578
12579             if (cm == EndOfFile ||
12580                 cm == GNUChessGame || cm == XBoardGame) {
12581                 /* Empty game; pretend end-of-file and handle later */
12582                 cm = EndOfFile;
12583                 break;
12584             }
12585
12586             if (cm == MoveNumberOne || cm == PositionDiagram ||
12587                 cm == PGNTag || cm == Comment)
12588               break;
12589         }
12590     } else if (cm == GNUChessGame) {
12591         if (gameInfo.event != NULL) {
12592             free(gameInfo.event);
12593         }
12594         gameInfo.event = StrSave(yy_text);
12595     }
12596
12597     startedFromSetupPosition = FALSE;
12598     while (cm == PGNTag) {
12599         if (appData.debugMode)
12600           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12601         err = ParsePGNTag(yy_text, &gameInfo);
12602         if (!err) numPGNTags++;
12603
12604         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12605         if(gameInfo.variant != oldVariant) {
12606             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12607             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12608             InitPosition(TRUE);
12609             oldVariant = gameInfo.variant;
12610             if (appData.debugMode)
12611               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12612         }
12613
12614
12615         if (gameInfo.fen != NULL) {
12616           Board initial_position;
12617           startedFromSetupPosition = TRUE;
12618           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12619             Reset(TRUE, TRUE);
12620             DisplayError(_("Bad FEN position in file"), 0);
12621             return FALSE;
12622           }
12623           CopyBoard(boards[0], initial_position);
12624           if (blackPlaysFirst) {
12625             currentMove = forwardMostMove = backwardMostMove = 1;
12626             CopyBoard(boards[1], initial_position);
12627             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12628             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12629             timeRemaining[0][1] = whiteTimeRemaining;
12630             timeRemaining[1][1] = blackTimeRemaining;
12631             if (commentList[0] != NULL) {
12632               commentList[1] = commentList[0];
12633               commentList[0] = NULL;
12634             }
12635           } else {
12636             currentMove = forwardMostMove = backwardMostMove = 0;
12637           }
12638           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12639           {   int i;
12640               initialRulePlies = FENrulePlies;
12641               for( i=0; i< nrCastlingRights; i++ )
12642                   initialRights[i] = initial_position[CASTLING][i];
12643           }
12644           yyboardindex = forwardMostMove;
12645           free(gameInfo.fen);
12646           gameInfo.fen = NULL;
12647         }
12648
12649         yyboardindex = forwardMostMove;
12650         cm = (ChessMove) Myylex();
12651
12652         /* Handle comments interspersed among the tags */
12653         while (cm == Comment) {
12654             char *p;
12655             if (appData.debugMode)
12656               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12657             p = yy_text;
12658             AppendComment(currentMove, p, FALSE);
12659             yyboardindex = forwardMostMove;
12660             cm = (ChessMove) Myylex();
12661         }
12662     }
12663
12664     /* don't rely on existence of Event tag since if game was
12665      * pasted from clipboard the Event tag may not exist
12666      */
12667     if (numPGNTags > 0){
12668         char *tags;
12669         if (gameInfo.variant == VariantNormal) {
12670           VariantClass v = StringToVariant(gameInfo.event);
12671           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12672           if(v < VariantShogi) gameInfo.variant = v;
12673         }
12674         if (!matchMode) {
12675           if( appData.autoDisplayTags ) {
12676             tags = PGNTags(&gameInfo);
12677             TagsPopUp(tags, CmailMsg());
12678             free(tags);
12679           }
12680         }
12681     } else {
12682         /* Make something up, but don't display it now */
12683         SetGameInfo();
12684         TagsPopDown();
12685     }
12686
12687     if (cm == PositionDiagram) {
12688         int i, j;
12689         char *p;
12690         Board initial_position;
12691
12692         if (appData.debugMode)
12693           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12694
12695         if (!startedFromSetupPosition) {
12696             p = yy_text;
12697             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12698               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12699                 switch (*p) {
12700                   case '{':
12701                   case '[':
12702                   case '-':
12703                   case ' ':
12704                   case '\t':
12705                   case '\n':
12706                   case '\r':
12707                     break;
12708                   default:
12709                     initial_position[i][j++] = CharToPiece(*p);
12710                     break;
12711                 }
12712             while (*p == ' ' || *p == '\t' ||
12713                    *p == '\n' || *p == '\r') p++;
12714
12715             if (strncmp(p, "black", strlen("black"))==0)
12716               blackPlaysFirst = TRUE;
12717             else
12718               blackPlaysFirst = FALSE;
12719             startedFromSetupPosition = TRUE;
12720
12721             CopyBoard(boards[0], initial_position);
12722             if (blackPlaysFirst) {
12723                 currentMove = forwardMostMove = backwardMostMove = 1;
12724                 CopyBoard(boards[1], initial_position);
12725                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12726                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12727                 timeRemaining[0][1] = whiteTimeRemaining;
12728                 timeRemaining[1][1] = blackTimeRemaining;
12729                 if (commentList[0] != NULL) {
12730                     commentList[1] = commentList[0];
12731                     commentList[0] = NULL;
12732                 }
12733             } else {
12734                 currentMove = forwardMostMove = backwardMostMove = 0;
12735             }
12736         }
12737         yyboardindex = forwardMostMove;
12738         cm = (ChessMove) Myylex();
12739     }
12740
12741   if(!creatingBook) {
12742     if (first.pr == NoProc) {
12743         StartChessProgram(&first);
12744     }
12745     InitChessProgram(&first, FALSE);
12746     SendToProgram("force\n", &first);
12747     if (startedFromSetupPosition) {
12748         SendBoard(&first, forwardMostMove);
12749     if (appData.debugMode) {
12750         fprintf(debugFP, "Load Game\n");
12751     }
12752         DisplayBothClocks();
12753     }
12754   }
12755
12756     /* [HGM] server: flag to write setup moves in broadcast file as one */
12757     loadFlag = appData.suppressLoadMoves;
12758
12759     while (cm == Comment) {
12760         char *p;
12761         if (appData.debugMode)
12762           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12763         p = yy_text;
12764         AppendComment(currentMove, p, FALSE);
12765         yyboardindex = forwardMostMove;
12766         cm = (ChessMove) Myylex();
12767     }
12768
12769     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12770         cm == WhiteWins || cm == BlackWins ||
12771         cm == GameIsDrawn || cm == GameUnfinished) {
12772         DisplayMessage("", _("No moves in game"));
12773         if (cmailMsgLoaded) {
12774             if (appData.debugMode)
12775               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12776             ClearHighlights();
12777             flipView = FALSE;
12778         }
12779         DrawPosition(FALSE, boards[currentMove]);
12780         DisplayBothClocks();
12781         gameMode = EditGame;
12782         ModeHighlight();
12783         gameFileFP = NULL;
12784         cmailOldMove = 0;
12785         return TRUE;
12786     }
12787
12788     // [HGM] PV info: routine tests if comment empty
12789     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12790         DisplayComment(currentMove - 1, commentList[currentMove]);
12791     }
12792     if (!matchMode && appData.timeDelay != 0)
12793       DrawPosition(FALSE, boards[currentMove]);
12794
12795     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12796       programStats.ok_to_send = 1;
12797     }
12798
12799     /* if the first token after the PGN tags is a move
12800      * and not move number 1, retrieve it from the parser
12801      */
12802     if (cm != MoveNumberOne)
12803         LoadGameOneMove(cm);
12804
12805     /* load the remaining moves from the file */
12806     while (LoadGameOneMove(EndOfFile)) {
12807       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12808       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12809     }
12810
12811     /* rewind to the start of the game */
12812     currentMove = backwardMostMove;
12813
12814     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12815
12816     if (oldGameMode == AnalyzeFile) {
12817       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12818       AnalyzeFileEvent();
12819     } else
12820     if (oldGameMode == AnalyzeMode) {
12821       AnalyzeFileEvent();
12822     }
12823
12824     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12825         long int w, b; // [HGM] adjourn: restore saved clock times
12826         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12827         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12828             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12829             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12830         }
12831     }
12832
12833     if(creatingBook) return TRUE;
12834     if (!matchMode && pos > 0) {
12835         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12836     } else
12837     if (matchMode || appData.timeDelay == 0) {
12838       ToEndEvent();
12839     } else if (appData.timeDelay > 0) {
12840       AutoPlayGameLoop();
12841     }
12842
12843     if (appData.debugMode)
12844         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12845
12846     loadFlag = 0; /* [HGM] true game starts */
12847     return TRUE;
12848 }
12849
12850 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12851 int
12852 ReloadPosition (int offset)
12853 {
12854     int positionNumber = lastLoadPositionNumber + offset;
12855     if (lastLoadPositionFP == NULL) {
12856         DisplayError(_("No position has been loaded yet"), 0);
12857         return FALSE;
12858     }
12859     if (positionNumber <= 0) {
12860         DisplayError(_("Can't back up any further"), 0);
12861         return FALSE;
12862     }
12863     return LoadPosition(lastLoadPositionFP, positionNumber,
12864                         lastLoadPositionTitle);
12865 }
12866
12867 /* Load the nth position from the given file */
12868 int
12869 LoadPositionFromFile (char *filename, int n, char *title)
12870 {
12871     FILE *f;
12872     char buf[MSG_SIZ];
12873
12874     if (strcmp(filename, "-") == 0) {
12875         return LoadPosition(stdin, n, "stdin");
12876     } else {
12877         f = fopen(filename, "rb");
12878         if (f == NULL) {
12879             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12880             DisplayError(buf, errno);
12881             return FALSE;
12882         } else {
12883             return LoadPosition(f, n, title);
12884         }
12885     }
12886 }
12887
12888 /* Load the nth position from the given open file, and close it */
12889 int
12890 LoadPosition (FILE *f, int positionNumber, char *title)
12891 {
12892     char *p, line[MSG_SIZ];
12893     Board initial_position;
12894     int i, j, fenMode, pn;
12895
12896     if (gameMode == Training )
12897         SetTrainingModeOff();
12898
12899     if (gameMode != BeginningOfGame) {
12900         Reset(FALSE, TRUE);
12901     }
12902     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12903         fclose(lastLoadPositionFP);
12904     }
12905     if (positionNumber == 0) positionNumber = 1;
12906     lastLoadPositionFP = f;
12907     lastLoadPositionNumber = positionNumber;
12908     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12909     if (first.pr == NoProc && !appData.noChessProgram) {
12910       StartChessProgram(&first);
12911       InitChessProgram(&first, FALSE);
12912     }
12913     pn = positionNumber;
12914     if (positionNumber < 0) {
12915         /* Negative position number means to seek to that byte offset */
12916         if (fseek(f, -positionNumber, 0) == -1) {
12917             DisplayError(_("Can't seek on position file"), 0);
12918             return FALSE;
12919         };
12920         pn = 1;
12921     } else {
12922         if (fseek(f, 0, 0) == -1) {
12923             if (f == lastLoadPositionFP ?
12924                 positionNumber == lastLoadPositionNumber + 1 :
12925                 positionNumber == 1) {
12926                 pn = 1;
12927             } else {
12928                 DisplayError(_("Can't seek on position file"), 0);
12929                 return FALSE;
12930             }
12931         }
12932     }
12933     /* See if this file is FEN or old-style xboard */
12934     if (fgets(line, MSG_SIZ, f) == NULL) {
12935         DisplayError(_("Position not found in file"), 0);
12936         return FALSE;
12937     }
12938     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12939     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12940
12941     if (pn >= 2) {
12942         if (fenMode || line[0] == '#') pn--;
12943         while (pn > 0) {
12944             /* skip positions before number pn */
12945             if (fgets(line, MSG_SIZ, f) == NULL) {
12946                 Reset(TRUE, TRUE);
12947                 DisplayError(_("Position not found in file"), 0);
12948                 return FALSE;
12949             }
12950             if (fenMode || line[0] == '#') pn--;
12951         }
12952     }
12953
12954     if (fenMode) {
12955         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12956             DisplayError(_("Bad FEN position in file"), 0);
12957             return FALSE;
12958         }
12959     } else {
12960         (void) fgets(line, MSG_SIZ, f);
12961         (void) fgets(line, MSG_SIZ, f);
12962
12963         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12964             (void) fgets(line, MSG_SIZ, f);
12965             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12966                 if (*p == ' ')
12967                   continue;
12968                 initial_position[i][j++] = CharToPiece(*p);
12969             }
12970         }
12971
12972         blackPlaysFirst = FALSE;
12973         if (!feof(f)) {
12974             (void) fgets(line, MSG_SIZ, f);
12975             if (strncmp(line, "black", strlen("black"))==0)
12976               blackPlaysFirst = TRUE;
12977         }
12978     }
12979     startedFromSetupPosition = TRUE;
12980
12981     CopyBoard(boards[0], initial_position);
12982     if (blackPlaysFirst) {
12983         currentMove = forwardMostMove = backwardMostMove = 1;
12984         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12985         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12986         CopyBoard(boards[1], initial_position);
12987         DisplayMessage("", _("Black to play"));
12988     } else {
12989         currentMove = forwardMostMove = backwardMostMove = 0;
12990         DisplayMessage("", _("White to play"));
12991     }
12992     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12993     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12994         SendToProgram("force\n", &first);
12995         SendBoard(&first, forwardMostMove);
12996     }
12997     if (appData.debugMode) {
12998 int i, j;
12999   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13000   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13001         fprintf(debugFP, "Load Position\n");
13002     }
13003
13004     if (positionNumber > 1) {
13005       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13006         DisplayTitle(line);
13007     } else {
13008         DisplayTitle(title);
13009     }
13010     gameMode = EditGame;
13011     ModeHighlight();
13012     ResetClocks();
13013     timeRemaining[0][1] = whiteTimeRemaining;
13014     timeRemaining[1][1] = blackTimeRemaining;
13015     DrawPosition(FALSE, boards[currentMove]);
13016
13017     return TRUE;
13018 }
13019
13020
13021 void
13022 CopyPlayerNameIntoFileName (char **dest, char *src)
13023 {
13024     while (*src != NULLCHAR && *src != ',') {
13025         if (*src == ' ') {
13026             *(*dest)++ = '_';
13027             src++;
13028         } else {
13029             *(*dest)++ = *src++;
13030         }
13031     }
13032 }
13033
13034 char *
13035 DefaultFileName (char *ext)
13036 {
13037     static char def[MSG_SIZ];
13038     char *p;
13039
13040     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13041         p = def;
13042         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13043         *p++ = '-';
13044         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13045         *p++ = '.';
13046         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13047     } else {
13048         def[0] = NULLCHAR;
13049     }
13050     return def;
13051 }
13052
13053 /* Save the current game to the given file */
13054 int
13055 SaveGameToFile (char *filename, int append)
13056 {
13057     FILE *f;
13058     char buf[MSG_SIZ];
13059     int result, i, t,tot=0;
13060
13061     if (strcmp(filename, "-") == 0) {
13062         return SaveGame(stdout, 0, NULL);
13063     } else {
13064         for(i=0; i<10; i++) { // upto 10 tries
13065              f = fopen(filename, append ? "a" : "w");
13066              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13067              if(f || errno != 13) break;
13068              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13069              tot += t;
13070         }
13071         if (f == NULL) {
13072             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13073             DisplayError(buf, errno);
13074             return FALSE;
13075         } else {
13076             safeStrCpy(buf, lastMsg, MSG_SIZ);
13077             DisplayMessage(_("Waiting for access to save file"), "");
13078             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13079             DisplayMessage(_("Saving game"), "");
13080             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13081             result = SaveGame(f, 0, NULL);
13082             DisplayMessage(buf, "");
13083             return result;
13084         }
13085     }
13086 }
13087
13088 char *
13089 SavePart (char *str)
13090 {
13091     static char buf[MSG_SIZ];
13092     char *p;
13093
13094     p = strchr(str, ' ');
13095     if (p == NULL) return str;
13096     strncpy(buf, str, p - str);
13097     buf[p - str] = NULLCHAR;
13098     return buf;
13099 }
13100
13101 #define PGN_MAX_LINE 75
13102
13103 #define PGN_SIDE_WHITE  0
13104 #define PGN_SIDE_BLACK  1
13105
13106 static int
13107 FindFirstMoveOutOfBook (int side)
13108 {
13109     int result = -1;
13110
13111     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13112         int index = backwardMostMove;
13113         int has_book_hit = 0;
13114
13115         if( (index % 2) != side ) {
13116             index++;
13117         }
13118
13119         while( index < forwardMostMove ) {
13120             /* Check to see if engine is in book */
13121             int depth = pvInfoList[index].depth;
13122             int score = pvInfoList[index].score;
13123             int in_book = 0;
13124
13125             if( depth <= 2 ) {
13126                 in_book = 1;
13127             }
13128             else if( score == 0 && depth == 63 ) {
13129                 in_book = 1; /* Zappa */
13130             }
13131             else if( score == 2 && depth == 99 ) {
13132                 in_book = 1; /* Abrok */
13133             }
13134
13135             has_book_hit += in_book;
13136
13137             if( ! in_book ) {
13138                 result = index;
13139
13140                 break;
13141             }
13142
13143             index += 2;
13144         }
13145     }
13146
13147     return result;
13148 }
13149
13150 void
13151 GetOutOfBookInfo (char * buf)
13152 {
13153     int oob[2];
13154     int i;
13155     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13156
13157     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13158     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13159
13160     *buf = '\0';
13161
13162     if( oob[0] >= 0 || oob[1] >= 0 ) {
13163         for( i=0; i<2; i++ ) {
13164             int idx = oob[i];
13165
13166             if( idx >= 0 ) {
13167                 if( i > 0 && oob[0] >= 0 ) {
13168                     strcat( buf, "   " );
13169                 }
13170
13171                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13172                 sprintf( buf+strlen(buf), "%s%.2f",
13173                     pvInfoList[idx].score >= 0 ? "+" : "",
13174                     pvInfoList[idx].score / 100.0 );
13175             }
13176         }
13177     }
13178 }
13179
13180 /* Save game in PGN style and close the file */
13181 int
13182 SaveGamePGN (FILE *f)
13183 {
13184     int i, offset, linelen, newblock;
13185 //    char *movetext;
13186     char numtext[32];
13187     int movelen, numlen, blank;
13188     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13189
13190     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13191
13192     PrintPGNTags(f, &gameInfo);
13193
13194     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13195
13196     if (backwardMostMove > 0 || startedFromSetupPosition) {
13197         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13198         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13199         fprintf(f, "\n{--------------\n");
13200         PrintPosition(f, backwardMostMove);
13201         fprintf(f, "--------------}\n");
13202         free(fen);
13203     }
13204     else {
13205         /* [AS] Out of book annotation */
13206         if( appData.saveOutOfBookInfo ) {
13207             char buf[64];
13208
13209             GetOutOfBookInfo( buf );
13210
13211             if( buf[0] != '\0' ) {
13212                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13213             }
13214         }
13215
13216         fprintf(f, "\n");
13217     }
13218
13219     i = backwardMostMove;
13220     linelen = 0;
13221     newblock = TRUE;
13222
13223     while (i < forwardMostMove) {
13224         /* Print comments preceding this move */
13225         if (commentList[i] != NULL) {
13226             if (linelen > 0) fprintf(f, "\n");
13227             fprintf(f, "%s", commentList[i]);
13228             linelen = 0;
13229             newblock = TRUE;
13230         }
13231
13232         /* Format move number */
13233         if ((i % 2) == 0)
13234           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13235         else
13236           if (newblock)
13237             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13238           else
13239             numtext[0] = NULLCHAR;
13240
13241         numlen = strlen(numtext);
13242         newblock = FALSE;
13243
13244         /* Print move number */
13245         blank = linelen > 0 && numlen > 0;
13246         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13247             fprintf(f, "\n");
13248             linelen = 0;
13249             blank = 0;
13250         }
13251         if (blank) {
13252             fprintf(f, " ");
13253             linelen++;
13254         }
13255         fprintf(f, "%s", numtext);
13256         linelen += numlen;
13257
13258         /* Get move */
13259         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13260         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13261
13262         /* Print move */
13263         blank = linelen > 0 && movelen > 0;
13264         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13265             fprintf(f, "\n");
13266             linelen = 0;
13267             blank = 0;
13268         }
13269         if (blank) {
13270             fprintf(f, " ");
13271             linelen++;
13272         }
13273         fprintf(f, "%s", move_buffer);
13274         linelen += movelen;
13275
13276         /* [AS] Add PV info if present */
13277         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13278             /* [HGM] add time */
13279             char buf[MSG_SIZ]; int seconds;
13280
13281             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13282
13283             if( seconds <= 0)
13284               buf[0] = 0;
13285             else
13286               if( seconds < 30 )
13287                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13288               else
13289                 {
13290                   seconds = (seconds + 4)/10; // round to full seconds
13291                   if( seconds < 60 )
13292                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13293                   else
13294                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13295                 }
13296
13297             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13298                       pvInfoList[i].score >= 0 ? "+" : "",
13299                       pvInfoList[i].score / 100.0,
13300                       pvInfoList[i].depth,
13301                       buf );
13302
13303             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13304
13305             /* Print score/depth */
13306             blank = linelen > 0 && movelen > 0;
13307             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13308                 fprintf(f, "\n");
13309                 linelen = 0;
13310                 blank = 0;
13311             }
13312             if (blank) {
13313                 fprintf(f, " ");
13314                 linelen++;
13315             }
13316             fprintf(f, "%s", move_buffer);
13317             linelen += movelen;
13318         }
13319
13320         i++;
13321     }
13322
13323     /* Start a new line */
13324     if (linelen > 0) fprintf(f, "\n");
13325
13326     /* Print comments after last move */
13327     if (commentList[i] != NULL) {
13328         fprintf(f, "%s\n", commentList[i]);
13329     }
13330
13331     /* Print result */
13332     if (gameInfo.resultDetails != NULL &&
13333         gameInfo.resultDetails[0] != NULLCHAR) {
13334         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13335         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13336            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13337             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13338         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13339     } else {
13340         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13341     }
13342
13343     fclose(f);
13344     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13345     return TRUE;
13346 }
13347
13348 /* Save game in old style and close the file */
13349 int
13350 SaveGameOldStyle (FILE *f)
13351 {
13352     int i, offset;
13353     time_t tm;
13354
13355     tm = time((time_t *) NULL);
13356
13357     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13358     PrintOpponents(f);
13359
13360     if (backwardMostMove > 0 || startedFromSetupPosition) {
13361         fprintf(f, "\n[--------------\n");
13362         PrintPosition(f, backwardMostMove);
13363         fprintf(f, "--------------]\n");
13364     } else {
13365         fprintf(f, "\n");
13366     }
13367
13368     i = backwardMostMove;
13369     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13370
13371     while (i < forwardMostMove) {
13372         if (commentList[i] != NULL) {
13373             fprintf(f, "[%s]\n", commentList[i]);
13374         }
13375
13376         if ((i % 2) == 1) {
13377             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13378             i++;
13379         } else {
13380             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13381             i++;
13382             if (commentList[i] != NULL) {
13383                 fprintf(f, "\n");
13384                 continue;
13385             }
13386             if (i >= forwardMostMove) {
13387                 fprintf(f, "\n");
13388                 break;
13389             }
13390             fprintf(f, "%s\n", parseList[i]);
13391             i++;
13392         }
13393     }
13394
13395     if (commentList[i] != NULL) {
13396         fprintf(f, "[%s]\n", commentList[i]);
13397     }
13398
13399     /* This isn't really the old style, but it's close enough */
13400     if (gameInfo.resultDetails != NULL &&
13401         gameInfo.resultDetails[0] != NULLCHAR) {
13402         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13403                 gameInfo.resultDetails);
13404     } else {
13405         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13406     }
13407
13408     fclose(f);
13409     return TRUE;
13410 }
13411
13412 /* Save the current game to open file f and close the file */
13413 int
13414 SaveGame (FILE *f, int dummy, char *dummy2)
13415 {
13416     if (gameMode == EditPosition) EditPositionDone(TRUE);
13417     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13418     if (appData.oldSaveStyle)
13419       return SaveGameOldStyle(f);
13420     else
13421       return SaveGamePGN(f);
13422 }
13423
13424 /* Save the current position to the given file */
13425 int
13426 SavePositionToFile (char *filename)
13427 {
13428     FILE *f;
13429     char buf[MSG_SIZ];
13430
13431     if (strcmp(filename, "-") == 0) {
13432         return SavePosition(stdout, 0, NULL);
13433     } else {
13434         f = fopen(filename, "a");
13435         if (f == NULL) {
13436             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13437             DisplayError(buf, errno);
13438             return FALSE;
13439         } else {
13440             safeStrCpy(buf, lastMsg, MSG_SIZ);
13441             DisplayMessage(_("Waiting for access to save file"), "");
13442             flock(fileno(f), LOCK_EX); // [HGM] lock
13443             DisplayMessage(_("Saving position"), "");
13444             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13445             SavePosition(f, 0, NULL);
13446             DisplayMessage(buf, "");
13447             return TRUE;
13448         }
13449     }
13450 }
13451
13452 /* Save the current position to the given open file and close the file */
13453 int
13454 SavePosition (FILE *f, int dummy, char *dummy2)
13455 {
13456     time_t tm;
13457     char *fen;
13458
13459     if (gameMode == EditPosition) EditPositionDone(TRUE);
13460     if (appData.oldSaveStyle) {
13461         tm = time((time_t *) NULL);
13462
13463         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13464         PrintOpponents(f);
13465         fprintf(f, "[--------------\n");
13466         PrintPosition(f, currentMove);
13467         fprintf(f, "--------------]\n");
13468     } else {
13469         fen = PositionToFEN(currentMove, NULL, 1);
13470         fprintf(f, "%s\n", fen);
13471         free(fen);
13472     }
13473     fclose(f);
13474     return TRUE;
13475 }
13476
13477 void
13478 ReloadCmailMsgEvent (int unregister)
13479 {
13480 #if !WIN32
13481     static char *inFilename = NULL;
13482     static char *outFilename;
13483     int i;
13484     struct stat inbuf, outbuf;
13485     int status;
13486
13487     /* Any registered moves are unregistered if unregister is set, */
13488     /* i.e. invoked by the signal handler */
13489     if (unregister) {
13490         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13491             cmailMoveRegistered[i] = FALSE;
13492             if (cmailCommentList[i] != NULL) {
13493                 free(cmailCommentList[i]);
13494                 cmailCommentList[i] = NULL;
13495             }
13496         }
13497         nCmailMovesRegistered = 0;
13498     }
13499
13500     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13501         cmailResult[i] = CMAIL_NOT_RESULT;
13502     }
13503     nCmailResults = 0;
13504
13505     if (inFilename == NULL) {
13506         /* Because the filenames are static they only get malloced once  */
13507         /* and they never get freed                                      */
13508         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13509         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13510
13511         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13512         sprintf(outFilename, "%s.out", appData.cmailGameName);
13513     }
13514
13515     status = stat(outFilename, &outbuf);
13516     if (status < 0) {
13517         cmailMailedMove = FALSE;
13518     } else {
13519         status = stat(inFilename, &inbuf);
13520         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13521     }
13522
13523     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13524        counts the games, notes how each one terminated, etc.
13525
13526        It would be nice to remove this kludge and instead gather all
13527        the information while building the game list.  (And to keep it
13528        in the game list nodes instead of having a bunch of fixed-size
13529        parallel arrays.)  Note this will require getting each game's
13530        termination from the PGN tags, as the game list builder does
13531        not process the game moves.  --mann
13532        */
13533     cmailMsgLoaded = TRUE;
13534     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13535
13536     /* Load first game in the file or popup game menu */
13537     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13538
13539 #endif /* !WIN32 */
13540     return;
13541 }
13542
13543 int
13544 RegisterMove ()
13545 {
13546     FILE *f;
13547     char string[MSG_SIZ];
13548
13549     if (   cmailMailedMove
13550         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13551         return TRUE;            /* Allow free viewing  */
13552     }
13553
13554     /* Unregister move to ensure that we don't leave RegisterMove        */
13555     /* with the move registered when the conditions for registering no   */
13556     /* longer hold                                                       */
13557     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13558         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13559         nCmailMovesRegistered --;
13560
13561         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13562           {
13563               free(cmailCommentList[lastLoadGameNumber - 1]);
13564               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13565           }
13566     }
13567
13568     if (cmailOldMove == -1) {
13569         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13570         return FALSE;
13571     }
13572
13573     if (currentMove > cmailOldMove + 1) {
13574         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13575         return FALSE;
13576     }
13577
13578     if (currentMove < cmailOldMove) {
13579         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13580         return FALSE;
13581     }
13582
13583     if (forwardMostMove > currentMove) {
13584         /* Silently truncate extra moves */
13585         TruncateGame();
13586     }
13587
13588     if (   (currentMove == cmailOldMove + 1)
13589         || (   (currentMove == cmailOldMove)
13590             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13591                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13592         if (gameInfo.result != GameUnfinished) {
13593             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13594         }
13595
13596         if (commentList[currentMove] != NULL) {
13597             cmailCommentList[lastLoadGameNumber - 1]
13598               = StrSave(commentList[currentMove]);
13599         }
13600         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13601
13602         if (appData.debugMode)
13603           fprintf(debugFP, "Saving %s for game %d\n",
13604                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13605
13606         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13607
13608         f = fopen(string, "w");
13609         if (appData.oldSaveStyle) {
13610             SaveGameOldStyle(f); /* also closes the file */
13611
13612             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13613             f = fopen(string, "w");
13614             SavePosition(f, 0, NULL); /* also closes the file */
13615         } else {
13616             fprintf(f, "{--------------\n");
13617             PrintPosition(f, currentMove);
13618             fprintf(f, "--------------}\n\n");
13619
13620             SaveGame(f, 0, NULL); /* also closes the file*/
13621         }
13622
13623         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13624         nCmailMovesRegistered ++;
13625     } else if (nCmailGames == 1) {
13626         DisplayError(_("You have not made a move yet"), 0);
13627         return FALSE;
13628     }
13629
13630     return TRUE;
13631 }
13632
13633 void
13634 MailMoveEvent ()
13635 {
13636 #if !WIN32
13637     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13638     FILE *commandOutput;
13639     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13640     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13641     int nBuffers;
13642     int i;
13643     int archived;
13644     char *arcDir;
13645
13646     if (! cmailMsgLoaded) {
13647         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13648         return;
13649     }
13650
13651     if (nCmailGames == nCmailResults) {
13652         DisplayError(_("No unfinished games"), 0);
13653         return;
13654     }
13655
13656 #if CMAIL_PROHIBIT_REMAIL
13657     if (cmailMailedMove) {
13658       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);
13659         DisplayError(msg, 0);
13660         return;
13661     }
13662 #endif
13663
13664     if (! (cmailMailedMove || RegisterMove())) return;
13665
13666     if (   cmailMailedMove
13667         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13668       snprintf(string, MSG_SIZ, partCommandString,
13669                appData.debugMode ? " -v" : "", appData.cmailGameName);
13670         commandOutput = popen(string, "r");
13671
13672         if (commandOutput == NULL) {
13673             DisplayError(_("Failed to invoke cmail"), 0);
13674         } else {
13675             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13676                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13677             }
13678             if (nBuffers > 1) {
13679                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13680                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13681                 nBytes = MSG_SIZ - 1;
13682             } else {
13683                 (void) memcpy(msg, buffer, nBytes);
13684             }
13685             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13686
13687             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13688                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13689
13690                 archived = TRUE;
13691                 for (i = 0; i < nCmailGames; i ++) {
13692                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13693                         archived = FALSE;
13694                     }
13695                 }
13696                 if (   archived
13697                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13698                         != NULL)) {
13699                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13700                            arcDir,
13701                            appData.cmailGameName,
13702                            gameInfo.date);
13703                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13704                     cmailMsgLoaded = FALSE;
13705                 }
13706             }
13707
13708             DisplayInformation(msg);
13709             pclose(commandOutput);
13710         }
13711     } else {
13712         if ((*cmailMsg) != '\0') {
13713             DisplayInformation(cmailMsg);
13714         }
13715     }
13716
13717     return;
13718 #endif /* !WIN32 */
13719 }
13720
13721 char *
13722 CmailMsg ()
13723 {
13724 #if WIN32
13725     return NULL;
13726 #else
13727     int  prependComma = 0;
13728     char number[5];
13729     char string[MSG_SIZ];       /* Space for game-list */
13730     int  i;
13731
13732     if (!cmailMsgLoaded) return "";
13733
13734     if (cmailMailedMove) {
13735       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13736     } else {
13737         /* Create a list of games left */
13738       snprintf(string, MSG_SIZ, "[");
13739         for (i = 0; i < nCmailGames; i ++) {
13740             if (! (   cmailMoveRegistered[i]
13741                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13742                 if (prependComma) {
13743                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13744                 } else {
13745                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13746                     prependComma = 1;
13747                 }
13748
13749                 strcat(string, number);
13750             }
13751         }
13752         strcat(string, "]");
13753
13754         if (nCmailMovesRegistered + nCmailResults == 0) {
13755             switch (nCmailGames) {
13756               case 1:
13757                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13758                 break;
13759
13760               case 2:
13761                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13762                 break;
13763
13764               default:
13765                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13766                          nCmailGames);
13767                 break;
13768             }
13769         } else {
13770             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13771               case 1:
13772                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13773                          string);
13774                 break;
13775
13776               case 0:
13777                 if (nCmailResults == nCmailGames) {
13778                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13779                 } else {
13780                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13781                 }
13782                 break;
13783
13784               default:
13785                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13786                          string);
13787             }
13788         }
13789     }
13790     return cmailMsg;
13791 #endif /* WIN32 */
13792 }
13793
13794 void
13795 ResetGameEvent ()
13796 {
13797     if (gameMode == Training)
13798       SetTrainingModeOff();
13799
13800     Reset(TRUE, TRUE);
13801     cmailMsgLoaded = FALSE;
13802     if (appData.icsActive) {
13803       SendToICS(ics_prefix);
13804       SendToICS("refresh\n");
13805     }
13806 }
13807
13808 void
13809 ExitEvent (int status)
13810 {
13811     exiting++;
13812     if (exiting > 2) {
13813       /* Give up on clean exit */
13814       exit(status);
13815     }
13816     if (exiting > 1) {
13817       /* Keep trying for clean exit */
13818       return;
13819     }
13820
13821     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13822
13823     if (telnetISR != NULL) {
13824       RemoveInputSource(telnetISR);
13825     }
13826     if (icsPR != NoProc) {
13827       DestroyChildProcess(icsPR, TRUE);
13828     }
13829
13830     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13831     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13832
13833     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13834     /* make sure this other one finishes before killing it!                  */
13835     if(endingGame) { int count = 0;
13836         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13837         while(endingGame && count++ < 10) DoSleep(1);
13838         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13839     }
13840
13841     /* Kill off chess programs */
13842     if (first.pr != NoProc) {
13843         ExitAnalyzeMode();
13844
13845         DoSleep( appData.delayBeforeQuit );
13846         SendToProgram("quit\n", &first);
13847         DoSleep( appData.delayAfterQuit );
13848         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13849     }
13850     if (second.pr != NoProc) {
13851         DoSleep( appData.delayBeforeQuit );
13852         SendToProgram("quit\n", &second);
13853         DoSleep( appData.delayAfterQuit );
13854         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13855     }
13856     if (first.isr != NULL) {
13857         RemoveInputSource(first.isr);
13858     }
13859     if (second.isr != NULL) {
13860         RemoveInputSource(second.isr);
13861     }
13862
13863     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13864     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13865
13866     ShutDownFrontEnd();
13867     exit(status);
13868 }
13869
13870 void
13871 PauseEngine (ChessProgramState *cps)
13872 {
13873     SendToProgram("pause\n", cps);
13874     cps->pause = 2;
13875 }
13876
13877 void
13878 UnPauseEngine (ChessProgramState *cps)
13879 {
13880     SendToProgram("resume\n", cps);
13881     cps->pause = 1;
13882 }
13883
13884 void
13885 PauseEvent ()
13886 {
13887     if (appData.debugMode)
13888         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13889     if (pausing) {
13890         pausing = FALSE;
13891         ModeHighlight();
13892         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13893             StartClocks();
13894             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13895                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13896                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13897             }
13898             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13899             HandleMachineMove(stashedInputMove, stalledEngine);
13900             stalledEngine = NULL;
13901             return;
13902         }
13903         if (gameMode == MachinePlaysWhite ||
13904             gameMode == TwoMachinesPlay   ||
13905             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13906             if(first.pause)  UnPauseEngine(&first);
13907             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13908             if(second.pause) UnPauseEngine(&second);
13909             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13910             StartClocks();
13911         } else {
13912             DisplayBothClocks();
13913         }
13914         if (gameMode == PlayFromGameFile) {
13915             if (appData.timeDelay >= 0)
13916                 AutoPlayGameLoop();
13917         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13918             Reset(FALSE, TRUE);
13919             SendToICS(ics_prefix);
13920             SendToICS("refresh\n");
13921         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13922             ForwardInner(forwardMostMove);
13923         }
13924         pauseExamInvalid = FALSE;
13925     } else {
13926         switch (gameMode) {
13927           default:
13928             return;
13929           case IcsExamining:
13930             pauseExamForwardMostMove = forwardMostMove;
13931             pauseExamInvalid = FALSE;
13932             /* fall through */
13933           case IcsObserving:
13934           case IcsPlayingWhite:
13935           case IcsPlayingBlack:
13936             pausing = TRUE;
13937             ModeHighlight();
13938             return;
13939           case PlayFromGameFile:
13940             (void) StopLoadGameTimer();
13941             pausing = TRUE;
13942             ModeHighlight();
13943             break;
13944           case BeginningOfGame:
13945             if (appData.icsActive) return;
13946             /* else fall through */
13947           case MachinePlaysWhite:
13948           case MachinePlaysBlack:
13949           case TwoMachinesPlay:
13950             if (forwardMostMove == 0)
13951               return;           /* don't pause if no one has moved */
13952             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13953                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13954                 if(onMove->pause) {           // thinking engine can be paused
13955                     PauseEngine(onMove);      // do it
13956                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13957                         PauseEngine(onMove->other);
13958                     else
13959                         SendToProgram("easy\n", onMove->other);
13960                     StopClocks();
13961                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13962             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13963                 if(first.pause) {
13964                     PauseEngine(&first);
13965                     StopClocks();
13966                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13967             } else { // human on move, pause pondering by either method
13968                 if(first.pause)
13969                     PauseEngine(&first);
13970                 else if(appData.ponderNextMove)
13971                     SendToProgram("easy\n", &first);
13972                 StopClocks();
13973             }
13974             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13975           case AnalyzeMode:
13976             pausing = TRUE;
13977             ModeHighlight();
13978             break;
13979         }
13980     }
13981 }
13982
13983 void
13984 EditCommentEvent ()
13985 {
13986     char title[MSG_SIZ];
13987
13988     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13989       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13990     } else {
13991       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13992                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13993                parseList[currentMove - 1]);
13994     }
13995
13996     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13997 }
13998
13999
14000 void
14001 EditTagsEvent ()
14002 {
14003     char *tags = PGNTags(&gameInfo);
14004     bookUp = FALSE;
14005     EditTagsPopUp(tags, NULL);
14006     free(tags);
14007 }
14008
14009 void
14010 ToggleSecond ()
14011 {
14012   if(second.analyzing) {
14013     SendToProgram("exit\n", &second);
14014     second.analyzing = FALSE;
14015   } else {
14016     if (second.pr == NoProc) StartChessProgram(&second);
14017     InitChessProgram(&second, FALSE);
14018     FeedMovesToProgram(&second, currentMove);
14019
14020     SendToProgram("analyze\n", &second);
14021     second.analyzing = TRUE;
14022   }
14023 }
14024
14025 /* Toggle ShowThinking */
14026 void
14027 ToggleShowThinking()
14028 {
14029   appData.showThinking = !appData.showThinking;
14030   ShowThinkingEvent();
14031 }
14032
14033 int
14034 AnalyzeModeEvent ()
14035 {
14036     char buf[MSG_SIZ];
14037
14038     if (!first.analysisSupport) {
14039       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14040       DisplayError(buf, 0);
14041       return 0;
14042     }
14043     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14044     if (appData.icsActive) {
14045         if (gameMode != IcsObserving) {
14046           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14047             DisplayError(buf, 0);
14048             /* secure check */
14049             if (appData.icsEngineAnalyze) {
14050                 if (appData.debugMode)
14051                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14052                 ExitAnalyzeMode();
14053                 ModeHighlight();
14054             }
14055             return 0;
14056         }
14057         /* if enable, user wants to disable icsEngineAnalyze */
14058         if (appData.icsEngineAnalyze) {
14059                 ExitAnalyzeMode();
14060                 ModeHighlight();
14061                 return 0;
14062         }
14063         appData.icsEngineAnalyze = TRUE;
14064         if (appData.debugMode)
14065             fprintf(debugFP, "ICS engine analyze starting... \n");
14066     }
14067
14068     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14069     if (appData.noChessProgram || gameMode == AnalyzeMode)
14070       return 0;
14071
14072     if (gameMode != AnalyzeFile) {
14073         if (!appData.icsEngineAnalyze) {
14074                EditGameEvent();
14075                if (gameMode != EditGame) return 0;
14076         }
14077         if (!appData.showThinking) ToggleShowThinking();
14078         ResurrectChessProgram();
14079         SendToProgram("analyze\n", &first);
14080         first.analyzing = TRUE;
14081         /*first.maybeThinking = TRUE;*/
14082         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14083         EngineOutputPopUp();
14084     }
14085     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14086     pausing = FALSE;
14087     ModeHighlight();
14088     SetGameInfo();
14089
14090     StartAnalysisClock();
14091     GetTimeMark(&lastNodeCountTime);
14092     lastNodeCount = 0;
14093     return 1;
14094 }
14095
14096 void
14097 AnalyzeFileEvent ()
14098 {
14099     if (appData.noChessProgram || gameMode == AnalyzeFile)
14100       return;
14101
14102     if (!first.analysisSupport) {
14103       char buf[MSG_SIZ];
14104       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14105       DisplayError(buf, 0);
14106       return;
14107     }
14108
14109     if (gameMode != AnalyzeMode) {
14110         keepInfo = 1; // mere annotating should not alter PGN tags
14111         EditGameEvent();
14112         keepInfo = 0;
14113         if (gameMode != EditGame) return;
14114         if (!appData.showThinking) ToggleShowThinking();
14115         ResurrectChessProgram();
14116         SendToProgram("analyze\n", &first);
14117         first.analyzing = TRUE;
14118         /*first.maybeThinking = TRUE;*/
14119         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14120         EngineOutputPopUp();
14121     }
14122     gameMode = AnalyzeFile;
14123     pausing = FALSE;
14124     ModeHighlight();
14125
14126     StartAnalysisClock();
14127     GetTimeMark(&lastNodeCountTime);
14128     lastNodeCount = 0;
14129     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14130     AnalysisPeriodicEvent(1);
14131 }
14132
14133 void
14134 MachineWhiteEvent ()
14135 {
14136     char buf[MSG_SIZ];
14137     char *bookHit = NULL;
14138
14139     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14140       return;
14141
14142
14143     if (gameMode == PlayFromGameFile ||
14144         gameMode == TwoMachinesPlay  ||
14145         gameMode == Training         ||
14146         gameMode == AnalyzeMode      ||
14147         gameMode == EndOfGame)
14148         EditGameEvent();
14149
14150     if (gameMode == EditPosition)
14151         EditPositionDone(TRUE);
14152
14153     if (!WhiteOnMove(currentMove)) {
14154         DisplayError(_("It is not White's turn"), 0);
14155         return;
14156     }
14157
14158     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14159       ExitAnalyzeMode();
14160
14161     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14162         gameMode == AnalyzeFile)
14163         TruncateGame();
14164
14165     ResurrectChessProgram();    /* in case it isn't running */
14166     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14167         gameMode = MachinePlaysWhite;
14168         ResetClocks();
14169     } else
14170     gameMode = MachinePlaysWhite;
14171     pausing = FALSE;
14172     ModeHighlight();
14173     SetGameInfo();
14174     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14175     DisplayTitle(buf);
14176     if (first.sendName) {
14177       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14178       SendToProgram(buf, &first);
14179     }
14180     if (first.sendTime) {
14181       if (first.useColors) {
14182         SendToProgram("black\n", &first); /*gnu kludge*/
14183       }
14184       SendTimeRemaining(&first, TRUE);
14185     }
14186     if (first.useColors) {
14187       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14188     }
14189     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14190     SetMachineThinkingEnables();
14191     first.maybeThinking = TRUE;
14192     StartClocks();
14193     firstMove = FALSE;
14194
14195     if (appData.autoFlipView && !flipView) {
14196       flipView = !flipView;
14197       DrawPosition(FALSE, NULL);
14198       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14199     }
14200
14201     if(bookHit) { // [HGM] book: simulate book reply
14202         static char bookMove[MSG_SIZ]; // a bit generous?
14203
14204         programStats.nodes = programStats.depth = programStats.time =
14205         programStats.score = programStats.got_only_move = 0;
14206         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14207
14208         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14209         strcat(bookMove, bookHit);
14210         HandleMachineMove(bookMove, &first);
14211     }
14212 }
14213
14214 void
14215 MachineBlackEvent ()
14216 {
14217   char buf[MSG_SIZ];
14218   char *bookHit = NULL;
14219
14220     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14221         return;
14222
14223
14224     if (gameMode == PlayFromGameFile ||
14225         gameMode == TwoMachinesPlay  ||
14226         gameMode == Training         ||
14227         gameMode == AnalyzeMode      ||
14228         gameMode == EndOfGame)
14229         EditGameEvent();
14230
14231     if (gameMode == EditPosition)
14232         EditPositionDone(TRUE);
14233
14234     if (WhiteOnMove(currentMove)) {
14235         DisplayError(_("It is not Black's turn"), 0);
14236         return;
14237     }
14238
14239     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14240       ExitAnalyzeMode();
14241
14242     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14243         gameMode == AnalyzeFile)
14244         TruncateGame();
14245
14246     ResurrectChessProgram();    /* in case it isn't running */
14247     gameMode = MachinePlaysBlack;
14248     pausing = FALSE;
14249     ModeHighlight();
14250     SetGameInfo();
14251     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14252     DisplayTitle(buf);
14253     if (first.sendName) {
14254       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14255       SendToProgram(buf, &first);
14256     }
14257     if (first.sendTime) {
14258       if (first.useColors) {
14259         SendToProgram("white\n", &first); /*gnu kludge*/
14260       }
14261       SendTimeRemaining(&first, FALSE);
14262     }
14263     if (first.useColors) {
14264       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14265     }
14266     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14267     SetMachineThinkingEnables();
14268     first.maybeThinking = TRUE;
14269     StartClocks();
14270
14271     if (appData.autoFlipView && flipView) {
14272       flipView = !flipView;
14273       DrawPosition(FALSE, NULL);
14274       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14275     }
14276     if(bookHit) { // [HGM] book: simulate book reply
14277         static char bookMove[MSG_SIZ]; // a bit generous?
14278
14279         programStats.nodes = programStats.depth = programStats.time =
14280         programStats.score = programStats.got_only_move = 0;
14281         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14282
14283         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14284         strcat(bookMove, bookHit);
14285         HandleMachineMove(bookMove, &first);
14286     }
14287 }
14288
14289
14290 void
14291 DisplayTwoMachinesTitle ()
14292 {
14293     char buf[MSG_SIZ];
14294     if (appData.matchGames > 0) {
14295         if(appData.tourneyFile[0]) {
14296           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14297                    gameInfo.white, _("vs."), gameInfo.black,
14298                    nextGame+1, appData.matchGames+1,
14299                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14300         } else
14301         if (first.twoMachinesColor[0] == 'w') {
14302           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14303                    gameInfo.white, _("vs."),  gameInfo.black,
14304                    first.matchWins, second.matchWins,
14305                    matchGame - 1 - (first.matchWins + second.matchWins));
14306         } else {
14307           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14308                    gameInfo.white, _("vs."), gameInfo.black,
14309                    second.matchWins, first.matchWins,
14310                    matchGame - 1 - (first.matchWins + second.matchWins));
14311         }
14312     } else {
14313       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14314     }
14315     DisplayTitle(buf);
14316 }
14317
14318 void
14319 SettingsMenuIfReady ()
14320 {
14321   if (second.lastPing != second.lastPong) {
14322     DisplayMessage("", _("Waiting for second chess program"));
14323     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14324     return;
14325   }
14326   ThawUI();
14327   DisplayMessage("", "");
14328   SettingsPopUp(&second);
14329 }
14330
14331 int
14332 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14333 {
14334     char buf[MSG_SIZ];
14335     if (cps->pr == NoProc) {
14336         StartChessProgram(cps);
14337         if (cps->protocolVersion == 1) {
14338           retry();
14339           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14340         } else {
14341           /* kludge: allow timeout for initial "feature" command */
14342           if(retry != TwoMachinesEventIfReady) FreezeUI();
14343           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14344           DisplayMessage("", buf);
14345           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14346         }
14347         return 1;
14348     }
14349     return 0;
14350 }
14351
14352 void
14353 TwoMachinesEvent P((void))
14354 {
14355     int i;
14356     char buf[MSG_SIZ];
14357     ChessProgramState *onmove;
14358     char *bookHit = NULL;
14359     static int stalling = 0;
14360     TimeMark now;
14361     long wait;
14362
14363     if (appData.noChessProgram) return;
14364
14365     switch (gameMode) {
14366       case TwoMachinesPlay:
14367         return;
14368       case MachinePlaysWhite:
14369       case MachinePlaysBlack:
14370         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14371             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14372             return;
14373         }
14374         /* fall through */
14375       case BeginningOfGame:
14376       case PlayFromGameFile:
14377       case EndOfGame:
14378         EditGameEvent();
14379         if (gameMode != EditGame) return;
14380         break;
14381       case EditPosition:
14382         EditPositionDone(TRUE);
14383         break;
14384       case AnalyzeMode:
14385       case AnalyzeFile:
14386         ExitAnalyzeMode();
14387         break;
14388       case EditGame:
14389       default:
14390         break;
14391     }
14392
14393 //    forwardMostMove = currentMove;
14394     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14395     startingEngine = TRUE;
14396
14397     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14398
14399     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14400     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14401       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14402       return;
14403     }
14404     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14405
14406     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14407                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14408         startingEngine = FALSE;
14409         DisplayError("second engine does not play this", 0);
14410         return;
14411     }
14412
14413     if(!stalling) {
14414       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14415       SendToProgram("force\n", &second);
14416       stalling = 1;
14417       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14418       return;
14419     }
14420     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14421     if(appData.matchPause>10000 || appData.matchPause<10)
14422                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14423     wait = SubtractTimeMarks(&now, &pauseStart);
14424     if(wait < appData.matchPause) {
14425         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14426         return;
14427     }
14428     // we are now committed to starting the game
14429     stalling = 0;
14430     DisplayMessage("", "");
14431     if (startedFromSetupPosition) {
14432         SendBoard(&second, backwardMostMove);
14433     if (appData.debugMode) {
14434         fprintf(debugFP, "Two Machines\n");
14435     }
14436     }
14437     for (i = backwardMostMove; i < forwardMostMove; i++) {
14438         SendMoveToProgram(i, &second);
14439     }
14440
14441     gameMode = TwoMachinesPlay;
14442     pausing = startingEngine = FALSE;
14443     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14444     SetGameInfo();
14445     DisplayTwoMachinesTitle();
14446     firstMove = TRUE;
14447     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14448         onmove = &first;
14449     } else {
14450         onmove = &second;
14451     }
14452     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14453     SendToProgram(first.computerString, &first);
14454     if (first.sendName) {
14455       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14456       SendToProgram(buf, &first);
14457     }
14458     SendToProgram(second.computerString, &second);
14459     if (second.sendName) {
14460       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14461       SendToProgram(buf, &second);
14462     }
14463
14464     ResetClocks();
14465     if (!first.sendTime || !second.sendTime) {
14466         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14467         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14468     }
14469     if (onmove->sendTime) {
14470       if (onmove->useColors) {
14471         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14472       }
14473       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14474     }
14475     if (onmove->useColors) {
14476       SendToProgram(onmove->twoMachinesColor, onmove);
14477     }
14478     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14479 //    SendToProgram("go\n", onmove);
14480     onmove->maybeThinking = TRUE;
14481     SetMachineThinkingEnables();
14482
14483     StartClocks();
14484
14485     if(bookHit) { // [HGM] book: simulate book reply
14486         static char bookMove[MSG_SIZ]; // a bit generous?
14487
14488         programStats.nodes = programStats.depth = programStats.time =
14489         programStats.score = programStats.got_only_move = 0;
14490         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14491
14492         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14493         strcat(bookMove, bookHit);
14494         savedMessage = bookMove; // args for deferred call
14495         savedState = onmove;
14496         ScheduleDelayedEvent(DeferredBookMove, 1);
14497     }
14498 }
14499
14500 void
14501 TrainingEvent ()
14502 {
14503     if (gameMode == Training) {
14504       SetTrainingModeOff();
14505       gameMode = PlayFromGameFile;
14506       DisplayMessage("", _("Training mode off"));
14507     } else {
14508       gameMode = Training;
14509       animateTraining = appData.animate;
14510
14511       /* make sure we are not already at the end of the game */
14512       if (currentMove < forwardMostMove) {
14513         SetTrainingModeOn();
14514         DisplayMessage("", _("Training mode on"));
14515       } else {
14516         gameMode = PlayFromGameFile;
14517         DisplayError(_("Already at end of game"), 0);
14518       }
14519     }
14520     ModeHighlight();
14521 }
14522
14523 void
14524 IcsClientEvent ()
14525 {
14526     if (!appData.icsActive) return;
14527     switch (gameMode) {
14528       case IcsPlayingWhite:
14529       case IcsPlayingBlack:
14530       case IcsObserving:
14531       case IcsIdle:
14532       case BeginningOfGame:
14533       case IcsExamining:
14534         return;
14535
14536       case EditGame:
14537         break;
14538
14539       case EditPosition:
14540         EditPositionDone(TRUE);
14541         break;
14542
14543       case AnalyzeMode:
14544       case AnalyzeFile:
14545         ExitAnalyzeMode();
14546         break;
14547
14548       default:
14549         EditGameEvent();
14550         break;
14551     }
14552
14553     gameMode = IcsIdle;
14554     ModeHighlight();
14555     return;
14556 }
14557
14558 void
14559 EditGameEvent ()
14560 {
14561     int i;
14562
14563     switch (gameMode) {
14564       case Training:
14565         SetTrainingModeOff();
14566         break;
14567       case MachinePlaysWhite:
14568       case MachinePlaysBlack:
14569       case BeginningOfGame:
14570         SendToProgram("force\n", &first);
14571         SetUserThinkingEnables();
14572         break;
14573       case PlayFromGameFile:
14574         (void) StopLoadGameTimer();
14575         if (gameFileFP != NULL) {
14576             gameFileFP = NULL;
14577         }
14578         break;
14579       case EditPosition:
14580         EditPositionDone(TRUE);
14581         break;
14582       case AnalyzeMode:
14583       case AnalyzeFile:
14584         ExitAnalyzeMode();
14585         SendToProgram("force\n", &first);
14586         break;
14587       case TwoMachinesPlay:
14588         GameEnds(EndOfFile, NULL, GE_PLAYER);
14589         ResurrectChessProgram();
14590         SetUserThinkingEnables();
14591         break;
14592       case EndOfGame:
14593         ResurrectChessProgram();
14594         break;
14595       case IcsPlayingBlack:
14596       case IcsPlayingWhite:
14597         DisplayError(_("Warning: You are still playing a game"), 0);
14598         break;
14599       case IcsObserving:
14600         DisplayError(_("Warning: You are still observing a game"), 0);
14601         break;
14602       case IcsExamining:
14603         DisplayError(_("Warning: You are still examining a game"), 0);
14604         break;
14605       case IcsIdle:
14606         break;
14607       case EditGame:
14608       default:
14609         return;
14610     }
14611
14612     pausing = FALSE;
14613     StopClocks();
14614     first.offeredDraw = second.offeredDraw = 0;
14615
14616     if (gameMode == PlayFromGameFile) {
14617         whiteTimeRemaining = timeRemaining[0][currentMove];
14618         blackTimeRemaining = timeRemaining[1][currentMove];
14619         DisplayTitle("");
14620     }
14621
14622     if (gameMode == MachinePlaysWhite ||
14623         gameMode == MachinePlaysBlack ||
14624         gameMode == TwoMachinesPlay ||
14625         gameMode == EndOfGame) {
14626         i = forwardMostMove;
14627         while (i > currentMove) {
14628             SendToProgram("undo\n", &first);
14629             i--;
14630         }
14631         if(!adjustedClock) {
14632         whiteTimeRemaining = timeRemaining[0][currentMove];
14633         blackTimeRemaining = timeRemaining[1][currentMove];
14634         DisplayBothClocks();
14635         }
14636         if (whiteFlag || blackFlag) {
14637             whiteFlag = blackFlag = 0;
14638         }
14639         DisplayTitle("");
14640     }
14641
14642     gameMode = EditGame;
14643     ModeHighlight();
14644     SetGameInfo();
14645 }
14646
14647
14648 void
14649 EditPositionEvent ()
14650 {
14651     if (gameMode == EditPosition) {
14652         EditGameEvent();
14653         return;
14654     }
14655
14656     EditGameEvent();
14657     if (gameMode != EditGame) return;
14658
14659     gameMode = EditPosition;
14660     ModeHighlight();
14661     SetGameInfo();
14662     if (currentMove > 0)
14663       CopyBoard(boards[0], boards[currentMove]);
14664
14665     blackPlaysFirst = !WhiteOnMove(currentMove);
14666     ResetClocks();
14667     currentMove = forwardMostMove = backwardMostMove = 0;
14668     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14669     DisplayMove(-1);
14670     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14671 }
14672
14673 void
14674 ExitAnalyzeMode ()
14675 {
14676     /* [DM] icsEngineAnalyze - possible call from other functions */
14677     if (appData.icsEngineAnalyze) {
14678         appData.icsEngineAnalyze = FALSE;
14679
14680         DisplayMessage("",_("Close ICS engine analyze..."));
14681     }
14682     if (first.analysisSupport && first.analyzing) {
14683       SendToBoth("exit\n");
14684       first.analyzing = second.analyzing = FALSE;
14685     }
14686     thinkOutput[0] = NULLCHAR;
14687 }
14688
14689 void
14690 EditPositionDone (Boolean fakeRights)
14691 {
14692     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14693
14694     startedFromSetupPosition = TRUE;
14695     InitChessProgram(&first, FALSE);
14696     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14697       boards[0][EP_STATUS] = EP_NONE;
14698       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14699       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14700         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14701         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14702       } else boards[0][CASTLING][2] = NoRights;
14703       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14704         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14705         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14706       } else boards[0][CASTLING][5] = NoRights;
14707       if(gameInfo.variant == VariantSChess) {
14708         int i;
14709         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14710           boards[0][VIRGIN][i] = 0;
14711           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14712           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14713         }
14714       }
14715     }
14716     SendToProgram("force\n", &first);
14717     if (blackPlaysFirst) {
14718         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14719         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14720         currentMove = forwardMostMove = backwardMostMove = 1;
14721         CopyBoard(boards[1], boards[0]);
14722     } else {
14723         currentMove = forwardMostMove = backwardMostMove = 0;
14724     }
14725     SendBoard(&first, forwardMostMove);
14726     if (appData.debugMode) {
14727         fprintf(debugFP, "EditPosDone\n");
14728     }
14729     DisplayTitle("");
14730     DisplayMessage("", "");
14731     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14732     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14733     gameMode = EditGame;
14734     ModeHighlight();
14735     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14736     ClearHighlights(); /* [AS] */
14737 }
14738
14739 /* Pause for `ms' milliseconds */
14740 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14741 void
14742 TimeDelay (long ms)
14743 {
14744     TimeMark m1, m2;
14745
14746     GetTimeMark(&m1);
14747     do {
14748         GetTimeMark(&m2);
14749     } while (SubtractTimeMarks(&m2, &m1) < ms);
14750 }
14751
14752 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14753 void
14754 SendMultiLineToICS (char *buf)
14755 {
14756     char temp[MSG_SIZ+1], *p;
14757     int len;
14758
14759     len = strlen(buf);
14760     if (len > MSG_SIZ)
14761       len = MSG_SIZ;
14762
14763     strncpy(temp, buf, len);
14764     temp[len] = 0;
14765
14766     p = temp;
14767     while (*p) {
14768         if (*p == '\n' || *p == '\r')
14769           *p = ' ';
14770         ++p;
14771     }
14772
14773     strcat(temp, "\n");
14774     SendToICS(temp);
14775     SendToPlayer(temp, strlen(temp));
14776 }
14777
14778 void
14779 SetWhiteToPlayEvent ()
14780 {
14781     if (gameMode == EditPosition) {
14782         blackPlaysFirst = FALSE;
14783         DisplayBothClocks();    /* works because currentMove is 0 */
14784     } else if (gameMode == IcsExamining) {
14785         SendToICS(ics_prefix);
14786         SendToICS("tomove white\n");
14787     }
14788 }
14789
14790 void
14791 SetBlackToPlayEvent ()
14792 {
14793     if (gameMode == EditPosition) {
14794         blackPlaysFirst = TRUE;
14795         currentMove = 1;        /* kludge */
14796         DisplayBothClocks();
14797         currentMove = 0;
14798     } else if (gameMode == IcsExamining) {
14799         SendToICS(ics_prefix);
14800         SendToICS("tomove black\n");
14801     }
14802 }
14803
14804 void
14805 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14806 {
14807     char buf[MSG_SIZ];
14808     ChessSquare piece = boards[0][y][x];
14809     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14810     static int lastVariant;
14811
14812     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14813
14814     switch (selection) {
14815       case ClearBoard:
14816         CopyBoard(currentBoard, boards[0]);
14817         CopyBoard(menuBoard, initialPosition);
14818         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14819             SendToICS(ics_prefix);
14820             SendToICS("bsetup clear\n");
14821         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14822             SendToICS(ics_prefix);
14823             SendToICS("clearboard\n");
14824         } else {
14825             int nonEmpty = 0;
14826             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14827                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14828                 for (y = 0; y < BOARD_HEIGHT; y++) {
14829                     if (gameMode == IcsExamining) {
14830                         if (boards[currentMove][y][x] != EmptySquare) {
14831                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14832                                     AAA + x, ONE + y);
14833                             SendToICS(buf);
14834                         }
14835                     } else {
14836                         if(boards[0][y][x] != p) nonEmpty++;
14837                         boards[0][y][x] = p;
14838                     }
14839                 }
14840                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14841             }
14842             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14843                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14844                     ChessSquare p = menuBoard[0][x];
14845                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14846                     p = menuBoard[BOARD_HEIGHT-1][x];
14847                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14848                 }
14849                 DisplayMessage("Clicking clock again restores position", "");
14850                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14851                 if(!nonEmpty) { // asked to clear an empty board
14852                     CopyBoard(boards[0], menuBoard);
14853                 } else
14854                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14855                     CopyBoard(boards[0], initialPosition);
14856                 } else
14857                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14858                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14859                     CopyBoard(boards[0], erasedBoard);
14860                 } else
14861                     CopyBoard(erasedBoard, currentBoard);
14862
14863             }
14864         }
14865         if (gameMode == EditPosition) {
14866             DrawPosition(FALSE, boards[0]);
14867         }
14868         break;
14869
14870       case WhitePlay:
14871         SetWhiteToPlayEvent();
14872         break;
14873
14874       case BlackPlay:
14875         SetBlackToPlayEvent();
14876         break;
14877
14878       case EmptySquare:
14879         if (gameMode == IcsExamining) {
14880             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14881             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14882             SendToICS(buf);
14883         } else {
14884             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14885                 if(x == BOARD_LEFT-2) {
14886                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14887                     boards[0][y][1] = 0;
14888                 } else
14889                 if(x == BOARD_RGHT+1) {
14890                     if(y >= gameInfo.holdingsSize) break;
14891                     boards[0][y][BOARD_WIDTH-2] = 0;
14892                 } else break;
14893             }
14894             boards[0][y][x] = EmptySquare;
14895             DrawPosition(FALSE, boards[0]);
14896         }
14897         break;
14898
14899       case PromotePiece:
14900         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14901            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14902             selection = (ChessSquare) (PROMOTED piece);
14903         } else if(piece == EmptySquare) selection = WhiteSilver;
14904         else selection = (ChessSquare)((int)piece - 1);
14905         goto defaultlabel;
14906
14907       case DemotePiece:
14908         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14909            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14910             selection = (ChessSquare) (DEMOTED piece);
14911         } else if(piece == EmptySquare) selection = BlackSilver;
14912         else selection = (ChessSquare)((int)piece + 1);
14913         goto defaultlabel;
14914
14915       case WhiteQueen:
14916       case BlackQueen:
14917         if(gameInfo.variant == VariantShatranj ||
14918            gameInfo.variant == VariantXiangqi  ||
14919            gameInfo.variant == VariantCourier  ||
14920            gameInfo.variant == VariantASEAN    ||
14921            gameInfo.variant == VariantMakruk     )
14922             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14923         goto defaultlabel;
14924
14925       case WhiteKing:
14926       case BlackKing:
14927         if(gameInfo.variant == VariantXiangqi)
14928             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14929         if(gameInfo.variant == VariantKnightmate)
14930             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14931       default:
14932         defaultlabel:
14933         if (gameMode == IcsExamining) {
14934             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14935             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14936                      PieceToChar(selection), AAA + x, ONE + y);
14937             SendToICS(buf);
14938         } else {
14939             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14940                 int n;
14941                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14942                     n = PieceToNumber(selection - BlackPawn);
14943                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14944                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14945                     boards[0][BOARD_HEIGHT-1-n][1]++;
14946                 } else
14947                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14948                     n = PieceToNumber(selection);
14949                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14950                     boards[0][n][BOARD_WIDTH-1] = selection;
14951                     boards[0][n][BOARD_WIDTH-2]++;
14952                 }
14953             } else
14954             boards[0][y][x] = selection;
14955             DrawPosition(TRUE, boards[0]);
14956             ClearHighlights();
14957             fromX = fromY = -1;
14958         }
14959         break;
14960     }
14961 }
14962
14963
14964 void
14965 DropMenuEvent (ChessSquare selection, int x, int y)
14966 {
14967     ChessMove moveType;
14968
14969     switch (gameMode) {
14970       case IcsPlayingWhite:
14971       case MachinePlaysBlack:
14972         if (!WhiteOnMove(currentMove)) {
14973             DisplayMoveError(_("It is Black's turn"));
14974             return;
14975         }
14976         moveType = WhiteDrop;
14977         break;
14978       case IcsPlayingBlack:
14979       case MachinePlaysWhite:
14980         if (WhiteOnMove(currentMove)) {
14981             DisplayMoveError(_("It is White's turn"));
14982             return;
14983         }
14984         moveType = BlackDrop;
14985         break;
14986       case EditGame:
14987         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14988         break;
14989       default:
14990         return;
14991     }
14992
14993     if (moveType == BlackDrop && selection < BlackPawn) {
14994       selection = (ChessSquare) ((int) selection
14995                                  + (int) BlackPawn - (int) WhitePawn);
14996     }
14997     if (boards[currentMove][y][x] != EmptySquare) {
14998         DisplayMoveError(_("That square is occupied"));
14999         return;
15000     }
15001
15002     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15003 }
15004
15005 void
15006 AcceptEvent ()
15007 {
15008     /* Accept a pending offer of any kind from opponent */
15009
15010     if (appData.icsActive) {
15011         SendToICS(ics_prefix);
15012         SendToICS("accept\n");
15013     } else if (cmailMsgLoaded) {
15014         if (currentMove == cmailOldMove &&
15015             commentList[cmailOldMove] != NULL &&
15016             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15017                    "Black offers a draw" : "White offers a draw")) {
15018             TruncateGame();
15019             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15020             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15021         } else {
15022             DisplayError(_("There is no pending offer on this move"), 0);
15023             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15024         }
15025     } else {
15026         /* Not used for offers from chess program */
15027     }
15028 }
15029
15030 void
15031 DeclineEvent ()
15032 {
15033     /* Decline a pending offer of any kind from opponent */
15034
15035     if (appData.icsActive) {
15036         SendToICS(ics_prefix);
15037         SendToICS("decline\n");
15038     } else if (cmailMsgLoaded) {
15039         if (currentMove == cmailOldMove &&
15040             commentList[cmailOldMove] != NULL &&
15041             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15042                    "Black offers a draw" : "White offers a draw")) {
15043 #ifdef NOTDEF
15044             AppendComment(cmailOldMove, "Draw declined", TRUE);
15045             DisplayComment(cmailOldMove - 1, "Draw declined");
15046 #endif /*NOTDEF*/
15047         } else {
15048             DisplayError(_("There is no pending offer on this move"), 0);
15049         }
15050     } else {
15051         /* Not used for offers from chess program */
15052     }
15053 }
15054
15055 void
15056 RematchEvent ()
15057 {
15058     /* Issue ICS rematch command */
15059     if (appData.icsActive) {
15060         SendToICS(ics_prefix);
15061         SendToICS("rematch\n");
15062     }
15063 }
15064
15065 void
15066 CallFlagEvent ()
15067 {
15068     /* Call your opponent's flag (claim a win on time) */
15069     if (appData.icsActive) {
15070         SendToICS(ics_prefix);
15071         SendToICS("flag\n");
15072     } else {
15073         switch (gameMode) {
15074           default:
15075             return;
15076           case MachinePlaysWhite:
15077             if (whiteFlag) {
15078                 if (blackFlag)
15079                   GameEnds(GameIsDrawn, "Both players ran out of time",
15080                            GE_PLAYER);
15081                 else
15082                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15083             } else {
15084                 DisplayError(_("Your opponent is not out of time"), 0);
15085             }
15086             break;
15087           case MachinePlaysBlack:
15088             if (blackFlag) {
15089                 if (whiteFlag)
15090                   GameEnds(GameIsDrawn, "Both players ran out of time",
15091                            GE_PLAYER);
15092                 else
15093                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15094             } else {
15095                 DisplayError(_("Your opponent is not out of time"), 0);
15096             }
15097             break;
15098         }
15099     }
15100 }
15101
15102 void
15103 ClockClick (int which)
15104 {       // [HGM] code moved to back-end from winboard.c
15105         if(which) { // black clock
15106           if (gameMode == EditPosition || gameMode == IcsExamining) {
15107             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15108             SetBlackToPlayEvent();
15109           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15110           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15111           } else if (shiftKey) {
15112             AdjustClock(which, -1);
15113           } else if (gameMode == IcsPlayingWhite ||
15114                      gameMode == MachinePlaysBlack) {
15115             CallFlagEvent();
15116           }
15117         } else { // white clock
15118           if (gameMode == EditPosition || gameMode == IcsExamining) {
15119             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15120             SetWhiteToPlayEvent();
15121           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15122           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15123           } else if (shiftKey) {
15124             AdjustClock(which, -1);
15125           } else if (gameMode == IcsPlayingBlack ||
15126                    gameMode == MachinePlaysWhite) {
15127             CallFlagEvent();
15128           }
15129         }
15130 }
15131
15132 void
15133 DrawEvent ()
15134 {
15135     /* Offer draw or accept pending draw offer from opponent */
15136
15137     if (appData.icsActive) {
15138         /* Note: tournament rules require draw offers to be
15139            made after you make your move but before you punch
15140            your clock.  Currently ICS doesn't let you do that;
15141            instead, you immediately punch your clock after making
15142            a move, but you can offer a draw at any time. */
15143
15144         SendToICS(ics_prefix);
15145         SendToICS("draw\n");
15146         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15147     } else if (cmailMsgLoaded) {
15148         if (currentMove == cmailOldMove &&
15149             commentList[cmailOldMove] != NULL &&
15150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15151                    "Black offers a draw" : "White offers a draw")) {
15152             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15153             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15154         } else if (currentMove == cmailOldMove + 1) {
15155             char *offer = WhiteOnMove(cmailOldMove) ?
15156               "White offers a draw" : "Black offers a draw";
15157             AppendComment(currentMove, offer, TRUE);
15158             DisplayComment(currentMove - 1, offer);
15159             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15160         } else {
15161             DisplayError(_("You must make your move before offering a draw"), 0);
15162             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15163         }
15164     } else if (first.offeredDraw) {
15165         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15166     } else {
15167         if (first.sendDrawOffers) {
15168             SendToProgram("draw\n", &first);
15169             userOfferedDraw = TRUE;
15170         }
15171     }
15172 }
15173
15174 void
15175 AdjournEvent ()
15176 {
15177     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15178
15179     if (appData.icsActive) {
15180         SendToICS(ics_prefix);
15181         SendToICS("adjourn\n");
15182     } else {
15183         /* Currently GNU Chess doesn't offer or accept Adjourns */
15184     }
15185 }
15186
15187
15188 void
15189 AbortEvent ()
15190 {
15191     /* Offer Abort or accept pending Abort offer from opponent */
15192
15193     if (appData.icsActive) {
15194         SendToICS(ics_prefix);
15195         SendToICS("abort\n");
15196     } else {
15197         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15198     }
15199 }
15200
15201 void
15202 ResignEvent ()
15203 {
15204     /* Resign.  You can do this even if it's not your turn. */
15205
15206     if (appData.icsActive) {
15207         SendToICS(ics_prefix);
15208         SendToICS("resign\n");
15209     } else {
15210         switch (gameMode) {
15211           case MachinePlaysWhite:
15212             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15213             break;
15214           case MachinePlaysBlack:
15215             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15216             break;
15217           case EditGame:
15218             if (cmailMsgLoaded) {
15219                 TruncateGame();
15220                 if (WhiteOnMove(cmailOldMove)) {
15221                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15222                 } else {
15223                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15224                 }
15225                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15226             }
15227             break;
15228           default:
15229             break;
15230         }
15231     }
15232 }
15233
15234
15235 void
15236 StopObservingEvent ()
15237 {
15238     /* Stop observing current games */
15239     SendToICS(ics_prefix);
15240     SendToICS("unobserve\n");
15241 }
15242
15243 void
15244 StopExaminingEvent ()
15245 {
15246     /* Stop observing current game */
15247     SendToICS(ics_prefix);
15248     SendToICS("unexamine\n");
15249 }
15250
15251 void
15252 ForwardInner (int target)
15253 {
15254     int limit; int oldSeekGraphUp = seekGraphUp;
15255
15256     if (appData.debugMode)
15257         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15258                 target, currentMove, forwardMostMove);
15259
15260     if (gameMode == EditPosition)
15261       return;
15262
15263     seekGraphUp = FALSE;
15264     MarkTargetSquares(1);
15265
15266     if (gameMode == PlayFromGameFile && !pausing)
15267       PauseEvent();
15268
15269     if (gameMode == IcsExamining && pausing)
15270       limit = pauseExamForwardMostMove;
15271     else
15272       limit = forwardMostMove;
15273
15274     if (target > limit) target = limit;
15275
15276     if (target > 0 && moveList[target - 1][0]) {
15277         int fromX, fromY, toX, toY;
15278         toX = moveList[target - 1][2] - AAA;
15279         toY = moveList[target - 1][3] - ONE;
15280         if (moveList[target - 1][1] == '@') {
15281             if (appData.highlightLastMove) {
15282                 SetHighlights(-1, -1, toX, toY);
15283             }
15284         } else {
15285             fromX = moveList[target - 1][0] - AAA;
15286             fromY = moveList[target - 1][1] - ONE;
15287             if (target == currentMove + 1) {
15288                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15289             }
15290             if (appData.highlightLastMove) {
15291                 SetHighlights(fromX, fromY, toX, toY);
15292             }
15293         }
15294     }
15295     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15296         gameMode == Training || gameMode == PlayFromGameFile ||
15297         gameMode == AnalyzeFile) {
15298         while (currentMove < target) {
15299             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15300             SendMoveToProgram(currentMove++, &first);
15301         }
15302     } else {
15303         currentMove = target;
15304     }
15305
15306     if (gameMode == EditGame || gameMode == EndOfGame) {
15307         whiteTimeRemaining = timeRemaining[0][currentMove];
15308         blackTimeRemaining = timeRemaining[1][currentMove];
15309     }
15310     DisplayBothClocks();
15311     DisplayMove(currentMove - 1);
15312     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15313     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15314     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15315         DisplayComment(currentMove - 1, commentList[currentMove]);
15316     }
15317     ClearMap(); // [HGM] exclude: invalidate map
15318 }
15319
15320
15321 void
15322 ForwardEvent ()
15323 {
15324     if (gameMode == IcsExamining && !pausing) {
15325         SendToICS(ics_prefix);
15326         SendToICS("forward\n");
15327     } else {
15328         ForwardInner(currentMove + 1);
15329     }
15330 }
15331
15332 void
15333 ToEndEvent ()
15334 {
15335     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15336         /* to optimze, we temporarily turn off analysis mode while we feed
15337          * the remaining moves to the engine. Otherwise we get analysis output
15338          * after each move.
15339          */
15340         if (first.analysisSupport) {
15341           SendToProgram("exit\nforce\n", &first);
15342           first.analyzing = FALSE;
15343         }
15344     }
15345
15346     if (gameMode == IcsExamining && !pausing) {
15347         SendToICS(ics_prefix);
15348         SendToICS("forward 999999\n");
15349     } else {
15350         ForwardInner(forwardMostMove);
15351     }
15352
15353     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15354         /* we have fed all the moves, so reactivate analysis mode */
15355         SendToProgram("analyze\n", &first);
15356         first.analyzing = TRUE;
15357         /*first.maybeThinking = TRUE;*/
15358         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15359     }
15360 }
15361
15362 void
15363 BackwardInner (int target)
15364 {
15365     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15366
15367     if (appData.debugMode)
15368         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15369                 target, currentMove, forwardMostMove);
15370
15371     if (gameMode == EditPosition) return;
15372     seekGraphUp = FALSE;
15373     MarkTargetSquares(1);
15374     if (currentMove <= backwardMostMove) {
15375         ClearHighlights();
15376         DrawPosition(full_redraw, boards[currentMove]);
15377         return;
15378     }
15379     if (gameMode == PlayFromGameFile && !pausing)
15380       PauseEvent();
15381
15382     if (moveList[target][0]) {
15383         int fromX, fromY, toX, toY;
15384         toX = moveList[target][2] - AAA;
15385         toY = moveList[target][3] - ONE;
15386         if (moveList[target][1] == '@') {
15387             if (appData.highlightLastMove) {
15388                 SetHighlights(-1, -1, toX, toY);
15389             }
15390         } else {
15391             fromX = moveList[target][0] - AAA;
15392             fromY = moveList[target][1] - ONE;
15393             if (target == currentMove - 1) {
15394                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15395             }
15396             if (appData.highlightLastMove) {
15397                 SetHighlights(fromX, fromY, toX, toY);
15398             }
15399         }
15400     }
15401     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15402         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15403         while (currentMove > target) {
15404             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15405                 // null move cannot be undone. Reload program with move history before it.
15406                 int i;
15407                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15408                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15409                 }
15410                 SendBoard(&first, i);
15411               if(second.analyzing) SendBoard(&second, i);
15412                 for(currentMove=i; currentMove<target; currentMove++) {
15413                     SendMoveToProgram(currentMove, &first);
15414                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15415                 }
15416                 break;
15417             }
15418             SendToBoth("undo\n");
15419             currentMove--;
15420         }
15421     } else {
15422         currentMove = target;
15423     }
15424
15425     if (gameMode == EditGame || gameMode == EndOfGame) {
15426         whiteTimeRemaining = timeRemaining[0][currentMove];
15427         blackTimeRemaining = timeRemaining[1][currentMove];
15428     }
15429     DisplayBothClocks();
15430     DisplayMove(currentMove - 1);
15431     DrawPosition(full_redraw, boards[currentMove]);
15432     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15433     // [HGM] PV info: routine tests if comment empty
15434     DisplayComment(currentMove - 1, commentList[currentMove]);
15435     ClearMap(); // [HGM] exclude: invalidate map
15436 }
15437
15438 void
15439 BackwardEvent ()
15440 {
15441     if (gameMode == IcsExamining && !pausing) {
15442         SendToICS(ics_prefix);
15443         SendToICS("backward\n");
15444     } else {
15445         BackwardInner(currentMove - 1);
15446     }
15447 }
15448
15449 void
15450 ToStartEvent ()
15451 {
15452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15453         /* to optimize, we temporarily turn off analysis mode while we undo
15454          * all the moves. Otherwise we get analysis output after each undo.
15455          */
15456         if (first.analysisSupport) {
15457           SendToProgram("exit\nforce\n", &first);
15458           first.analyzing = FALSE;
15459         }
15460     }
15461
15462     if (gameMode == IcsExamining && !pausing) {
15463         SendToICS(ics_prefix);
15464         SendToICS("backward 999999\n");
15465     } else {
15466         BackwardInner(backwardMostMove);
15467     }
15468
15469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15470         /* we have fed all the moves, so reactivate analysis mode */
15471         SendToProgram("analyze\n", &first);
15472         first.analyzing = TRUE;
15473         /*first.maybeThinking = TRUE;*/
15474         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15475     }
15476 }
15477
15478 void
15479 ToNrEvent (int to)
15480 {
15481   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15482   if (to >= forwardMostMove) to = forwardMostMove;
15483   if (to <= backwardMostMove) to = backwardMostMove;
15484   if (to < currentMove) {
15485     BackwardInner(to);
15486   } else {
15487     ForwardInner(to);
15488   }
15489 }
15490
15491 void
15492 RevertEvent (Boolean annotate)
15493 {
15494     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15495         return;
15496     }
15497     if (gameMode != IcsExamining) {
15498         DisplayError(_("You are not examining a game"), 0);
15499         return;
15500     }
15501     if (pausing) {
15502         DisplayError(_("You can't revert while pausing"), 0);
15503         return;
15504     }
15505     SendToICS(ics_prefix);
15506     SendToICS("revert\n");
15507 }
15508
15509 void
15510 RetractMoveEvent ()
15511 {
15512     switch (gameMode) {
15513       case MachinePlaysWhite:
15514       case MachinePlaysBlack:
15515         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15516             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15517             return;
15518         }
15519         if (forwardMostMove < 2) return;
15520         currentMove = forwardMostMove = forwardMostMove - 2;
15521         whiteTimeRemaining = timeRemaining[0][currentMove];
15522         blackTimeRemaining = timeRemaining[1][currentMove];
15523         DisplayBothClocks();
15524         DisplayMove(currentMove - 1);
15525         ClearHighlights();/*!! could figure this out*/
15526         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15527         SendToProgram("remove\n", &first);
15528         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15529         break;
15530
15531       case BeginningOfGame:
15532       default:
15533         break;
15534
15535       case IcsPlayingWhite:
15536       case IcsPlayingBlack:
15537         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15538             SendToICS(ics_prefix);
15539             SendToICS("takeback 2\n");
15540         } else {
15541             SendToICS(ics_prefix);
15542             SendToICS("takeback 1\n");
15543         }
15544         break;
15545     }
15546 }
15547
15548 void
15549 MoveNowEvent ()
15550 {
15551     ChessProgramState *cps;
15552
15553     switch (gameMode) {
15554       case MachinePlaysWhite:
15555         if (!WhiteOnMove(forwardMostMove)) {
15556             DisplayError(_("It is your turn"), 0);
15557             return;
15558         }
15559         cps = &first;
15560         break;
15561       case MachinePlaysBlack:
15562         if (WhiteOnMove(forwardMostMove)) {
15563             DisplayError(_("It is your turn"), 0);
15564             return;
15565         }
15566         cps = &first;
15567         break;
15568       case TwoMachinesPlay:
15569         if (WhiteOnMove(forwardMostMove) ==
15570             (first.twoMachinesColor[0] == 'w')) {
15571             cps = &first;
15572         } else {
15573             cps = &second;
15574         }
15575         break;
15576       case BeginningOfGame:
15577       default:
15578         return;
15579     }
15580     SendToProgram("?\n", cps);
15581 }
15582
15583 void
15584 TruncateGameEvent ()
15585 {
15586     EditGameEvent();
15587     if (gameMode != EditGame) return;
15588     TruncateGame();
15589 }
15590
15591 void
15592 TruncateGame ()
15593 {
15594     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15595     if (forwardMostMove > currentMove) {
15596         if (gameInfo.resultDetails != NULL) {
15597             free(gameInfo.resultDetails);
15598             gameInfo.resultDetails = NULL;
15599             gameInfo.result = GameUnfinished;
15600         }
15601         forwardMostMove = currentMove;
15602         HistorySet(parseList, backwardMostMove, forwardMostMove,
15603                    currentMove-1);
15604     }
15605 }
15606
15607 void
15608 HintEvent ()
15609 {
15610     if (appData.noChessProgram) return;
15611     switch (gameMode) {
15612       case MachinePlaysWhite:
15613         if (WhiteOnMove(forwardMostMove)) {
15614             DisplayError(_("Wait until your turn."), 0);
15615             return;
15616         }
15617         break;
15618       case BeginningOfGame:
15619       case MachinePlaysBlack:
15620         if (!WhiteOnMove(forwardMostMove)) {
15621             DisplayError(_("Wait until your turn."), 0);
15622             return;
15623         }
15624         break;
15625       default:
15626         DisplayError(_("No hint available"), 0);
15627         return;
15628     }
15629     SendToProgram("hint\n", &first);
15630     hintRequested = TRUE;
15631 }
15632
15633 void
15634 CreateBookEvent ()
15635 {
15636     ListGame * lg = (ListGame *) gameList.head;
15637     FILE *f, *g;
15638     int nItem;
15639     static int secondTime = FALSE;
15640
15641     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15642         DisplayError(_("Game list not loaded or empty"), 0);
15643         return;
15644     }
15645
15646     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15647         fclose(g);
15648         secondTime++;
15649         DisplayNote(_("Book file exists! Try again for overwrite."));
15650         return;
15651     }
15652
15653     creatingBook = TRUE;
15654     secondTime = FALSE;
15655
15656     /* Get list size */
15657     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15658         LoadGame(f, nItem, "", TRUE);
15659         AddGameToBook(TRUE);
15660         lg = (ListGame *) lg->node.succ;
15661     }
15662
15663     creatingBook = FALSE;
15664     FlushBook();
15665 }
15666
15667 void
15668 BookEvent ()
15669 {
15670     if (appData.noChessProgram) return;
15671     switch (gameMode) {
15672       case MachinePlaysWhite:
15673         if (WhiteOnMove(forwardMostMove)) {
15674             DisplayError(_("Wait until your turn."), 0);
15675             return;
15676         }
15677         break;
15678       case BeginningOfGame:
15679       case MachinePlaysBlack:
15680         if (!WhiteOnMove(forwardMostMove)) {
15681             DisplayError(_("Wait until your turn."), 0);
15682             return;
15683         }
15684         break;
15685       case EditPosition:
15686         EditPositionDone(TRUE);
15687         break;
15688       case TwoMachinesPlay:
15689         return;
15690       default:
15691         break;
15692     }
15693     SendToProgram("bk\n", &first);
15694     bookOutput[0] = NULLCHAR;
15695     bookRequested = TRUE;
15696 }
15697
15698 void
15699 AboutGameEvent ()
15700 {
15701     char *tags = PGNTags(&gameInfo);
15702     TagsPopUp(tags, CmailMsg());
15703     free(tags);
15704 }
15705
15706 /* end button procedures */
15707
15708 void
15709 PrintPosition (FILE *fp, int move)
15710 {
15711     int i, j;
15712
15713     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15714         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15715             char c = PieceToChar(boards[move][i][j]);
15716             fputc(c == 'x' ? '.' : c, fp);
15717             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15718         }
15719     }
15720     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15721       fprintf(fp, "white to play\n");
15722     else
15723       fprintf(fp, "black to play\n");
15724 }
15725
15726 void
15727 PrintOpponents (FILE *fp)
15728 {
15729     if (gameInfo.white != NULL) {
15730         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15731     } else {
15732         fprintf(fp, "\n");
15733     }
15734 }
15735
15736 /* Find last component of program's own name, using some heuristics */
15737 void
15738 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15739 {
15740     char *p, *q, c;
15741     int local = (strcmp(host, "localhost") == 0);
15742     while (!local && (p = strchr(prog, ';')) != NULL) {
15743         p++;
15744         while (*p == ' ') p++;
15745         prog = p;
15746     }
15747     if (*prog == '"' || *prog == '\'') {
15748         q = strchr(prog + 1, *prog);
15749     } else {
15750         q = strchr(prog, ' ');
15751     }
15752     if (q == NULL) q = prog + strlen(prog);
15753     p = q;
15754     while (p >= prog && *p != '/' && *p != '\\') p--;
15755     p++;
15756     if(p == prog && *p == '"') p++;
15757     c = *q; *q = 0;
15758     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15759     memcpy(buf, p, q - p);
15760     buf[q - p] = NULLCHAR;
15761     if (!local) {
15762         strcat(buf, "@");
15763         strcat(buf, host);
15764     }
15765 }
15766
15767 char *
15768 TimeControlTagValue ()
15769 {
15770     char buf[MSG_SIZ];
15771     if (!appData.clockMode) {
15772       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15773     } else if (movesPerSession > 0) {
15774       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15775     } else if (timeIncrement == 0) {
15776       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15777     } else {
15778       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15779     }
15780     return StrSave(buf);
15781 }
15782
15783 void
15784 SetGameInfo ()
15785 {
15786     /* This routine is used only for certain modes */
15787     VariantClass v = gameInfo.variant;
15788     ChessMove r = GameUnfinished;
15789     char *p = NULL;
15790
15791     if(keepInfo) return;
15792
15793     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15794         r = gameInfo.result;
15795         p = gameInfo.resultDetails;
15796         gameInfo.resultDetails = NULL;
15797     }
15798     ClearGameInfo(&gameInfo);
15799     gameInfo.variant = v;
15800
15801     switch (gameMode) {
15802       case MachinePlaysWhite:
15803         gameInfo.event = StrSave( appData.pgnEventHeader );
15804         gameInfo.site = StrSave(HostName());
15805         gameInfo.date = PGNDate();
15806         gameInfo.round = StrSave("-");
15807         gameInfo.white = StrSave(first.tidy);
15808         gameInfo.black = StrSave(UserName());
15809         gameInfo.timeControl = TimeControlTagValue();
15810         break;
15811
15812       case MachinePlaysBlack:
15813         gameInfo.event = StrSave( appData.pgnEventHeader );
15814         gameInfo.site = StrSave(HostName());
15815         gameInfo.date = PGNDate();
15816         gameInfo.round = StrSave("-");
15817         gameInfo.white = StrSave(UserName());
15818         gameInfo.black = StrSave(first.tidy);
15819         gameInfo.timeControl = TimeControlTagValue();
15820         break;
15821
15822       case TwoMachinesPlay:
15823         gameInfo.event = StrSave( appData.pgnEventHeader );
15824         gameInfo.site = StrSave(HostName());
15825         gameInfo.date = PGNDate();
15826         if (roundNr > 0) {
15827             char buf[MSG_SIZ];
15828             snprintf(buf, MSG_SIZ, "%d", roundNr);
15829             gameInfo.round = StrSave(buf);
15830         } else {
15831             gameInfo.round = StrSave("-");
15832         }
15833         if (first.twoMachinesColor[0] == 'w') {
15834             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15835             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15836         } else {
15837             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15838             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15839         }
15840         gameInfo.timeControl = TimeControlTagValue();
15841         break;
15842
15843       case EditGame:
15844         gameInfo.event = StrSave("Edited game");
15845         gameInfo.site = StrSave(HostName());
15846         gameInfo.date = PGNDate();
15847         gameInfo.round = StrSave("-");
15848         gameInfo.white = StrSave("-");
15849         gameInfo.black = StrSave("-");
15850         gameInfo.result = r;
15851         gameInfo.resultDetails = p;
15852         break;
15853
15854       case EditPosition:
15855         gameInfo.event = StrSave("Edited position");
15856         gameInfo.site = StrSave(HostName());
15857         gameInfo.date = PGNDate();
15858         gameInfo.round = StrSave("-");
15859         gameInfo.white = StrSave("-");
15860         gameInfo.black = StrSave("-");
15861         break;
15862
15863       case IcsPlayingWhite:
15864       case IcsPlayingBlack:
15865       case IcsObserving:
15866       case IcsExamining:
15867         break;
15868
15869       case PlayFromGameFile:
15870         gameInfo.event = StrSave("Game from non-PGN file");
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       default:
15879         break;
15880     }
15881 }
15882
15883 void
15884 ReplaceComment (int index, char *text)
15885 {
15886     int len;
15887     char *p;
15888     float score;
15889
15890     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15891        pvInfoList[index-1].depth == len &&
15892        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15893        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15894     while (*text == '\n') text++;
15895     len = strlen(text);
15896     while (len > 0 && text[len - 1] == '\n') len--;
15897
15898     if (commentList[index] != NULL)
15899       free(commentList[index]);
15900
15901     if (len == 0) {
15902         commentList[index] = NULL;
15903         return;
15904     }
15905   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15906       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15907       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15908     commentList[index] = (char *) malloc(len + 2);
15909     strncpy(commentList[index], text, len);
15910     commentList[index][len] = '\n';
15911     commentList[index][len + 1] = NULLCHAR;
15912   } else {
15913     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15914     char *p;
15915     commentList[index] = (char *) malloc(len + 7);
15916     safeStrCpy(commentList[index], "{\n", 3);
15917     safeStrCpy(commentList[index]+2, text, len+1);
15918     commentList[index][len+2] = NULLCHAR;
15919     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15920     strcat(commentList[index], "\n}\n");
15921   }
15922 }
15923
15924 void
15925 CrushCRs (char *text)
15926 {
15927   char *p = text;
15928   char *q = text;
15929   char ch;
15930
15931   do {
15932     ch = *p++;
15933     if (ch == '\r') continue;
15934     *q++ = ch;
15935   } while (ch != '\0');
15936 }
15937
15938 void
15939 AppendComment (int index, char *text, Boolean addBraces)
15940 /* addBraces  tells if we should add {} */
15941 {
15942     int oldlen, len;
15943     char *old;
15944
15945 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15946     if(addBraces == 3) addBraces = 0; else // force appending literally
15947     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15948
15949     CrushCRs(text);
15950     while (*text == '\n') text++;
15951     len = strlen(text);
15952     while (len > 0 && text[len - 1] == '\n') len--;
15953     text[len] = NULLCHAR;
15954
15955     if (len == 0) return;
15956
15957     if (commentList[index] != NULL) {
15958       Boolean addClosingBrace = addBraces;
15959         old = commentList[index];
15960         oldlen = strlen(old);
15961         while(commentList[index][oldlen-1] ==  '\n')
15962           commentList[index][--oldlen] = NULLCHAR;
15963         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15964         safeStrCpy(commentList[index], old, oldlen + len + 6);
15965         free(old);
15966         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15967         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15968           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15969           while (*text == '\n') { text++; len--; }
15970           commentList[index][--oldlen] = NULLCHAR;
15971       }
15972         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15973         else          strcat(commentList[index], "\n");
15974         strcat(commentList[index], text);
15975         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15976         else          strcat(commentList[index], "\n");
15977     } else {
15978         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15979         if(addBraces)
15980           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15981         else commentList[index][0] = NULLCHAR;
15982         strcat(commentList[index], text);
15983         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15984         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15985     }
15986 }
15987
15988 static char *
15989 FindStr (char * text, char * sub_text)
15990 {
15991     char * result = strstr( text, sub_text );
15992
15993     if( result != NULL ) {
15994         result += strlen( sub_text );
15995     }
15996
15997     return result;
15998 }
15999
16000 /* [AS] Try to extract PV info from PGN comment */
16001 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16002 char *
16003 GetInfoFromComment (int index, char * text)
16004 {
16005     char * sep = text, *p;
16006
16007     if( text != NULL && index > 0 ) {
16008         int score = 0;
16009         int depth = 0;
16010         int time = -1, sec = 0, deci;
16011         char * s_eval = FindStr( text, "[%eval " );
16012         char * s_emt = FindStr( text, "[%emt " );
16013 #if 0
16014         if( s_eval != NULL || s_emt != NULL ) {
16015 #else
16016         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16017 #endif
16018             /* New style */
16019             char delim;
16020
16021             if( s_eval != NULL ) {
16022                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16023                     return text;
16024                 }
16025
16026                 if( delim != ']' ) {
16027                     return text;
16028                 }
16029             }
16030
16031             if( s_emt != NULL ) {
16032             }
16033                 return text;
16034         }
16035         else {
16036             /* We expect something like: [+|-]nnn.nn/dd */
16037             int score_lo = 0;
16038
16039             if(*text != '{') return text; // [HGM] braces: must be normal comment
16040
16041             sep = strchr( text, '/' );
16042             if( sep == NULL || sep < (text+4) ) {
16043                 return text;
16044             }
16045
16046             p = text;
16047             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16048             if(p[1] == '(') { // comment starts with PV
16049                p = strchr(p, ')'); // locate end of PV
16050                if(p == NULL || sep < p+5) return text;
16051                // at this point we have something like "{(.*) +0.23/6 ..."
16052                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16053                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16054                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16055             }
16056             time = -1; sec = -1; deci = -1;
16057             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16058                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16059                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16060                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16061                 return text;
16062             }
16063
16064             if( score_lo < 0 || score_lo >= 100 ) {
16065                 return text;
16066             }
16067
16068             if(sec >= 0) time = 600*time + 10*sec; else
16069             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16070
16071             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16072
16073             /* [HGM] PV time: now locate end of PV info */
16074             while( *++sep >= '0' && *sep <= '9'); // strip depth
16075             if(time >= 0)
16076             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16077             if(sec >= 0)
16078             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16079             if(deci >= 0)
16080             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16081             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16082         }
16083
16084         if( depth <= 0 ) {
16085             return text;
16086         }
16087
16088         if( time < 0 ) {
16089             time = -1;
16090         }
16091
16092         pvInfoList[index-1].depth = depth;
16093         pvInfoList[index-1].score = score;
16094         pvInfoList[index-1].time  = 10*time; // centi-sec
16095         if(*sep == '}') *sep = 0; else *--sep = '{';
16096         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16097     }
16098     return sep;
16099 }
16100
16101 void
16102 SendToProgram (char *message, ChessProgramState *cps)
16103 {
16104     int count, outCount, error;
16105     char buf[MSG_SIZ];
16106
16107     if (cps->pr == NoProc) return;
16108     Attention(cps);
16109
16110     if (appData.debugMode) {
16111         TimeMark now;
16112         GetTimeMark(&now);
16113         fprintf(debugFP, "%ld >%-6s: %s",
16114                 SubtractTimeMarks(&now, &programStartTime),
16115                 cps->which, message);
16116         if(serverFP)
16117             fprintf(serverFP, "%ld >%-6s: %s",
16118                 SubtractTimeMarks(&now, &programStartTime),
16119                 cps->which, message), fflush(serverFP);
16120     }
16121
16122     count = strlen(message);
16123     outCount = OutputToProcess(cps->pr, message, count, &error);
16124     if (outCount < count && !exiting
16125                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16126       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16127       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16128         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16129             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16130                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16131                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16132                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16133             } else {
16134                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16135                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16136                 gameInfo.result = res;
16137             }
16138             gameInfo.resultDetails = StrSave(buf);
16139         }
16140         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16141         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16142     }
16143 }
16144
16145 void
16146 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16147 {
16148     char *end_str;
16149     char buf[MSG_SIZ];
16150     ChessProgramState *cps = (ChessProgramState *)closure;
16151
16152     if (isr != cps->isr) return; /* Killed intentionally */
16153     if (count <= 0) {
16154         if (count == 0) {
16155             RemoveInputSource(cps->isr);
16156             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16157                     _(cps->which), cps->program);
16158             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16159             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16160                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16161                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16162                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16163                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16164                 } else {
16165                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16166                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16167                     gameInfo.result = res;
16168                 }
16169                 gameInfo.resultDetails = StrSave(buf);
16170             }
16171             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16172             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16173         } else {
16174             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16175                     _(cps->which), cps->program);
16176             RemoveInputSource(cps->isr);
16177
16178             /* [AS] Program is misbehaving badly... kill it */
16179             if( count == -2 ) {
16180                 DestroyChildProcess( cps->pr, 9 );
16181                 cps->pr = NoProc;
16182             }
16183
16184             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16185         }
16186         return;
16187     }
16188
16189     if ((end_str = strchr(message, '\r')) != NULL)
16190       *end_str = NULLCHAR;
16191     if ((end_str = strchr(message, '\n')) != NULL)
16192       *end_str = NULLCHAR;
16193
16194     if (appData.debugMode) {
16195         TimeMark now; int print = 1;
16196         char *quote = ""; char c; int i;
16197
16198         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16199                 char start = message[0];
16200                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16201                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16202                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16203                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16204                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16205                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16206                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16207                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16208                    sscanf(message, "hint: %c", &c)!=1 &&
16209                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16210                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16211                     print = (appData.engineComments >= 2);
16212                 }
16213                 message[0] = start; // restore original message
16214         }
16215         if(print) {
16216                 GetTimeMark(&now);
16217                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16218                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16219                         quote,
16220                         message);
16221                 if(serverFP)
16222                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16223                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16224                         quote,
16225                         message), fflush(serverFP);
16226         }
16227     }
16228
16229     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16230     if (appData.icsEngineAnalyze) {
16231         if (strstr(message, "whisper") != NULL ||
16232              strstr(message, "kibitz") != NULL ||
16233             strstr(message, "tellics") != NULL) return;
16234     }
16235
16236     HandleMachineMove(message, cps);
16237 }
16238
16239
16240 void
16241 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16242 {
16243     char buf[MSG_SIZ];
16244     int seconds;
16245
16246     if( timeControl_2 > 0 ) {
16247         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16248             tc = timeControl_2;
16249         }
16250     }
16251     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16252     inc /= cps->timeOdds;
16253     st  /= cps->timeOdds;
16254
16255     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16256
16257     if (st > 0) {
16258       /* Set exact time per move, normally using st command */
16259       if (cps->stKludge) {
16260         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16261         seconds = st % 60;
16262         if (seconds == 0) {
16263           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16264         } else {
16265           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16266         }
16267       } else {
16268         snprintf(buf, MSG_SIZ, "st %d\n", st);
16269       }
16270     } else {
16271       /* Set conventional or incremental time control, using level command */
16272       if (seconds == 0) {
16273         /* Note old gnuchess bug -- minutes:seconds used to not work.
16274            Fixed in later versions, but still avoid :seconds
16275            when seconds is 0. */
16276         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16277       } else {
16278         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16279                  seconds, inc/1000.);
16280       }
16281     }
16282     SendToProgram(buf, cps);
16283
16284     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16285     /* Orthogonally, limit search to given depth */
16286     if (sd > 0) {
16287       if (cps->sdKludge) {
16288         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16289       } else {
16290         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16291       }
16292       SendToProgram(buf, cps);
16293     }
16294
16295     if(cps->nps >= 0) { /* [HGM] nps */
16296         if(cps->supportsNPS == FALSE)
16297           cps->nps = -1; // don't use if engine explicitly says not supported!
16298         else {
16299           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16300           SendToProgram(buf, cps);
16301         }
16302     }
16303 }
16304
16305 ChessProgramState *
16306 WhitePlayer ()
16307 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16308 {
16309     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16310        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16311         return &second;
16312     return &first;
16313 }
16314
16315 void
16316 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16317 {
16318     char message[MSG_SIZ];
16319     long time, otime;
16320
16321     /* Note: this routine must be called when the clocks are stopped
16322        or when they have *just* been set or switched; otherwise
16323        it will be off by the time since the current tick started.
16324     */
16325     if (machineWhite) {
16326         time = whiteTimeRemaining / 10;
16327         otime = blackTimeRemaining / 10;
16328     } else {
16329         time = blackTimeRemaining / 10;
16330         otime = whiteTimeRemaining / 10;
16331     }
16332     /* [HGM] translate opponent's time by time-odds factor */
16333     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16334
16335     if (time <= 0) time = 1;
16336     if (otime <= 0) otime = 1;
16337
16338     snprintf(message, MSG_SIZ, "time %ld\n", time);
16339     SendToProgram(message, cps);
16340
16341     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16342     SendToProgram(message, cps);
16343 }
16344
16345 char *
16346 EngineDefinedVariant (ChessProgramState *cps, int n)
16347 {   // return name of n-th unknown variant that engine supports
16348     static char buf[MSG_SIZ];
16349     char *p, *s = cps->variants;
16350     if(!s) return NULL;
16351     do { // parse string from variants feature
16352       VariantClass v;
16353         p = strchr(s, ',');
16354         if(p) *p = NULLCHAR;
16355       v = StringToVariant(s);
16356       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16357         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16358             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16359         }
16360         if(p) *p++ = ',';
16361         if(n < 0) return buf;
16362     } while(s = p);
16363     return NULL;
16364 }
16365
16366 int
16367 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16368 {
16369   char buf[MSG_SIZ];
16370   int len = strlen(name);
16371   int val;
16372
16373   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16374     (*p) += len + 1;
16375     sscanf(*p, "%d", &val);
16376     *loc = (val != 0);
16377     while (**p && **p != ' ')
16378       (*p)++;
16379     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16380     SendToProgram(buf, cps);
16381     return TRUE;
16382   }
16383   return FALSE;
16384 }
16385
16386 int
16387 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16388 {
16389   char buf[MSG_SIZ];
16390   int len = strlen(name);
16391   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16392     (*p) += len + 1;
16393     sscanf(*p, "%d", loc);
16394     while (**p && **p != ' ') (*p)++;
16395     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16396     SendToProgram(buf, cps);
16397     return TRUE;
16398   }
16399   return FALSE;
16400 }
16401
16402 int
16403 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16404 {
16405   char buf[MSG_SIZ];
16406   int len = strlen(name);
16407   if (strncmp((*p), name, len) == 0
16408       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16409     (*p) += len + 2;
16410     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16411     sscanf(*p, "%[^\"]", *loc);
16412     while (**p && **p != '\"') (*p)++;
16413     if (**p == '\"') (*p)++;
16414     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16415     SendToProgram(buf, cps);
16416     return TRUE;
16417   }
16418   return FALSE;
16419 }
16420
16421 int
16422 ParseOption (Option *opt, ChessProgramState *cps)
16423 // [HGM] options: process the string that defines an engine option, and determine
16424 // name, type, default value, and allowed value range
16425 {
16426         char *p, *q, buf[MSG_SIZ];
16427         int n, min = (-1)<<31, max = 1<<31, def;
16428
16429         if(p = strstr(opt->name, " -spin ")) {
16430             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16431             if(max < min) max = min; // enforce consistency
16432             if(def < min) def = min;
16433             if(def > max) def = max;
16434             opt->value = def;
16435             opt->min = min;
16436             opt->max = max;
16437             opt->type = Spin;
16438         } else if((p = strstr(opt->name, " -slider "))) {
16439             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16440             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16441             if(max < min) max = min; // enforce consistency
16442             if(def < min) def = min;
16443             if(def > max) def = max;
16444             opt->value = def;
16445             opt->min = min;
16446             opt->max = max;
16447             opt->type = Spin; // Slider;
16448         } else if((p = strstr(opt->name, " -string "))) {
16449             opt->textValue = p+9;
16450             opt->type = TextBox;
16451         } else if((p = strstr(opt->name, " -file "))) {
16452             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16453             opt->textValue = p+7;
16454             opt->type = FileName; // FileName;
16455         } else if((p = strstr(opt->name, " -path "))) {
16456             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16457             opt->textValue = p+7;
16458             opt->type = PathName; // PathName;
16459         } else if(p = strstr(opt->name, " -check ")) {
16460             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16461             opt->value = (def != 0);
16462             opt->type = CheckBox;
16463         } else if(p = strstr(opt->name, " -combo ")) {
16464             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16465             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16466             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16467             opt->value = n = 0;
16468             while(q = StrStr(q, " /// ")) {
16469                 n++; *q = 0;    // count choices, and null-terminate each of them
16470                 q += 5;
16471                 if(*q == '*') { // remember default, which is marked with * prefix
16472                     q++;
16473                     opt->value = n;
16474                 }
16475                 cps->comboList[cps->comboCnt++] = q;
16476             }
16477             cps->comboList[cps->comboCnt++] = NULL;
16478             opt->max = n + 1;
16479             opt->type = ComboBox;
16480         } else if(p = strstr(opt->name, " -button")) {
16481             opt->type = Button;
16482         } else if(p = strstr(opt->name, " -save")) {
16483             opt->type = SaveButton;
16484         } else return FALSE;
16485         *p = 0; // terminate option name
16486         // now look if the command-line options define a setting for this engine option.
16487         if(cps->optionSettings && cps->optionSettings[0])
16488             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16489         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16490           snprintf(buf, MSG_SIZ, "option %s", p);
16491                 if(p = strstr(buf, ",")) *p = 0;
16492                 if(q = strchr(buf, '=')) switch(opt->type) {
16493                     case ComboBox:
16494                         for(n=0; n<opt->max; n++)
16495                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16496                         break;
16497                     case TextBox:
16498                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16499                         break;
16500                     case Spin:
16501                     case CheckBox:
16502                         opt->value = atoi(q+1);
16503                     default:
16504                         break;
16505                 }
16506                 strcat(buf, "\n");
16507                 SendToProgram(buf, cps);
16508         }
16509         return TRUE;
16510 }
16511
16512 void
16513 FeatureDone (ChessProgramState *cps, int val)
16514 {
16515   DelayedEventCallback cb = GetDelayedEvent();
16516   if ((cb == InitBackEnd3 && cps == &first) ||
16517       (cb == SettingsMenuIfReady && cps == &second) ||
16518       (cb == LoadEngine) ||
16519       (cb == TwoMachinesEventIfReady)) {
16520     CancelDelayedEvent();
16521     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16522   }
16523   cps->initDone = val;
16524   if(val) cps->reload = FALSE;
16525 }
16526
16527 /* Parse feature command from engine */
16528 void
16529 ParseFeatures (char *args, ChessProgramState *cps)
16530 {
16531   char *p = args;
16532   char *q = NULL;
16533   int val;
16534   char buf[MSG_SIZ];
16535
16536   for (;;) {
16537     while (*p == ' ') p++;
16538     if (*p == NULLCHAR) return;
16539
16540     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16541     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16542     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16543     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16544     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16545     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16546     if (BoolFeature(&p, "reuse", &val, cps)) {
16547       /* Engine can disable reuse, but can't enable it if user said no */
16548       if (!val) cps->reuse = FALSE;
16549       continue;
16550     }
16551     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16552     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16553       if (gameMode == TwoMachinesPlay) {
16554         DisplayTwoMachinesTitle();
16555       } else {
16556         DisplayTitle("");
16557       }
16558       continue;
16559     }
16560     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16561     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16562     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16563     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16564     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16565     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16566     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16567     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16568     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16569     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16570     if (IntFeature(&p, "done", &val, cps)) {
16571       FeatureDone(cps, val);
16572       continue;
16573     }
16574     /* Added by Tord: */
16575     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16576     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16577     /* End of additions by Tord */
16578
16579     /* [HGM] added features: */
16580     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16581     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16582     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16583     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16584     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16585     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16586     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16587     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16588         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16589         FREE(cps->option[cps->nrOptions].name);
16590         cps->option[cps->nrOptions].name = q; q = NULL;
16591         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16592           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16593             SendToProgram(buf, cps);
16594             continue;
16595         }
16596         if(cps->nrOptions >= MAX_OPTIONS) {
16597             cps->nrOptions--;
16598             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16599             DisplayError(buf, 0);
16600         }
16601         continue;
16602     }
16603     /* End of additions by HGM */
16604
16605     /* unknown feature: complain and skip */
16606     q = p;
16607     while (*q && *q != '=') q++;
16608     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16609     SendToProgram(buf, cps);
16610     p = q;
16611     if (*p == '=') {
16612       p++;
16613       if (*p == '\"') {
16614         p++;
16615         while (*p && *p != '\"') p++;
16616         if (*p == '\"') p++;
16617       } else {
16618         while (*p && *p != ' ') p++;
16619       }
16620     }
16621   }
16622
16623 }
16624
16625 void
16626 PeriodicUpdatesEvent (int newState)
16627 {
16628     if (newState == appData.periodicUpdates)
16629       return;
16630
16631     appData.periodicUpdates=newState;
16632
16633     /* Display type changes, so update it now */
16634 //    DisplayAnalysis();
16635
16636     /* Get the ball rolling again... */
16637     if (newState) {
16638         AnalysisPeriodicEvent(1);
16639         StartAnalysisClock();
16640     }
16641 }
16642
16643 void
16644 PonderNextMoveEvent (int newState)
16645 {
16646     if (newState == appData.ponderNextMove) return;
16647     if (gameMode == EditPosition) EditPositionDone(TRUE);
16648     if (newState) {
16649         SendToProgram("hard\n", &first);
16650         if (gameMode == TwoMachinesPlay) {
16651             SendToProgram("hard\n", &second);
16652         }
16653     } else {
16654         SendToProgram("easy\n", &first);
16655         thinkOutput[0] = NULLCHAR;
16656         if (gameMode == TwoMachinesPlay) {
16657             SendToProgram("easy\n", &second);
16658         }
16659     }
16660     appData.ponderNextMove = newState;
16661 }
16662
16663 void
16664 NewSettingEvent (int option, int *feature, char *command, int value)
16665 {
16666     char buf[MSG_SIZ];
16667
16668     if (gameMode == EditPosition) EditPositionDone(TRUE);
16669     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16670     if(feature == NULL || *feature) SendToProgram(buf, &first);
16671     if (gameMode == TwoMachinesPlay) {
16672         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16673     }
16674 }
16675
16676 void
16677 ShowThinkingEvent ()
16678 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16679 {
16680     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16681     int newState = appData.showThinking
16682         // [HGM] thinking: other features now need thinking output as well
16683         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16684
16685     if (oldState == newState) return;
16686     oldState = newState;
16687     if (gameMode == EditPosition) EditPositionDone(TRUE);
16688     if (oldState) {
16689         SendToProgram("post\n", &first);
16690         if (gameMode == TwoMachinesPlay) {
16691             SendToProgram("post\n", &second);
16692         }
16693     } else {
16694         SendToProgram("nopost\n", &first);
16695         thinkOutput[0] = NULLCHAR;
16696         if (gameMode == TwoMachinesPlay) {
16697             SendToProgram("nopost\n", &second);
16698         }
16699     }
16700 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16701 }
16702
16703 void
16704 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16705 {
16706   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16707   if (pr == NoProc) return;
16708   AskQuestion(title, question, replyPrefix, pr);
16709 }
16710
16711 void
16712 TypeInEvent (char firstChar)
16713 {
16714     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16715         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16716         gameMode == AnalyzeMode || gameMode == EditGame ||
16717         gameMode == EditPosition || gameMode == IcsExamining ||
16718         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16719         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16720                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16721                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16722         gameMode == Training) PopUpMoveDialog(firstChar);
16723 }
16724
16725 void
16726 TypeInDoneEvent (char *move)
16727 {
16728         Board board;
16729         int n, fromX, fromY, toX, toY;
16730         char promoChar;
16731         ChessMove moveType;
16732
16733         // [HGM] FENedit
16734         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16735                 EditPositionPasteFEN(move);
16736                 return;
16737         }
16738         // [HGM] movenum: allow move number to be typed in any mode
16739         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16740           ToNrEvent(2*n-1);
16741           return;
16742         }
16743         // undocumented kludge: allow command-line option to be typed in!
16744         // (potentially fatal, and does not implement the effect of the option.)
16745         // should only be used for options that are values on which future decisions will be made,
16746         // and definitely not on options that would be used during initialization.
16747         if(strstr(move, "!!! -") == move) {
16748             ParseArgsFromString(move+4);
16749             return;
16750         }
16751
16752       if (gameMode != EditGame && currentMove != forwardMostMove &&
16753         gameMode != Training) {
16754         DisplayMoveError(_("Displayed move is not current"));
16755       } else {
16756         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16757           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16758         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16759         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16760           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16761           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16762         } else {
16763           DisplayMoveError(_("Could not parse move"));
16764         }
16765       }
16766 }
16767
16768 void
16769 DisplayMove (int moveNumber)
16770 {
16771     char message[MSG_SIZ];
16772     char res[MSG_SIZ];
16773     char cpThinkOutput[MSG_SIZ];
16774
16775     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16776
16777     if (moveNumber == forwardMostMove - 1 ||
16778         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16779
16780         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16781
16782         if (strchr(cpThinkOutput, '\n')) {
16783             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16784         }
16785     } else {
16786         *cpThinkOutput = NULLCHAR;
16787     }
16788
16789     /* [AS] Hide thinking from human user */
16790     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16791         *cpThinkOutput = NULLCHAR;
16792         if( thinkOutput[0] != NULLCHAR ) {
16793             int i;
16794
16795             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16796                 cpThinkOutput[i] = '.';
16797             }
16798             cpThinkOutput[i] = NULLCHAR;
16799             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16800         }
16801     }
16802
16803     if (moveNumber == forwardMostMove - 1 &&
16804         gameInfo.resultDetails != NULL) {
16805         if (gameInfo.resultDetails[0] == NULLCHAR) {
16806           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16807         } else {
16808           snprintf(res, MSG_SIZ, " {%s} %s",
16809                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16810         }
16811     } else {
16812         res[0] = NULLCHAR;
16813     }
16814
16815     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16816         DisplayMessage(res, cpThinkOutput);
16817     } else {
16818       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16819                 WhiteOnMove(moveNumber) ? " " : ".. ",
16820                 parseList[moveNumber], res);
16821         DisplayMessage(message, cpThinkOutput);
16822     }
16823 }
16824
16825 void
16826 DisplayComment (int moveNumber, char *text)
16827 {
16828     char title[MSG_SIZ];
16829
16830     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16831       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16832     } else {
16833       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16834               WhiteOnMove(moveNumber) ? " " : ".. ",
16835               parseList[moveNumber]);
16836     }
16837     if (text != NULL && (appData.autoDisplayComment || commentUp))
16838         CommentPopUp(title, text);
16839 }
16840
16841 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16842  * might be busy thinking or pondering.  It can be omitted if your
16843  * gnuchess is configured to stop thinking immediately on any user
16844  * input.  However, that gnuchess feature depends on the FIONREAD
16845  * ioctl, which does not work properly on some flavors of Unix.
16846  */
16847 void
16848 Attention (ChessProgramState *cps)
16849 {
16850 #if ATTENTION
16851     if (!cps->useSigint) return;
16852     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16853     switch (gameMode) {
16854       case MachinePlaysWhite:
16855       case MachinePlaysBlack:
16856       case TwoMachinesPlay:
16857       case IcsPlayingWhite:
16858       case IcsPlayingBlack:
16859       case AnalyzeMode:
16860       case AnalyzeFile:
16861         /* Skip if we know it isn't thinking */
16862         if (!cps->maybeThinking) return;
16863         if (appData.debugMode)
16864           fprintf(debugFP, "Interrupting %s\n", cps->which);
16865         InterruptChildProcess(cps->pr);
16866         cps->maybeThinking = FALSE;
16867         break;
16868       default:
16869         break;
16870     }
16871 #endif /*ATTENTION*/
16872 }
16873
16874 int
16875 CheckFlags ()
16876 {
16877     if (whiteTimeRemaining <= 0) {
16878         if (!whiteFlag) {
16879             whiteFlag = TRUE;
16880             if (appData.icsActive) {
16881                 if (appData.autoCallFlag &&
16882                     gameMode == IcsPlayingBlack && !blackFlag) {
16883                   SendToICS(ics_prefix);
16884                   SendToICS("flag\n");
16885                 }
16886             } else {
16887                 if (blackFlag) {
16888                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16889                 } else {
16890                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16891                     if (appData.autoCallFlag) {
16892                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16893                         return TRUE;
16894                     }
16895                 }
16896             }
16897         }
16898     }
16899     if (blackTimeRemaining <= 0) {
16900         if (!blackFlag) {
16901             blackFlag = TRUE;
16902             if (appData.icsActive) {
16903                 if (appData.autoCallFlag &&
16904                     gameMode == IcsPlayingWhite && !whiteFlag) {
16905                   SendToICS(ics_prefix);
16906                   SendToICS("flag\n");
16907                 }
16908             } else {
16909                 if (whiteFlag) {
16910                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16911                 } else {
16912                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16913                     if (appData.autoCallFlag) {
16914                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16915                         return TRUE;
16916                     }
16917                 }
16918             }
16919         }
16920     }
16921     return FALSE;
16922 }
16923
16924 void
16925 CheckTimeControl ()
16926 {
16927     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16928         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16929
16930     /*
16931      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16932      */
16933     if ( !WhiteOnMove(forwardMostMove) ) {
16934         /* White made time control */
16935         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16936         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16937         /* [HGM] time odds: correct new time quota for time odds! */
16938                                             / WhitePlayer()->timeOdds;
16939         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16940     } else {
16941         lastBlack -= blackTimeRemaining;
16942         /* Black made time control */
16943         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16944                                             / WhitePlayer()->other->timeOdds;
16945         lastWhite = whiteTimeRemaining;
16946     }
16947 }
16948
16949 void
16950 DisplayBothClocks ()
16951 {
16952     int wom = gameMode == EditPosition ?
16953       !blackPlaysFirst : WhiteOnMove(currentMove);
16954     DisplayWhiteClock(whiteTimeRemaining, wom);
16955     DisplayBlackClock(blackTimeRemaining, !wom);
16956 }
16957
16958
16959 /* Timekeeping seems to be a portability nightmare.  I think everyone
16960    has ftime(), but I'm really not sure, so I'm including some ifdefs
16961    to use other calls if you don't.  Clocks will be less accurate if
16962    you have neither ftime nor gettimeofday.
16963 */
16964
16965 /* VS 2008 requires the #include outside of the function */
16966 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16967 #include <sys/timeb.h>
16968 #endif
16969
16970 /* Get the current time as a TimeMark */
16971 void
16972 GetTimeMark (TimeMark *tm)
16973 {
16974 #if HAVE_GETTIMEOFDAY
16975
16976     struct timeval timeVal;
16977     struct timezone timeZone;
16978
16979     gettimeofday(&timeVal, &timeZone);
16980     tm->sec = (long) timeVal.tv_sec;
16981     tm->ms = (int) (timeVal.tv_usec / 1000L);
16982
16983 #else /*!HAVE_GETTIMEOFDAY*/
16984 #if HAVE_FTIME
16985
16986 // include <sys/timeb.h> / moved to just above start of function
16987     struct timeb timeB;
16988
16989     ftime(&timeB);
16990     tm->sec = (long) timeB.time;
16991     tm->ms = (int) timeB.millitm;
16992
16993 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16994     tm->sec = (long) time(NULL);
16995     tm->ms = 0;
16996 #endif
16997 #endif
16998 }
16999
17000 /* Return the difference in milliseconds between two
17001    time marks.  We assume the difference will fit in a long!
17002 */
17003 long
17004 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17005 {
17006     return 1000L*(tm2->sec - tm1->sec) +
17007            (long) (tm2->ms - tm1->ms);
17008 }
17009
17010
17011 /*
17012  * Code to manage the game clocks.
17013  *
17014  * In tournament play, black starts the clock and then white makes a move.
17015  * We give the human user a slight advantage if he is playing white---the
17016  * clocks don't run until he makes his first move, so it takes zero time.
17017  * Also, we don't account for network lag, so we could get out of sync
17018  * with GNU Chess's clock -- but then, referees are always right.
17019  */
17020
17021 static TimeMark tickStartTM;
17022 static long intendedTickLength;
17023
17024 long
17025 NextTickLength (long timeRemaining)
17026 {
17027     long nominalTickLength, nextTickLength;
17028
17029     if (timeRemaining > 0L && timeRemaining <= 10000L)
17030       nominalTickLength = 100L;
17031     else
17032       nominalTickLength = 1000L;
17033     nextTickLength = timeRemaining % nominalTickLength;
17034     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17035
17036     return nextTickLength;
17037 }
17038
17039 /* Adjust clock one minute up or down */
17040 void
17041 AdjustClock (Boolean which, int dir)
17042 {
17043     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17044     if(which) blackTimeRemaining += 60000*dir;
17045     else      whiteTimeRemaining += 60000*dir;
17046     DisplayBothClocks();
17047     adjustedClock = TRUE;
17048 }
17049
17050 /* Stop clocks and reset to a fresh time control */
17051 void
17052 ResetClocks ()
17053 {
17054     (void) StopClockTimer();
17055     if (appData.icsActive) {
17056         whiteTimeRemaining = blackTimeRemaining = 0;
17057     } else if (searchTime) {
17058         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17059         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17060     } else { /* [HGM] correct new time quote for time odds */
17061         whiteTC = blackTC = fullTimeControlString;
17062         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17063         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17064     }
17065     if (whiteFlag || blackFlag) {
17066         DisplayTitle("");
17067         whiteFlag = blackFlag = FALSE;
17068     }
17069     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17070     DisplayBothClocks();
17071     adjustedClock = FALSE;
17072 }
17073
17074 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17075
17076 /* Decrement running clock by amount of time that has passed */
17077 void
17078 DecrementClocks ()
17079 {
17080     long timeRemaining;
17081     long lastTickLength, fudge;
17082     TimeMark now;
17083
17084     if (!appData.clockMode) return;
17085     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17086
17087     GetTimeMark(&now);
17088
17089     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17090
17091     /* Fudge if we woke up a little too soon */
17092     fudge = intendedTickLength - lastTickLength;
17093     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17094
17095     if (WhiteOnMove(forwardMostMove)) {
17096         if(whiteNPS >= 0) lastTickLength = 0;
17097         timeRemaining = whiteTimeRemaining -= lastTickLength;
17098         if(timeRemaining < 0 && !appData.icsActive) {
17099             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17100             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17101                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17102                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17103             }
17104         }
17105         DisplayWhiteClock(whiteTimeRemaining - fudge,
17106                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17107     } else {
17108         if(blackNPS >= 0) lastTickLength = 0;
17109         timeRemaining = blackTimeRemaining -= lastTickLength;
17110         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17111             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17112             if(suddenDeath) {
17113                 blackStartMove = forwardMostMove;
17114                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17115             }
17116         }
17117         DisplayBlackClock(blackTimeRemaining - fudge,
17118                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17119     }
17120     if (CheckFlags()) return;
17121
17122     if(twoBoards) { // count down secondary board's clocks as well
17123         activePartnerTime -= lastTickLength;
17124         partnerUp = 1;
17125         if(activePartner == 'W')
17126             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17127         else
17128             DisplayBlackClock(activePartnerTime, TRUE);
17129         partnerUp = 0;
17130     }
17131
17132     tickStartTM = now;
17133     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17134     StartClockTimer(intendedTickLength);
17135
17136     /* if the time remaining has fallen below the alarm threshold, sound the
17137      * alarm. if the alarm has sounded and (due to a takeback or time control
17138      * with increment) the time remaining has increased to a level above the
17139      * threshold, reset the alarm so it can sound again.
17140      */
17141
17142     if (appData.icsActive && appData.icsAlarm) {
17143
17144         /* make sure we are dealing with the user's clock */
17145         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17146                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17147            )) return;
17148
17149         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17150             alarmSounded = FALSE;
17151         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17152             PlayAlarmSound();
17153             alarmSounded = TRUE;
17154         }
17155     }
17156 }
17157
17158
17159 /* A player has just moved, so stop the previously running
17160    clock and (if in clock mode) start the other one.
17161    We redisplay both clocks in case we're in ICS mode, because
17162    ICS gives us an update to both clocks after every move.
17163    Note that this routine is called *after* forwardMostMove
17164    is updated, so the last fractional tick must be subtracted
17165    from the color that is *not* on move now.
17166 */
17167 void
17168 SwitchClocks (int newMoveNr)
17169 {
17170     long lastTickLength;
17171     TimeMark now;
17172     int flagged = FALSE;
17173
17174     GetTimeMark(&now);
17175
17176     if (StopClockTimer() && appData.clockMode) {
17177         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17178         if (!WhiteOnMove(forwardMostMove)) {
17179             if(blackNPS >= 0) lastTickLength = 0;
17180             blackTimeRemaining -= lastTickLength;
17181            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17182 //         if(pvInfoList[forwardMostMove].time == -1)
17183                  pvInfoList[forwardMostMove].time =               // use GUI time
17184                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17185         } else {
17186            if(whiteNPS >= 0) lastTickLength = 0;
17187            whiteTimeRemaining -= lastTickLength;
17188            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17189 //         if(pvInfoList[forwardMostMove].time == -1)
17190                  pvInfoList[forwardMostMove].time =
17191                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17192         }
17193         flagged = CheckFlags();
17194     }
17195     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17196     CheckTimeControl();
17197
17198     if (flagged || !appData.clockMode) return;
17199
17200     switch (gameMode) {
17201       case MachinePlaysBlack:
17202       case MachinePlaysWhite:
17203       case BeginningOfGame:
17204         if (pausing) return;
17205         break;
17206
17207       case EditGame:
17208       case PlayFromGameFile:
17209       case IcsExamining:
17210         return;
17211
17212       default:
17213         break;
17214     }
17215
17216     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17217         if(WhiteOnMove(forwardMostMove))
17218              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17219         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17220     }
17221
17222     tickStartTM = now;
17223     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17224       whiteTimeRemaining : blackTimeRemaining);
17225     StartClockTimer(intendedTickLength);
17226 }
17227
17228
17229 /* Stop both clocks */
17230 void
17231 StopClocks ()
17232 {
17233     long lastTickLength;
17234     TimeMark now;
17235
17236     if (!StopClockTimer()) return;
17237     if (!appData.clockMode) return;
17238
17239     GetTimeMark(&now);
17240
17241     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17242     if (WhiteOnMove(forwardMostMove)) {
17243         if(whiteNPS >= 0) lastTickLength = 0;
17244         whiteTimeRemaining -= lastTickLength;
17245         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17246     } else {
17247         if(blackNPS >= 0) lastTickLength = 0;
17248         blackTimeRemaining -= lastTickLength;
17249         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17250     }
17251     CheckFlags();
17252 }
17253
17254 /* Start clock of player on move.  Time may have been reset, so
17255    if clock is already running, stop and restart it. */
17256 void
17257 StartClocks ()
17258 {
17259     (void) StopClockTimer(); /* in case it was running already */
17260     DisplayBothClocks();
17261     if (CheckFlags()) return;
17262
17263     if (!appData.clockMode) return;
17264     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17265
17266     GetTimeMark(&tickStartTM);
17267     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17268       whiteTimeRemaining : blackTimeRemaining);
17269
17270    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17271     whiteNPS = blackNPS = -1;
17272     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17273        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17274         whiteNPS = first.nps;
17275     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17276        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17277         blackNPS = first.nps;
17278     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17279         whiteNPS = second.nps;
17280     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17281         blackNPS = second.nps;
17282     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17283
17284     StartClockTimer(intendedTickLength);
17285 }
17286
17287 char *
17288 TimeString (long ms)
17289 {
17290     long second, minute, hour, day;
17291     char *sign = "";
17292     static char buf[32];
17293
17294     if (ms > 0 && ms <= 9900) {
17295       /* convert milliseconds to tenths, rounding up */
17296       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17297
17298       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17299       return buf;
17300     }
17301
17302     /* convert milliseconds to seconds, rounding up */
17303     /* use floating point to avoid strangeness of integer division
17304        with negative dividends on many machines */
17305     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17306
17307     if (second < 0) {
17308         sign = "-";
17309         second = -second;
17310     }
17311
17312     day = second / (60 * 60 * 24);
17313     second = second % (60 * 60 * 24);
17314     hour = second / (60 * 60);
17315     second = second % (60 * 60);
17316     minute = second / 60;
17317     second = second % 60;
17318
17319     if (day > 0)
17320       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17321               sign, day, hour, minute, second);
17322     else if (hour > 0)
17323       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17324     else
17325       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17326
17327     return buf;
17328 }
17329
17330
17331 /*
17332  * This is necessary because some C libraries aren't ANSI C compliant yet.
17333  */
17334 char *
17335 StrStr (char *string, char *match)
17336 {
17337     int i, length;
17338
17339     length = strlen(match);
17340
17341     for (i = strlen(string) - length; i >= 0; i--, string++)
17342       if (!strncmp(match, string, length))
17343         return string;
17344
17345     return NULL;
17346 }
17347
17348 char *
17349 StrCaseStr (char *string, char *match)
17350 {
17351     int i, j, length;
17352
17353     length = strlen(match);
17354
17355     for (i = strlen(string) - length; i >= 0; i--, string++) {
17356         for (j = 0; j < length; j++) {
17357             if (ToLower(match[j]) != ToLower(string[j]))
17358               break;
17359         }
17360         if (j == length) return string;
17361     }
17362
17363     return NULL;
17364 }
17365
17366 #ifndef _amigados
17367 int
17368 StrCaseCmp (char *s1, char *s2)
17369 {
17370     char c1, c2;
17371
17372     for (;;) {
17373         c1 = ToLower(*s1++);
17374         c2 = ToLower(*s2++);
17375         if (c1 > c2) return 1;
17376         if (c1 < c2) return -1;
17377         if (c1 == NULLCHAR) return 0;
17378     }
17379 }
17380
17381
17382 int
17383 ToLower (int c)
17384 {
17385     return isupper(c) ? tolower(c) : c;
17386 }
17387
17388
17389 int
17390 ToUpper (int c)
17391 {
17392     return islower(c) ? toupper(c) : c;
17393 }
17394 #endif /* !_amigados    */
17395
17396 char *
17397 StrSave (char *s)
17398 {
17399   char *ret;
17400
17401   if ((ret = (char *) malloc(strlen(s) + 1)))
17402     {
17403       safeStrCpy(ret, s, strlen(s)+1);
17404     }
17405   return ret;
17406 }
17407
17408 char *
17409 StrSavePtr (char *s, char **savePtr)
17410 {
17411     if (*savePtr) {
17412         free(*savePtr);
17413     }
17414     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17415       safeStrCpy(*savePtr, s, strlen(s)+1);
17416     }
17417     return(*savePtr);
17418 }
17419
17420 char *
17421 PGNDate ()
17422 {
17423     time_t clock;
17424     struct tm *tm;
17425     char buf[MSG_SIZ];
17426
17427     clock = time((time_t *)NULL);
17428     tm = localtime(&clock);
17429     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17430             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17431     return StrSave(buf);
17432 }
17433
17434
17435 char *
17436 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17437 {
17438     int i, j, fromX, fromY, toX, toY;
17439     int whiteToPlay;
17440     char buf[MSG_SIZ];
17441     char *p, *q;
17442     int emptycount;
17443     ChessSquare piece;
17444
17445     whiteToPlay = (gameMode == EditPosition) ?
17446       !blackPlaysFirst : (move % 2 == 0);
17447     p = buf;
17448
17449     /* Piece placement data */
17450     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17451         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17452         emptycount = 0;
17453         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17454             if (boards[move][i][j] == EmptySquare) {
17455                 emptycount++;
17456             } else { ChessSquare piece = boards[move][i][j];
17457                 if (emptycount > 0) {
17458                     if(emptycount<10) /* [HGM] can be >= 10 */
17459                         *p++ = '0' + emptycount;
17460                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17461                     emptycount = 0;
17462                 }
17463                 if(PieceToChar(piece) == '+') {
17464                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17465                     *p++ = '+';
17466                     piece = (ChessSquare)(DEMOTED piece);
17467                 }
17468                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17469                 if(p[-1] == '~') {
17470                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17471                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17472                     *p++ = '~';
17473                 }
17474             }
17475         }
17476         if (emptycount > 0) {
17477             if(emptycount<10) /* [HGM] can be >= 10 */
17478                 *p++ = '0' + emptycount;
17479             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17480             emptycount = 0;
17481         }
17482         *p++ = '/';
17483     }
17484     *(p - 1) = ' ';
17485
17486     /* [HGM] print Crazyhouse or Shogi holdings */
17487     if( gameInfo.holdingsWidth ) {
17488         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17489         q = p;
17490         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17491             piece = boards[move][i][BOARD_WIDTH-1];
17492             if( piece != EmptySquare )
17493               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17494                   *p++ = PieceToChar(piece);
17495         }
17496         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17497             piece = boards[move][BOARD_HEIGHT-i-1][0];
17498             if( piece != EmptySquare )
17499               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17500                   *p++ = PieceToChar(piece);
17501         }
17502
17503         if( q == p ) *p++ = '-';
17504         *p++ = ']';
17505         *p++ = ' ';
17506     }
17507
17508     /* Active color */
17509     *p++ = whiteToPlay ? 'w' : 'b';
17510     *p++ = ' ';
17511
17512   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17513     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17514   } else {
17515   if(nrCastlingRights) {
17516      q = p;
17517      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17518        /* [HGM] write directly from rights */
17519            if(boards[move][CASTLING][2] != NoRights &&
17520               boards[move][CASTLING][0] != NoRights   )
17521                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17522            if(boards[move][CASTLING][2] != NoRights &&
17523               boards[move][CASTLING][1] != NoRights   )
17524                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17525            if(boards[move][CASTLING][5] != NoRights &&
17526               boards[move][CASTLING][3] != NoRights   )
17527                 *p++ = boards[move][CASTLING][3] + AAA;
17528            if(boards[move][CASTLING][5] != NoRights &&
17529               boards[move][CASTLING][4] != NoRights   )
17530                 *p++ = boards[move][CASTLING][4] + AAA;
17531      } else {
17532
17533         /* [HGM] write true castling rights */
17534         if( nrCastlingRights == 6 ) {
17535             int q, k=0;
17536             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17537                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17538             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17539                  boards[move][CASTLING][2] != NoRights  );
17540             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17541                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17542                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17543                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17544                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17545             }
17546             if(q) *p++ = 'Q';
17547             k = 0;
17548             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17549                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17550             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17551                  boards[move][CASTLING][5] != NoRights  );
17552             if(gameInfo.variant == VariantSChess) {
17553                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17554                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17555                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17556                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17557             }
17558             if(q) *p++ = 'q';
17559         }
17560      }
17561      if (q == p) *p++ = '-'; /* No castling rights */
17562      *p++ = ' ';
17563   }
17564
17565   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17566      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17567      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17568     /* En passant target square */
17569     if (move > backwardMostMove) {
17570         fromX = moveList[move - 1][0] - AAA;
17571         fromY = moveList[move - 1][1] - ONE;
17572         toX = moveList[move - 1][2] - AAA;
17573         toY = moveList[move - 1][3] - ONE;
17574         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17575             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17576             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17577             fromX == toX) {
17578             /* 2-square pawn move just happened */
17579             *p++ = toX + AAA;
17580             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17581         } else {
17582             *p++ = '-';
17583         }
17584     } else if(move == backwardMostMove) {
17585         // [HGM] perhaps we should always do it like this, and forget the above?
17586         if((signed char)boards[move][EP_STATUS] >= 0) {
17587             *p++ = boards[move][EP_STATUS] + AAA;
17588             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17589         } else {
17590             *p++ = '-';
17591         }
17592     } else {
17593         *p++ = '-';
17594     }
17595     *p++ = ' ';
17596   }
17597   }
17598
17599     if(moveCounts)
17600     {   int i = 0, j=move;
17601
17602         /* [HGM] find reversible plies */
17603         if (appData.debugMode) { int k;
17604             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17605             for(k=backwardMostMove; k<=forwardMostMove; k++)
17606                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17607
17608         }
17609
17610         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17611         if( j == backwardMostMove ) i += initialRulePlies;
17612         sprintf(p, "%d ", i);
17613         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17614
17615         /* Fullmove number */
17616         sprintf(p, "%d", (move / 2) + 1);
17617     } else *--p = NULLCHAR;
17618
17619     return StrSave(buf);
17620 }
17621
17622 Boolean
17623 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17624 {
17625     int i, j, k, w=0;
17626     char *p, c;
17627     int emptycount, virgin[BOARD_FILES];
17628     ChessSquare piece;
17629
17630     p = fen;
17631
17632     /* Piece placement data */
17633     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17634         j = 0;
17635         for (;;) {
17636             if (*p == '/' || *p == ' ' || *p == '[' ) {
17637                 if(j > w) w = j;
17638                 emptycount = gameInfo.boardWidth - j;
17639                 while (emptycount--)
17640                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17641                 if (*p == '/') p++;
17642                 else if(autoSize) { // we stumbled unexpectedly into end of board
17643                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17644                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17645                     }
17646                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17647                 }
17648                 break;
17649 #if(BOARD_FILES >= 10)
17650             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17651                 p++; emptycount=10;
17652                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17653                 while (emptycount--)
17654                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17655 #endif
17656             } else if (*p == '*') {
17657                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17658             } else if (isdigit(*p)) {
17659                 emptycount = *p++ - '0';
17660                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17661                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17662                 while (emptycount--)
17663                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17664             } else if (*p == '+' || isalpha(*p)) {
17665                 if (j >= gameInfo.boardWidth) return FALSE;
17666                 if(*p=='+') {
17667                     piece = CharToPiece(*++p);
17668                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17669                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17670                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17671                 } else piece = CharToPiece(*p++);
17672
17673                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17674                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17675                     piece = (ChessSquare) (PROMOTED piece);
17676                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17677                     p++;
17678                 }
17679                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17680             } else {
17681                 return FALSE;
17682             }
17683         }
17684     }
17685     while (*p == '/' || *p == ' ') p++;
17686
17687     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17688
17689     /* [HGM] by default clear Crazyhouse holdings, if present */
17690     if(gameInfo.holdingsWidth) {
17691        for(i=0; i<BOARD_HEIGHT; i++) {
17692            board[i][0]             = EmptySquare; /* black holdings */
17693            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17694            board[i][1]             = (ChessSquare) 0; /* black counts */
17695            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17696        }
17697     }
17698
17699     /* [HGM] look for Crazyhouse holdings here */
17700     while(*p==' ') p++;
17701     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17702         if(*p == '[') p++;
17703         if(*p == '-' ) p++; /* empty holdings */ else {
17704             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17705             /* if we would allow FEN reading to set board size, we would   */
17706             /* have to add holdings and shift the board read so far here   */
17707             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17708                 p++;
17709                 if((int) piece >= (int) BlackPawn ) {
17710                     i = (int)piece - (int)BlackPawn;
17711                     i = PieceToNumber((ChessSquare)i);
17712                     if( i >= gameInfo.holdingsSize ) return FALSE;
17713                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17714                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17715                 } else {
17716                     i = (int)piece - (int)WhitePawn;
17717                     i = PieceToNumber((ChessSquare)i);
17718                     if( i >= gameInfo.holdingsSize ) return FALSE;
17719                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17720                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17721                 }
17722             }
17723         }
17724         if(*p == ']') p++;
17725     }
17726
17727     while(*p == ' ') p++;
17728
17729     /* Active color */
17730     c = *p++;
17731     if(appData.colorNickNames) {
17732       if( c == appData.colorNickNames[0] ) c = 'w'; else
17733       if( c == appData.colorNickNames[1] ) c = 'b';
17734     }
17735     switch (c) {
17736       case 'w':
17737         *blackPlaysFirst = FALSE;
17738         break;
17739       case 'b':
17740         *blackPlaysFirst = TRUE;
17741         break;
17742       default:
17743         return FALSE;
17744     }
17745
17746     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17747     /* return the extra info in global variiables             */
17748
17749     /* set defaults in case FEN is incomplete */
17750     board[EP_STATUS] = EP_UNKNOWN;
17751     for(i=0; i<nrCastlingRights; i++ ) {
17752         board[CASTLING][i] =
17753             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17754     }   /* assume possible unless obviously impossible */
17755     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17756     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17757     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17758                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17759     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17760     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17761     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17762                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17763     FENrulePlies = 0;
17764
17765     while(*p==' ') p++;
17766     if(nrCastlingRights) {
17767       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17768       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17769           /* castling indicator present, so default becomes no castlings */
17770           for(i=0; i<nrCastlingRights; i++ ) {
17771                  board[CASTLING][i] = NoRights;
17772           }
17773       }
17774       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17775              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17776              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17777              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17778         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17779
17780         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17781             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17782             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17783         }
17784         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17785             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17786         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17787                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17788         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17789                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17790         switch(c) {
17791           case'K':
17792               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17793               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17794               board[CASTLING][2] = whiteKingFile;
17795               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17796               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17797               break;
17798           case'Q':
17799               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17800               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17801               board[CASTLING][2] = whiteKingFile;
17802               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17803               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17804               break;
17805           case'k':
17806               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17807               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17808               board[CASTLING][5] = blackKingFile;
17809               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17810               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17811               break;
17812           case'q':
17813               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17814               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17815               board[CASTLING][5] = blackKingFile;
17816               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17817               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17818           case '-':
17819               break;
17820           default: /* FRC castlings */
17821               if(c >= 'a') { /* black rights */
17822                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17823                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17824                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17825                   if(i == BOARD_RGHT) break;
17826                   board[CASTLING][5] = i;
17827                   c -= AAA;
17828                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17829                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17830                   if(c > i)
17831                       board[CASTLING][3] = c;
17832                   else
17833                       board[CASTLING][4] = c;
17834               } else { /* white rights */
17835                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17836                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17837                     if(board[0][i] == WhiteKing) break;
17838                   if(i == BOARD_RGHT) break;
17839                   board[CASTLING][2] = i;
17840                   c -= AAA - 'a' + 'A';
17841                   if(board[0][c] >= WhiteKing) break;
17842                   if(c > i)
17843                       board[CASTLING][0] = c;
17844                   else
17845                       board[CASTLING][1] = c;
17846               }
17847         }
17848       }
17849       for(i=0; i<nrCastlingRights; i++)
17850         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17851       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17852     if (appData.debugMode) {
17853         fprintf(debugFP, "FEN castling rights:");
17854         for(i=0; i<nrCastlingRights; i++)
17855         fprintf(debugFP, " %d", board[CASTLING][i]);
17856         fprintf(debugFP, "\n");
17857     }
17858
17859       while(*p==' ') p++;
17860     }
17861
17862     /* read e.p. field in games that know e.p. capture */
17863     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17864        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17865        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17866       if(*p=='-') {
17867         p++; board[EP_STATUS] = EP_NONE;
17868       } else {
17869          char c = *p++ - AAA;
17870
17871          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17872          if(*p >= '0' && *p <='9') p++;
17873          board[EP_STATUS] = c;
17874       }
17875     }
17876
17877
17878     if(sscanf(p, "%d", &i) == 1) {
17879         FENrulePlies = i; /* 50-move ply counter */
17880         /* (The move number is still ignored)    */
17881     }
17882
17883     return TRUE;
17884 }
17885
17886 void
17887 EditPositionPasteFEN (char *fen)
17888 {
17889   if (fen != NULL) {
17890     Board initial_position;
17891
17892     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17893       DisplayError(_("Bad FEN position in clipboard"), 0);
17894       return ;
17895     } else {
17896       int savedBlackPlaysFirst = blackPlaysFirst;
17897       EditPositionEvent();
17898       blackPlaysFirst = savedBlackPlaysFirst;
17899       CopyBoard(boards[0], initial_position);
17900       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17901       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17902       DisplayBothClocks();
17903       DrawPosition(FALSE, boards[currentMove]);
17904     }
17905   }
17906 }
17907
17908 static char cseq[12] = "\\   ";
17909
17910 Boolean
17911 set_cont_sequence (char *new_seq)
17912 {
17913     int len;
17914     Boolean ret;
17915
17916     // handle bad attempts to set the sequence
17917         if (!new_seq)
17918                 return 0; // acceptable error - no debug
17919
17920     len = strlen(new_seq);
17921     ret = (len > 0) && (len < sizeof(cseq));
17922     if (ret)
17923       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17924     else if (appData.debugMode)
17925       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17926     return ret;
17927 }
17928
17929 /*
17930     reformat a source message so words don't cross the width boundary.  internal
17931     newlines are not removed.  returns the wrapped size (no null character unless
17932     included in source message).  If dest is NULL, only calculate the size required
17933     for the dest buffer.  lp argument indicats line position upon entry, and it's
17934     passed back upon exit.
17935 */
17936 int
17937 wrap (char *dest, char *src, int count, int width, int *lp)
17938 {
17939     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17940
17941     cseq_len = strlen(cseq);
17942     old_line = line = *lp;
17943     ansi = len = clen = 0;
17944
17945     for (i=0; i < count; i++)
17946     {
17947         if (src[i] == '\033')
17948             ansi = 1;
17949
17950         // if we hit the width, back up
17951         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17952         {
17953             // store i & len in case the word is too long
17954             old_i = i, old_len = len;
17955
17956             // find the end of the last word
17957             while (i && src[i] != ' ' && src[i] != '\n')
17958             {
17959                 i--;
17960                 len--;
17961             }
17962
17963             // word too long?  restore i & len before splitting it
17964             if ((old_i-i+clen) >= width)
17965             {
17966                 i = old_i;
17967                 len = old_len;
17968             }
17969
17970             // extra space?
17971             if (i && src[i-1] == ' ')
17972                 len--;
17973
17974             if (src[i] != ' ' && src[i] != '\n')
17975             {
17976                 i--;
17977                 if (len)
17978                     len--;
17979             }
17980
17981             // now append the newline and continuation sequence
17982             if (dest)
17983                 dest[len] = '\n';
17984             len++;
17985             if (dest)
17986                 strncpy(dest+len, cseq, cseq_len);
17987             len += cseq_len;
17988             line = cseq_len;
17989             clen = cseq_len;
17990             continue;
17991         }
17992
17993         if (dest)
17994             dest[len] = src[i];
17995         len++;
17996         if (!ansi)
17997             line++;
17998         if (src[i] == '\n')
17999             line = 0;
18000         if (src[i] == 'm')
18001             ansi = 0;
18002     }
18003     if (dest && appData.debugMode)
18004     {
18005         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18006             count, width, line, len, *lp);
18007         show_bytes(debugFP, src, count);
18008         fprintf(debugFP, "\ndest: ");
18009         show_bytes(debugFP, dest, len);
18010         fprintf(debugFP, "\n");
18011     }
18012     *lp = dest ? line : old_line;
18013
18014     return len;
18015 }
18016
18017 // [HGM] vari: routines for shelving variations
18018 Boolean modeRestore = FALSE;
18019
18020 void
18021 PushInner (int firstMove, int lastMove)
18022 {
18023         int i, j, nrMoves = lastMove - firstMove;
18024
18025         // push current tail of game on stack
18026         savedResult[storedGames] = gameInfo.result;
18027         savedDetails[storedGames] = gameInfo.resultDetails;
18028         gameInfo.resultDetails = NULL;
18029         savedFirst[storedGames] = firstMove;
18030         savedLast [storedGames] = lastMove;
18031         savedFramePtr[storedGames] = framePtr;
18032         framePtr -= nrMoves; // reserve space for the boards
18033         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18034             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18035             for(j=0; j<MOVE_LEN; j++)
18036                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18037             for(j=0; j<2*MOVE_LEN; j++)
18038                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18039             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18040             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18041             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18042             pvInfoList[firstMove+i-1].depth = 0;
18043             commentList[framePtr+i] = commentList[firstMove+i];
18044             commentList[firstMove+i] = NULL;
18045         }
18046
18047         storedGames++;
18048         forwardMostMove = firstMove; // truncate game so we can start variation
18049 }
18050
18051 void
18052 PushTail (int firstMove, int lastMove)
18053 {
18054         if(appData.icsActive) { // only in local mode
18055                 forwardMostMove = currentMove; // mimic old ICS behavior
18056                 return;
18057         }
18058         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18059
18060         PushInner(firstMove, lastMove);
18061         if(storedGames == 1) GreyRevert(FALSE);
18062         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18063 }
18064
18065 void
18066 PopInner (Boolean annotate)
18067 {
18068         int i, j, nrMoves;
18069         char buf[8000], moveBuf[20];
18070
18071         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18072         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18073         nrMoves = savedLast[storedGames] - currentMove;
18074         if(annotate) {
18075                 int cnt = 10;
18076                 if(!WhiteOnMove(currentMove))
18077                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18078                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18079                 for(i=currentMove; i<forwardMostMove; i++) {
18080                         if(WhiteOnMove(i))
18081                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18082                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18083                         strcat(buf, moveBuf);
18084                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18085                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18086                 }
18087                 strcat(buf, ")");
18088         }
18089         for(i=1; i<=nrMoves; i++) { // copy last variation back
18090             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18091             for(j=0; j<MOVE_LEN; j++)
18092                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18093             for(j=0; j<2*MOVE_LEN; j++)
18094                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18095             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18096             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18097             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18098             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18099             commentList[currentMove+i] = commentList[framePtr+i];
18100             commentList[framePtr+i] = NULL;
18101         }
18102         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18103         framePtr = savedFramePtr[storedGames];
18104         gameInfo.result = savedResult[storedGames];
18105         if(gameInfo.resultDetails != NULL) {
18106             free(gameInfo.resultDetails);
18107       }
18108         gameInfo.resultDetails = savedDetails[storedGames];
18109         forwardMostMove = currentMove + nrMoves;
18110 }
18111
18112 Boolean
18113 PopTail (Boolean annotate)
18114 {
18115         if(appData.icsActive) return FALSE; // only in local mode
18116         if(!storedGames) return FALSE; // sanity
18117         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18118
18119         PopInner(annotate);
18120         if(currentMove < forwardMostMove) ForwardEvent(); else
18121         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18122
18123         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18124         return TRUE;
18125 }
18126
18127 void
18128 CleanupTail ()
18129 {       // remove all shelved variations
18130         int i;
18131         for(i=0; i<storedGames; i++) {
18132             if(savedDetails[i])
18133                 free(savedDetails[i]);
18134             savedDetails[i] = NULL;
18135         }
18136         for(i=framePtr; i<MAX_MOVES; i++) {
18137                 if(commentList[i]) free(commentList[i]);
18138                 commentList[i] = NULL;
18139         }
18140         framePtr = MAX_MOVES-1;
18141         storedGames = 0;
18142 }
18143
18144 void
18145 LoadVariation (int index, char *text)
18146 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18147         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18148         int level = 0, move;
18149
18150         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18151         // first find outermost bracketing variation
18152         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18153             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18154                 if(*p == '{') wait = '}'; else
18155                 if(*p == '[') wait = ']'; else
18156                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18157                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18158             }
18159             if(*p == wait) wait = NULLCHAR; // closing ]} found
18160             p++;
18161         }
18162         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18163         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18164         end[1] = NULLCHAR; // clip off comment beyond variation
18165         ToNrEvent(currentMove-1);
18166         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18167         // kludge: use ParsePV() to append variation to game
18168         move = currentMove;
18169         ParsePV(start, TRUE, TRUE);
18170         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18171         ClearPremoveHighlights();
18172         CommentPopDown();
18173         ToNrEvent(currentMove+1);
18174 }
18175
18176 void
18177 LoadTheme ()
18178 {
18179     char *p, *q, buf[MSG_SIZ];
18180     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18181         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18182         ParseArgsFromString(buf);
18183         ActivateTheme(TRUE); // also redo colors
18184         return;
18185     }
18186     p = nickName;
18187     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18188     {
18189         int len;
18190         q = appData.themeNames;
18191         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18192       if(appData.useBitmaps) {
18193         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18194                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18195                 appData.liteBackTextureMode,
18196                 appData.darkBackTextureMode );
18197       } else {
18198         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18199                 Col2Text(2),   // lightSquareColor
18200                 Col2Text(3) ); // darkSquareColor
18201       }
18202       if(appData.useBorder) {
18203         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18204                 appData.border);
18205       } else {
18206         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18207       }
18208       if(appData.useFont) {
18209         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18210                 appData.renderPiecesWithFont,
18211                 appData.fontToPieceTable,
18212                 Col2Text(9),    // appData.fontBackColorWhite
18213                 Col2Text(10) ); // appData.fontForeColorBlack
18214       } else {
18215         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18216                 appData.pieceDirectory);
18217         if(!appData.pieceDirectory[0])
18218           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18219                 Col2Text(0),   // whitePieceColor
18220                 Col2Text(1) ); // blackPieceColor
18221       }
18222       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18223                 Col2Text(4),   // highlightSquareColor
18224                 Col2Text(5) ); // premoveHighlightColor
18225         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18226         if(insert != q) insert[-1] = NULLCHAR;
18227         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18228         if(q)   free(q);
18229     }
18230     ActivateTheme(FALSE);
18231 }