Implement ChuChess
[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 void
5302 Sweep (int step)
5303 {
5304     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5305     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5306     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5307     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5308     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5309     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5310     do {
5311         promoSweep -= step;
5312         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5313         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5314         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5315         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5316         if(!step) step = -1;
5317     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5318             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5319             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5320     if(toX >= 0) {
5321         int victim = boards[currentMove][toY][toX];
5322         boards[currentMove][toY][toX] = promoSweep;
5323         DrawPosition(FALSE, boards[currentMove]);
5324         boards[currentMove][toY][toX] = victim;
5325     } else
5326     ChangeDragPiece(promoSweep);
5327 }
5328
5329 int
5330 PromoScroll (int x, int y)
5331 {
5332   int step = 0;
5333
5334   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5335   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5336   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5337   if(!step) return FALSE;
5338   lastX = x; lastY = y;
5339   if((promoSweep < BlackPawn) == flipView) step = -step;
5340   if(step > 0) selectFlag = 1;
5341   if(!selectFlag) Sweep(step);
5342   return FALSE;
5343 }
5344
5345 void
5346 NextPiece (int step)
5347 {
5348     ChessSquare piece = boards[currentMove][toY][toX];
5349     do {
5350         pieceSweep -= step;
5351         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5352         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5353         if(!step) step = -1;
5354     } while(PieceToChar(pieceSweep) == '.');
5355     boards[currentMove][toY][toX] = pieceSweep;
5356     DrawPosition(FALSE, boards[currentMove]);
5357     boards[currentMove][toY][toX] = piece;
5358 }
5359 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5360 void
5361 AlphaRank (char *move, int n)
5362 {
5363 //    char *p = move, c; int x, y;
5364
5365     if (appData.debugMode) {
5366         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5367     }
5368
5369     if(move[1]=='*' &&
5370        move[2]>='0' && move[2]<='9' &&
5371        move[3]>='a' && move[3]<='x'    ) {
5372         move[1] = '@';
5373         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5374         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5375     } else
5376     if(move[0]>='0' && move[0]<='9' &&
5377        move[1]>='a' && move[1]<='x' &&
5378        move[2]>='0' && move[2]<='9' &&
5379        move[3]>='a' && move[3]<='x'    ) {
5380         /* input move, Shogi -> normal */
5381         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5382         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5387        move[3]>='0' && move[3]<='9' &&
5388        move[2]>='a' && move[2]<='x'    ) {
5389         move[1] = '*';
5390         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5391         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5392     } else
5393     if(
5394        move[0]>='a' && move[0]<='x' &&
5395        move[3]>='0' && move[3]<='9' &&
5396        move[2]>='a' && move[2]<='x'    ) {
5397          /* output move, normal -> Shogi */
5398         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5399         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5400         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5401         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5402         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5403     }
5404     if (appData.debugMode) {
5405         fprintf(debugFP, "   out = '%s'\n", move);
5406     }
5407 }
5408
5409 char yy_textstr[8000];
5410
5411 /* Parser for moves from gnuchess, ICS, or user typein box */
5412 Boolean
5413 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5414 {
5415     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5416
5417     switch (*moveType) {
5418       case WhitePromotion:
5419       case BlackPromotion:
5420       case WhiteNonPromotion:
5421       case BlackNonPromotion:
5422       case NormalMove:
5423       case FirstLeg:
5424       case WhiteCapturesEnPassant:
5425       case BlackCapturesEnPassant:
5426       case WhiteKingSideCastle:
5427       case WhiteQueenSideCastle:
5428       case BlackKingSideCastle:
5429       case BlackQueenSideCastle:
5430       case WhiteKingSideCastleWild:
5431       case WhiteQueenSideCastleWild:
5432       case BlackKingSideCastleWild:
5433       case BlackQueenSideCastleWild:
5434       /* Code added by Tord: */
5435       case WhiteHSideCastleFR:
5436       case WhiteASideCastleFR:
5437       case BlackHSideCastleFR:
5438       case BlackASideCastleFR:
5439       /* End of code added by Tord */
5440       case IllegalMove:         /* bug or odd chess variant */
5441         *fromX = currentMoveString[0] - AAA;
5442         *fromY = currentMoveString[1] - ONE;
5443         *toX = currentMoveString[2] - AAA;
5444         *toY = currentMoveString[3] - ONE;
5445         *promoChar = currentMoveString[4];
5446         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5447             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5448     if (appData.debugMode) {
5449         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5450     }
5451             *fromX = *fromY = *toX = *toY = 0;
5452             return FALSE;
5453         }
5454         if (appData.testLegality) {
5455           return (*moveType != IllegalMove);
5456         } else {
5457           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5458                          // [HGM] lion: if this is a double move we are less critical
5459                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5460         }
5461
5462       case WhiteDrop:
5463       case BlackDrop:
5464         *fromX = *moveType == WhiteDrop ?
5465           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5466           (int) CharToPiece(ToLower(currentMoveString[0]));
5467         *fromY = DROP_RANK;
5468         *toX = currentMoveString[2] - AAA;
5469         *toY = currentMoveString[3] - ONE;
5470         *promoChar = NULLCHAR;
5471         return TRUE;
5472
5473       case AmbiguousMove:
5474       case ImpossibleMove:
5475       case EndOfFile:
5476       case ElapsedTime:
5477       case Comment:
5478       case PGNTag:
5479       case NAG:
5480       case WhiteWins:
5481       case BlackWins:
5482       case GameIsDrawn:
5483       default:
5484     if (appData.debugMode) {
5485         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5486     }
5487         /* bug? */
5488         *fromX = *fromY = *toX = *toY = 0;
5489         *promoChar = NULLCHAR;
5490         return FALSE;
5491     }
5492 }
5493
5494 Boolean pushed = FALSE;
5495 char *lastParseAttempt;
5496
5497 void
5498 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5499 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5500   int fromX, fromY, toX, toY; char promoChar;
5501   ChessMove moveType;
5502   Boolean valid;
5503   int nr = 0;
5504
5505   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5506   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5507     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5508     pushed = TRUE;
5509   }
5510   endPV = forwardMostMove;
5511   do {
5512     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5513     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5514     lastParseAttempt = pv;
5515     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5516     if(!valid && nr == 0 &&
5517        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5518         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5519         // Hande case where played move is different from leading PV move
5520         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5521         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5522         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5523         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5524           endPV += 2; // if position different, keep this
5525           moveList[endPV-1][0] = fromX + AAA;
5526           moveList[endPV-1][1] = fromY + ONE;
5527           moveList[endPV-1][2] = toX + AAA;
5528           moveList[endPV-1][3] = toY + ONE;
5529           parseList[endPV-1][0] = NULLCHAR;
5530           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5531         }
5532       }
5533     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5534     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5535     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5536     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5537         valid++; // allow comments in PV
5538         continue;
5539     }
5540     nr++;
5541     if(endPV+1 > framePtr) break; // no space, truncate
5542     if(!valid) break;
5543     endPV++;
5544     CopyBoard(boards[endPV], boards[endPV-1]);
5545     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5546     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5547     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5548     CoordsToAlgebraic(boards[endPV - 1],
5549                              PosFlags(endPV - 1),
5550                              fromY, fromX, toY, toX, promoChar,
5551                              parseList[endPV - 1]);
5552   } while(valid);
5553   if(atEnd == 2) return; // used hidden, for PV conversion
5554   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5555   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5556   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5557                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5558   DrawPosition(TRUE, boards[currentMove]);
5559 }
5560
5561 int
5562 MultiPV (ChessProgramState *cps)
5563 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5564         int i;
5565         for(i=0; i<cps->nrOptions; i++)
5566             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5567                 return i;
5568         return -1;
5569 }
5570
5571 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5572
5573 Boolean
5574 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5575 {
5576         int startPV, multi, lineStart, origIndex = index;
5577         char *p, buf2[MSG_SIZ];
5578         ChessProgramState *cps = (pane ? &second : &first);
5579
5580         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5581         lastX = x; lastY = y;
5582         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5583         lineStart = startPV = index;
5584         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5585         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5586         index = startPV;
5587         do{ while(buf[index] && buf[index] != '\n') index++;
5588         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5589         buf[index] = 0;
5590         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5591                 int n = cps->option[multi].value;
5592                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5593                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5594                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5595                 cps->option[multi].value = n;
5596                 *start = *end = 0;
5597                 return FALSE;
5598         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5599                 ExcludeClick(origIndex - lineStart);
5600                 return FALSE;
5601         }
5602         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5603         *start = startPV; *end = index-1;
5604         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5605         return TRUE;
5606 }
5607
5608 char *
5609 PvToSAN (char *pv)
5610 {
5611         static char buf[10*MSG_SIZ];
5612         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5613         *buf = NULLCHAR;
5614         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5615         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5616         for(i = forwardMostMove; i<endPV; i++){
5617             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5618             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5619             k += strlen(buf+k);
5620         }
5621         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5622         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5623         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5624         endPV = savedEnd;
5625         return buf;
5626 }
5627
5628 Boolean
5629 LoadPV (int x, int y)
5630 { // called on right mouse click to load PV
5631   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5632   lastX = x; lastY = y;
5633   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5634   extendGame = FALSE;
5635   return TRUE;
5636 }
5637
5638 void
5639 UnLoadPV ()
5640 {
5641   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5642   if(endPV < 0) return;
5643   if(appData.autoCopyPV) CopyFENToClipboard();
5644   endPV = -1;
5645   if(extendGame && currentMove > forwardMostMove) {
5646         Boolean saveAnimate = appData.animate;
5647         if(pushed) {
5648             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5649                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5650             } else storedGames--; // abandon shelved tail of original game
5651         }
5652         pushed = FALSE;
5653         forwardMostMove = currentMove;
5654         currentMove = oldFMM;
5655         appData.animate = FALSE;
5656         ToNrEvent(forwardMostMove);
5657         appData.animate = saveAnimate;
5658   }
5659   currentMove = forwardMostMove;
5660   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5661   ClearPremoveHighlights();
5662   DrawPosition(TRUE, boards[currentMove]);
5663 }
5664
5665 void
5666 MovePV (int x, int y, int h)
5667 { // step through PV based on mouse coordinates (called on mouse move)
5668   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5669
5670   // we must somehow check if right button is still down (might be released off board!)
5671   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5672   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5673   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5674   if(!step) return;
5675   lastX = x; lastY = y;
5676
5677   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5678   if(endPV < 0) return;
5679   if(y < margin) step = 1; else
5680   if(y > h - margin) step = -1;
5681   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5682   currentMove += step;
5683   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5684   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5685                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5686   DrawPosition(FALSE, boards[currentMove]);
5687 }
5688
5689
5690 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5691 // All positions will have equal probability, but the current method will not provide a unique
5692 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5693 #define DARK 1
5694 #define LITE 2
5695 #define ANY 3
5696
5697 int squaresLeft[4];
5698 int piecesLeft[(int)BlackPawn];
5699 int seed, nrOfShuffles;
5700
5701 void
5702 GetPositionNumber ()
5703 {       // sets global variable seed
5704         int i;
5705
5706         seed = appData.defaultFrcPosition;
5707         if(seed < 0) { // randomize based on time for negative FRC position numbers
5708                 for(i=0; i<50; i++) seed += random();
5709                 seed = random() ^ random() >> 8 ^ random() << 8;
5710                 if(seed<0) seed = -seed;
5711         }
5712 }
5713
5714 int
5715 put (Board board, int pieceType, int rank, int n, int shade)
5716 // put the piece on the (n-1)-th empty squares of the given shade
5717 {
5718         int i;
5719
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5721                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5722                         board[rank][i] = (ChessSquare) pieceType;
5723                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5724                         squaresLeft[ANY]--;
5725                         piecesLeft[pieceType]--;
5726                         return i;
5727                 }
5728         }
5729         return -1;
5730 }
5731
5732
5733 void
5734 AddOnePiece (Board board, int pieceType, int rank, int shade)
5735 // calculate where the next piece goes, (any empty square), and put it there
5736 {
5737         int i;
5738
5739         i = seed % squaresLeft[shade];
5740         nrOfShuffles *= squaresLeft[shade];
5741         seed /= squaresLeft[shade];
5742         put(board, pieceType, rank, i, shade);
5743 }
5744
5745 void
5746 AddTwoPieces (Board board, int pieceType, int rank)
5747 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5748 {
5749         int i, n=squaresLeft[ANY], j=n-1, k;
5750
5751         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5752         i = seed % k;  // pick one
5753         nrOfShuffles *= k;
5754         seed /= k;
5755         while(i >= j) i -= j--;
5756         j = n - 1 - j; i += j;
5757         put(board, pieceType, rank, j, ANY);
5758         put(board, pieceType, rank, i, ANY);
5759 }
5760
5761 void
5762 SetUpShuffle (Board board, int number)
5763 {
5764         int i, p, first=1;
5765
5766         GetPositionNumber(); nrOfShuffles = 1;
5767
5768         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5769         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5770         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5771
5772         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5773
5774         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5775             p = (int) board[0][i];
5776             if(p < (int) BlackPawn) piecesLeft[p] ++;
5777             board[0][i] = EmptySquare;
5778         }
5779
5780         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5781             // shuffles restricted to allow normal castling put KRR first
5782             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5783                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5784             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5785                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5786             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5787                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5788             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5789                 put(board, WhiteRook, 0, 0, ANY);
5790             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5791         }
5792
5793         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5794             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5795             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5796                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5797                 while(piecesLeft[p] >= 2) {
5798                     AddOnePiece(board, p, 0, LITE);
5799                     AddOnePiece(board, p, 0, DARK);
5800                 }
5801                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5802             }
5803
5804         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5805             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5806             // but we leave King and Rooks for last, to possibly obey FRC restriction
5807             if(p == (int)WhiteRook) continue;
5808             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5809             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5810         }
5811
5812         // now everything is placed, except perhaps King (Unicorn) and Rooks
5813
5814         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5815             // Last King gets castling rights
5816             while(piecesLeft[(int)WhiteUnicorn]) {
5817                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5818                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5819             }
5820
5821             while(piecesLeft[(int)WhiteKing]) {
5822                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5823                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5824             }
5825
5826
5827         } else {
5828             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5829             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5830         }
5831
5832         // Only Rooks can be left; simply place them all
5833         while(piecesLeft[(int)WhiteRook]) {
5834                 i = put(board, WhiteRook, 0, 0, ANY);
5835                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5836                         if(first) {
5837                                 first=0;
5838                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5839                         }
5840                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5841                 }
5842         }
5843         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5844             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5845         }
5846
5847         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5848 }
5849
5850 int
5851 SetCharTable (char *table, const char * map)
5852 /* [HGM] moved here from winboard.c because of its general usefulness */
5853 /*       Basically a safe strcpy that uses the last character as King */
5854 {
5855     int result = FALSE; int NrPieces;
5856
5857     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5858                     && NrPieces >= 12 && !(NrPieces&1)) {
5859         int i; /* [HGM] Accept even length from 12 to 34 */
5860
5861         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5862         for( i=0; i<NrPieces/2-1; i++ ) {
5863             table[i] = map[i];
5864             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5865         }
5866         table[(int) WhiteKing]  = map[NrPieces/2-1];
5867         table[(int) BlackKing]  = map[NrPieces-1];
5868
5869         result = TRUE;
5870     }
5871
5872     return result;
5873 }
5874
5875 void
5876 Prelude (Board board)
5877 {       // [HGM] superchess: random selection of exo-pieces
5878         int i, j, k; ChessSquare p;
5879         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5880
5881         GetPositionNumber(); // use FRC position number
5882
5883         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5884             SetCharTable(pieceToChar, appData.pieceToCharTable);
5885             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5886                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5887         }
5888
5889         j = seed%4;                 seed /= 4;
5890         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5891         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5892         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5893         j = seed%3 + (seed%3 >= j); seed /= 3;
5894         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5895         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5896         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5897         j = seed%3;                 seed /= 3;
5898         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5899         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5900         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5901         j = seed%2 + (seed%2 >= j); seed /= 2;
5902         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5903         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5904         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5905         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5906         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5907         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5908         put(board, exoPieces[0],    0, 0, ANY);
5909         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5910 }
5911
5912 void
5913 InitPosition (int redraw)
5914 {
5915     ChessSquare (* pieces)[BOARD_FILES];
5916     int i, j, pawnRow=1, pieceRows=1, overrule,
5917     oldx = gameInfo.boardWidth,
5918     oldy = gameInfo.boardHeight,
5919     oldh = gameInfo.holdingsWidth;
5920     static int oldv;
5921
5922     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5923
5924     /* [AS] Initialize pv info list [HGM] and game status */
5925     {
5926         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5927             pvInfoList[i].depth = 0;
5928             boards[i][EP_STATUS] = EP_NONE;
5929             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5930         }
5931
5932         initialRulePlies = 0; /* 50-move counter start */
5933
5934         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5935         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5936     }
5937
5938
5939     /* [HGM] logic here is completely changed. In stead of full positions */
5940     /* the initialized data only consist of the two backranks. The switch */
5941     /* selects which one we will use, which is than copied to the Board   */
5942     /* initialPosition, which for the rest is initialized by Pawns and    */
5943     /* empty squares. This initial position is then copied to boards[0],  */
5944     /* possibly after shuffling, so that it remains available.            */
5945
5946     gameInfo.holdingsWidth = 0; /* default board sizes */
5947     gameInfo.boardWidth    = 8;
5948     gameInfo.boardHeight   = 8;
5949     gameInfo.holdingsSize  = 0;
5950     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5951     for(i=0; i<BOARD_FILES-2; i++)
5952       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5953     initialPosition[EP_STATUS] = EP_NONE;
5954     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5955     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5956          SetCharTable(pieceNickName, appData.pieceNickNames);
5957     else SetCharTable(pieceNickName, "............");
5958     pieces = FIDEArray;
5959
5960     switch (gameInfo.variant) {
5961     case VariantFischeRandom:
5962       shuffleOpenings = TRUE;
5963     default:
5964       break;
5965     case VariantShatranj:
5966       pieces = ShatranjArray;
5967       nrCastlingRights = 0;
5968       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5969       break;
5970     case VariantMakruk:
5971       pieces = makrukArray;
5972       nrCastlingRights = 0;
5973       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5974       break;
5975     case VariantASEAN:
5976       pieces = aseanArray;
5977       nrCastlingRights = 0;
5978       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5979       break;
5980     case VariantTwoKings:
5981       pieces = twoKingsArray;
5982       break;
5983     case VariantGrand:
5984       pieces = GrandArray;
5985       nrCastlingRights = 0;
5986       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5987       gameInfo.boardWidth = 10;
5988       gameInfo.boardHeight = 10;
5989       gameInfo.holdingsSize = 7;
5990       break;
5991     case VariantCapaRandom:
5992       shuffleOpenings = TRUE;
5993     case VariantCapablanca:
5994       pieces = CapablancaArray;
5995       gameInfo.boardWidth = 10;
5996       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5997       break;
5998     case VariantGothic:
5999       pieces = GothicArray;
6000       gameInfo.boardWidth = 10;
6001       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6002       break;
6003     case VariantSChess:
6004       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6005       gameInfo.holdingsSize = 7;
6006       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6007       break;
6008     case VariantJanus:
6009       pieces = JanusArray;
6010       gameInfo.boardWidth = 10;
6011       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6012       nrCastlingRights = 6;
6013         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6014         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6015         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6016         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6017         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6018         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6019       break;
6020     case VariantFalcon:
6021       pieces = FalconArray;
6022       gameInfo.boardWidth = 10;
6023       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6024       break;
6025     case VariantXiangqi:
6026       pieces = XiangqiArray;
6027       gameInfo.boardWidth  = 9;
6028       gameInfo.boardHeight = 10;
6029       nrCastlingRights = 0;
6030       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6031       break;
6032     case VariantShogi:
6033       pieces = ShogiArray;
6034       gameInfo.boardWidth  = 9;
6035       gameInfo.boardHeight = 9;
6036       gameInfo.holdingsSize = 7;
6037       nrCastlingRights = 0;
6038       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6039       break;
6040     case VariantChu:
6041       pieces = ChuArray; pieceRows = 3;
6042       gameInfo.boardWidth  = 12;
6043       gameInfo.boardHeight = 12;
6044       nrCastlingRights = 0;
6045       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6046                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6047       break;
6048     case VariantCourier:
6049       pieces = CourierArray;
6050       gameInfo.boardWidth  = 12;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6053       break;
6054     case VariantKnightmate:
6055       pieces = KnightmateArray;
6056       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6057       break;
6058     case VariantSpartan:
6059       pieces = SpartanArray;
6060       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6061       break;
6062     case VariantLion:
6063       pieces = lionArray;
6064       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6065       break;
6066     case VariantChuChess:
6067       pieces = ChuChessArray;
6068       gameInfo.boardWidth = 10;
6069       gameInfo.boardHeight = 10;
6070       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6071       break;
6072     case VariantFairy:
6073       pieces = fairyArray;
6074       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6075       break;
6076     case VariantGreat:
6077       pieces = GreatArray;
6078       gameInfo.boardWidth = 10;
6079       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6080       gameInfo.holdingsSize = 8;
6081       break;
6082     case VariantSuper:
6083       pieces = FIDEArray;
6084       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6085       gameInfo.holdingsSize = 8;
6086       startedFromSetupPosition = TRUE;
6087       break;
6088     case VariantCrazyhouse:
6089     case VariantBughouse:
6090       pieces = FIDEArray;
6091       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6092       gameInfo.holdingsSize = 5;
6093       break;
6094     case VariantWildCastle:
6095       pieces = FIDEArray;
6096       /* !!?shuffle with kings guaranteed to be on d or e file */
6097       shuffleOpenings = 1;
6098       break;
6099     case VariantNoCastle:
6100       pieces = FIDEArray;
6101       nrCastlingRights = 0;
6102       /* !!?unconstrained back-rank shuffle */
6103       shuffleOpenings = 1;
6104       break;
6105     }
6106
6107     overrule = 0;
6108     if(appData.NrFiles >= 0) {
6109         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6110         gameInfo.boardWidth = appData.NrFiles;
6111     }
6112     if(appData.NrRanks >= 0) {
6113         gameInfo.boardHeight = appData.NrRanks;
6114     }
6115     if(appData.holdingsSize >= 0) {
6116         i = appData.holdingsSize;
6117         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6118         gameInfo.holdingsSize = i;
6119     }
6120     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6121     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6122         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6123
6124     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6125     if(pawnRow < 1) pawnRow = 1;
6126     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6127        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6128     if(gameInfo.variant == VariantChu) pawnRow = 3;
6129
6130     /* User pieceToChar list overrules defaults */
6131     if(appData.pieceToCharTable != NULL)
6132         SetCharTable(pieceToChar, appData.pieceToCharTable);
6133
6134     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6135
6136         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6137             s = (ChessSquare) 0; /* account holding counts in guard band */
6138         for( i=0; i<BOARD_HEIGHT; i++ )
6139             initialPosition[i][j] = s;
6140
6141         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6142         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6143         initialPosition[pawnRow][j] = WhitePawn;
6144         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6145         if(gameInfo.variant == VariantXiangqi) {
6146             if(j&1) {
6147                 initialPosition[pawnRow][j] =
6148                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6149                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6150                    initialPosition[2][j] = WhiteCannon;
6151                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6152                 }
6153             }
6154         }
6155         if(gameInfo.variant == VariantChu) {
6156              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6157                initialPosition[pawnRow+1][j] = WhiteCobra,
6158                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6159              for(i=1; i<pieceRows; i++) {
6160                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6161                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6162              }
6163         }
6164         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6165             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6166                initialPosition[0][j] = WhiteRook;
6167                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6168             }
6169         }
6170         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6171     }
6172     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6173     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6174
6175             j=BOARD_LEFT+1;
6176             initialPosition[1][j] = WhiteBishop;
6177             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6178             j=BOARD_RGHT-2;
6179             initialPosition[1][j] = WhiteRook;
6180             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6181     }
6182
6183     if( nrCastlingRights == -1) {
6184         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6185         /*       This sets default castling rights from none to normal corners   */
6186         /* Variants with other castling rights must set them themselves above    */
6187         nrCastlingRights = 6;
6188
6189         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6190         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6191         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6192         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6193         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6194         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6195      }
6196
6197      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6198      if(gameInfo.variant == VariantGreat) { // promotion commoners
6199         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6200         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6201         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6202         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6203      }
6204      if( gameInfo.variant == VariantSChess ) {
6205       initialPosition[1][0] = BlackMarshall;
6206       initialPosition[2][0] = BlackAngel;
6207       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6208       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6209       initialPosition[1][1] = initialPosition[2][1] =
6210       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6211      }
6212   if (appData.debugMode) {
6213     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6214   }
6215     if(shuffleOpenings) {
6216         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6217         startedFromSetupPosition = TRUE;
6218     }
6219     if(startedFromPositionFile) {
6220       /* [HGM] loadPos: use PositionFile for every new game */
6221       CopyBoard(initialPosition, filePosition);
6222       for(i=0; i<nrCastlingRights; i++)
6223           initialRights[i] = filePosition[CASTLING][i];
6224       startedFromSetupPosition = TRUE;
6225     }
6226
6227     CopyBoard(boards[0], initialPosition);
6228
6229     if(oldx != gameInfo.boardWidth ||
6230        oldy != gameInfo.boardHeight ||
6231        oldv != gameInfo.variant ||
6232        oldh != gameInfo.holdingsWidth
6233                                          )
6234             InitDrawingSizes(-2 ,0);
6235
6236     oldv = gameInfo.variant;
6237     if (redraw)
6238       DrawPosition(TRUE, boards[currentMove]);
6239 }
6240
6241 void
6242 SendBoard (ChessProgramState *cps, int moveNum)
6243 {
6244     char message[MSG_SIZ];
6245
6246     if (cps->useSetboard) {
6247       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6248       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6249       SendToProgram(message, cps);
6250       free(fen);
6251
6252     } else {
6253       ChessSquare *bp;
6254       int i, j, left=0, right=BOARD_WIDTH;
6255       /* Kludge to set black to move, avoiding the troublesome and now
6256        * deprecated "black" command.
6257        */
6258       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6259         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6260
6261       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6262
6263       SendToProgram("edit\n", cps);
6264       SendToProgram("#\n", cps);
6265       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6266         bp = &boards[moveNum][i][left];
6267         for (j = left; j < right; j++, bp++) {
6268           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6269           if ((int) *bp < (int) BlackPawn) {
6270             if(j == BOARD_RGHT+1)
6271                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6272             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6273             if(message[0] == '+' || message[0] == '~') {
6274               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6275                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6276                         AAA + j, ONE + i);
6277             }
6278             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6279                 message[1] = BOARD_RGHT   - 1 - j + '1';
6280                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6281             }
6282             SendToProgram(message, cps);
6283           }
6284         }
6285       }
6286
6287       SendToProgram("c\n", cps);
6288       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6289         bp = &boards[moveNum][i][left];
6290         for (j = left; j < right; j++, bp++) {
6291           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6292           if (((int) *bp != (int) EmptySquare)
6293               && ((int) *bp >= (int) BlackPawn)) {
6294             if(j == BOARD_LEFT-2)
6295                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6296             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6297                     AAA + j, ONE + i);
6298             if(message[0] == '+' || message[0] == '~') {
6299               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6300                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6301                         AAA + j, ONE + i);
6302             }
6303             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6304                 message[1] = BOARD_RGHT   - 1 - j + '1';
6305                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6306             }
6307             SendToProgram(message, cps);
6308           }
6309         }
6310       }
6311
6312       SendToProgram(".\n", cps);
6313     }
6314     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6315 }
6316
6317 char exclusionHeader[MSG_SIZ];
6318 int exCnt, excludePtr;
6319 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6320 static Exclusion excluTab[200];
6321 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6322
6323 static void
6324 WriteMap (int s)
6325 {
6326     int j;
6327     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6328     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6329 }
6330
6331 static void
6332 ClearMap ()
6333 {
6334     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6335     excludePtr = 24; exCnt = 0;
6336     WriteMap(0);
6337 }
6338
6339 static void
6340 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6341 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6342     char buf[2*MOVE_LEN], *p;
6343     Exclusion *e = excluTab;
6344     int i;
6345     for(i=0; i<exCnt; i++)
6346         if(e[i].ff == fromX && e[i].fr == fromY &&
6347            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6348     if(i == exCnt) { // was not in exclude list; add it
6349         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6350         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6351             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6352             return; // abort
6353         }
6354         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6355         excludePtr++; e[i].mark = excludePtr++;
6356         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6357         exCnt++;
6358     }
6359     exclusionHeader[e[i].mark] = state;
6360 }
6361
6362 static int
6363 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6364 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6365     char buf[MSG_SIZ];
6366     int j, k;
6367     ChessMove moveType;
6368     if((signed char)promoChar == -1) { // kludge to indicate best move
6369         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6370             return 1; // if unparsable, abort
6371     }
6372     // update exclusion map (resolving toggle by consulting existing state)
6373     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6374     j = k%8; k >>= 3;
6375     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6376     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6377          excludeMap[k] |=   1<<j;
6378     else excludeMap[k] &= ~(1<<j);
6379     // update header
6380     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6381     // inform engine
6382     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6383     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6384     SendToBoth(buf);
6385     return (state == '+');
6386 }
6387
6388 static void
6389 ExcludeClick (int index)
6390 {
6391     int i, j;
6392     Exclusion *e = excluTab;
6393     if(index < 25) { // none, best or tail clicked
6394         if(index < 13) { // none: include all
6395             WriteMap(0); // clear map
6396             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6397             SendToBoth("include all\n"); // and inform engine
6398         } else if(index > 18) { // tail
6399             if(exclusionHeader[19] == '-') { // tail was excluded
6400                 SendToBoth("include all\n");
6401                 WriteMap(0); // clear map completely
6402                 // now re-exclude selected moves
6403                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6404                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6405             } else { // tail was included or in mixed state
6406                 SendToBoth("exclude all\n");
6407                 WriteMap(0xFF); // fill map completely
6408                 // now re-include selected moves
6409                 j = 0; // count them
6410                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6411                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6412                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6413             }
6414         } else { // best
6415             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6416         }
6417     } else {
6418         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6419             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6420             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6421             break;
6422         }
6423     }
6424 }
6425
6426 ChessSquare
6427 DefaultPromoChoice (int white)
6428 {
6429     ChessSquare result;
6430     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6431        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6432         result = WhiteFerz; // no choice
6433     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6434         result= WhiteKing; // in Suicide Q is the last thing we want
6435     else if(gameInfo.variant == VariantSpartan)
6436         result = white ? WhiteQueen : WhiteAngel;
6437     else result = WhiteQueen;
6438     if(!white) result = WHITE_TO_BLACK result;
6439     return result;
6440 }
6441
6442 static int autoQueen; // [HGM] oneclick
6443
6444 int
6445 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6446 {
6447     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6448     /* [HGM] add Shogi promotions */
6449     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6450     ChessSquare piece;
6451     ChessMove moveType;
6452     Boolean premove;
6453
6454     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6455     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6456
6457     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6458       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6459         return FALSE;
6460
6461     piece = boards[currentMove][fromY][fromX];
6462     if(gameInfo.variant == VariantChu) {
6463         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6464         promotionZoneSize = BOARD_HEIGHT/3;
6465         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6466     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6467         promotionZoneSize = BOARD_HEIGHT/3;
6468         highestPromotingPiece = (int)WhiteAlfil;
6469     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6470         promotionZoneSize = 3;
6471     }
6472
6473     // Treat Lance as Pawn when it is not representing Amazon
6474     if(gameInfo.variant != VariantSuper) {
6475         if(piece == WhiteLance) piece = WhitePawn; else
6476         if(piece == BlackLance) piece = BlackPawn;
6477     }
6478
6479     // next weed out all moves that do not touch the promotion zone at all
6480     if((int)piece >= BlackPawn) {
6481         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6482              return FALSE;
6483         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6484         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6485     } else {
6486         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6487            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6488         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6489              return FALSE;
6490     }
6491
6492     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6493
6494     // weed out mandatory Shogi promotions
6495     if(gameInfo.variant == VariantShogi) {
6496         if(piece >= BlackPawn) {
6497             if(toY == 0 && piece == BlackPawn ||
6498                toY == 0 && piece == BlackQueen ||
6499                toY <= 1 && piece == BlackKnight) {
6500                 *promoChoice = '+';
6501                 return FALSE;
6502             }
6503         } else {
6504             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6505                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6506                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6507                 *promoChoice = '+';
6508                 return FALSE;
6509             }
6510         }
6511     }
6512
6513     // weed out obviously illegal Pawn moves
6514     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6515         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6516         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6517         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6518         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6519         // note we are not allowed to test for valid (non-)capture, due to premove
6520     }
6521
6522     // we either have a choice what to promote to, or (in Shogi) whether to promote
6523     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6524        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6525         *promoChoice = PieceToChar(BlackFerz);  // no choice
6526         return FALSE;
6527     }
6528     // no sense asking what we must promote to if it is going to explode...
6529     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6530         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6531         return FALSE;
6532     }
6533     // give caller the default choice even if we will not make it
6534     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6535     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6536     if(gameInfo.variant == VariantChuChess) *promoChoice = (piece == WhitePawn || piece == BlackPawn ? 'q' : '+');
6537     if(        sweepSelect && gameInfo.variant != VariantGreat
6538                            && gameInfo.variant != VariantGrand
6539                            && gameInfo.variant != VariantSuper) return FALSE;
6540     if(autoQueen) return FALSE; // predetermined
6541
6542     // suppress promotion popup on illegal moves that are not premoves
6543     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6544               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6545     if(appData.testLegality && !premove) {
6546         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6547                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6548         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6549             return FALSE;
6550     }
6551
6552     return TRUE;
6553 }
6554
6555 int
6556 InPalace (int row, int column)
6557 {   /* [HGM] for Xiangqi */
6558     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6559          column < (BOARD_WIDTH + 4)/2 &&
6560          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6561     return FALSE;
6562 }
6563
6564 int
6565 PieceForSquare (int x, int y)
6566 {
6567   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6568      return -1;
6569   else
6570      return boards[currentMove][y][x];
6571 }
6572
6573 int
6574 OKToStartUserMove (int x, int y)
6575 {
6576     ChessSquare from_piece;
6577     int white_piece;
6578
6579     if (matchMode) return FALSE;
6580     if (gameMode == EditPosition) return TRUE;
6581
6582     if (x >= 0 && y >= 0)
6583       from_piece = boards[currentMove][y][x];
6584     else
6585       from_piece = EmptySquare;
6586
6587     if (from_piece == EmptySquare) return FALSE;
6588
6589     white_piece = (int)from_piece >= (int)WhitePawn &&
6590       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6591
6592     switch (gameMode) {
6593       case AnalyzeFile:
6594       case TwoMachinesPlay:
6595       case EndOfGame:
6596         return FALSE;
6597
6598       case IcsObserving:
6599       case IcsIdle:
6600         return FALSE;
6601
6602       case MachinePlaysWhite:
6603       case IcsPlayingBlack:
6604         if (appData.zippyPlay) return FALSE;
6605         if (white_piece) {
6606             DisplayMoveError(_("You are playing Black"));
6607             return FALSE;
6608         }
6609         break;
6610
6611       case MachinePlaysBlack:
6612       case IcsPlayingWhite:
6613         if (appData.zippyPlay) return FALSE;
6614         if (!white_piece) {
6615             DisplayMoveError(_("You are playing White"));
6616             return FALSE;
6617         }
6618         break;
6619
6620       case PlayFromGameFile:
6621             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6622       case EditGame:
6623         if (!white_piece && WhiteOnMove(currentMove)) {
6624             DisplayMoveError(_("It is White's turn"));
6625             return FALSE;
6626         }
6627         if (white_piece && !WhiteOnMove(currentMove)) {
6628             DisplayMoveError(_("It is Black's turn"));
6629             return FALSE;
6630         }
6631         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6632             /* Editing correspondence game history */
6633             /* Could disallow this or prompt for confirmation */
6634             cmailOldMove = -1;
6635         }
6636         break;
6637
6638       case BeginningOfGame:
6639         if (appData.icsActive) return FALSE;
6640         if (!appData.noChessProgram) {
6641             if (!white_piece) {
6642                 DisplayMoveError(_("You are playing White"));
6643                 return FALSE;
6644             }
6645         }
6646         break;
6647
6648       case Training:
6649         if (!white_piece && WhiteOnMove(currentMove)) {
6650             DisplayMoveError(_("It is White's turn"));
6651             return FALSE;
6652         }
6653         if (white_piece && !WhiteOnMove(currentMove)) {
6654             DisplayMoveError(_("It is Black's turn"));
6655             return FALSE;
6656         }
6657         break;
6658
6659       default:
6660       case IcsExamining:
6661         break;
6662     }
6663     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6664         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6665         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6666         && gameMode != AnalyzeFile && gameMode != Training) {
6667         DisplayMoveError(_("Displayed position is not current"));
6668         return FALSE;
6669     }
6670     return TRUE;
6671 }
6672
6673 Boolean
6674 OnlyMove (int *x, int *y, Boolean captures)
6675 {
6676     DisambiguateClosure cl;
6677     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6678     switch(gameMode) {
6679       case MachinePlaysBlack:
6680       case IcsPlayingWhite:
6681       case BeginningOfGame:
6682         if(!WhiteOnMove(currentMove)) return FALSE;
6683         break;
6684       case MachinePlaysWhite:
6685       case IcsPlayingBlack:
6686         if(WhiteOnMove(currentMove)) return FALSE;
6687         break;
6688       case EditGame:
6689         break;
6690       default:
6691         return FALSE;
6692     }
6693     cl.pieceIn = EmptySquare;
6694     cl.rfIn = *y;
6695     cl.ffIn = *x;
6696     cl.rtIn = -1;
6697     cl.ftIn = -1;
6698     cl.promoCharIn = NULLCHAR;
6699     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6700     if( cl.kind == NormalMove ||
6701         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6702         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6703         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6704       fromX = cl.ff;
6705       fromY = cl.rf;
6706       *x = cl.ft;
6707       *y = cl.rt;
6708       return TRUE;
6709     }
6710     if(cl.kind != ImpossibleMove) return FALSE;
6711     cl.pieceIn = EmptySquare;
6712     cl.rfIn = -1;
6713     cl.ffIn = -1;
6714     cl.rtIn = *y;
6715     cl.ftIn = *x;
6716     cl.promoCharIn = NULLCHAR;
6717     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6718     if( cl.kind == NormalMove ||
6719         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6720         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6721         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6722       fromX = cl.ff;
6723       fromY = cl.rf;
6724       *x = cl.ft;
6725       *y = cl.rt;
6726       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6727       return TRUE;
6728     }
6729     return FALSE;
6730 }
6731
6732 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6733 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6734 int lastLoadGameUseList = FALSE;
6735 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6736 ChessMove lastLoadGameStart = EndOfFile;
6737 int doubleClick;
6738
6739 void
6740 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6741 {
6742     ChessMove moveType;
6743     ChessSquare pup;
6744     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6745
6746     /* Check if the user is playing in turn.  This is complicated because we
6747        let the user "pick up" a piece before it is his turn.  So the piece he
6748        tried to pick up may have been captured by the time he puts it down!
6749        Therefore we use the color the user is supposed to be playing in this
6750        test, not the color of the piece that is currently on the starting
6751        square---except in EditGame mode, where the user is playing both
6752        sides; fortunately there the capture race can't happen.  (It can
6753        now happen in IcsExamining mode, but that's just too bad.  The user
6754        will get a somewhat confusing message in that case.)
6755        */
6756
6757     switch (gameMode) {
6758       case AnalyzeFile:
6759       case TwoMachinesPlay:
6760       case EndOfGame:
6761       case IcsObserving:
6762       case IcsIdle:
6763         /* We switched into a game mode where moves are not accepted,
6764            perhaps while the mouse button was down. */
6765         return;
6766
6767       case MachinePlaysWhite:
6768         /* User is moving for Black */
6769         if (WhiteOnMove(currentMove)) {
6770             DisplayMoveError(_("It is White's turn"));
6771             return;
6772         }
6773         break;
6774
6775       case MachinePlaysBlack:
6776         /* User is moving for White */
6777         if (!WhiteOnMove(currentMove)) {
6778             DisplayMoveError(_("It is Black's turn"));
6779             return;
6780         }
6781         break;
6782
6783       case PlayFromGameFile:
6784             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6785       case EditGame:
6786       case IcsExamining:
6787       case BeginningOfGame:
6788       case AnalyzeMode:
6789       case Training:
6790         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6791         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6792             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6793             /* User is moving for Black */
6794             if (WhiteOnMove(currentMove)) {
6795                 DisplayMoveError(_("It is White's turn"));
6796                 return;
6797             }
6798         } else {
6799             /* User is moving for White */
6800             if (!WhiteOnMove(currentMove)) {
6801                 DisplayMoveError(_("It is Black's turn"));
6802                 return;
6803             }
6804         }
6805         break;
6806
6807       case IcsPlayingBlack:
6808         /* User is moving for Black */
6809         if (WhiteOnMove(currentMove)) {
6810             if (!appData.premove) {
6811                 DisplayMoveError(_("It is White's turn"));
6812             } else if (toX >= 0 && toY >= 0) {
6813                 premoveToX = toX;
6814                 premoveToY = toY;
6815                 premoveFromX = fromX;
6816                 premoveFromY = fromY;
6817                 premovePromoChar = promoChar;
6818                 gotPremove = 1;
6819                 if (appData.debugMode)
6820                     fprintf(debugFP, "Got premove: fromX %d,"
6821                             "fromY %d, toX %d, toY %d\n",
6822                             fromX, fromY, toX, toY);
6823             }
6824             return;
6825         }
6826         break;
6827
6828       case IcsPlayingWhite:
6829         /* User is moving for White */
6830         if (!WhiteOnMove(currentMove)) {
6831             if (!appData.premove) {
6832                 DisplayMoveError(_("It is Black's turn"));
6833             } else if (toX >= 0 && toY >= 0) {
6834                 premoveToX = toX;
6835                 premoveToY = toY;
6836                 premoveFromX = fromX;
6837                 premoveFromY = fromY;
6838                 premovePromoChar = promoChar;
6839                 gotPremove = 1;
6840                 if (appData.debugMode)
6841                     fprintf(debugFP, "Got premove: fromX %d,"
6842                             "fromY %d, toX %d, toY %d\n",
6843                             fromX, fromY, toX, toY);
6844             }
6845             return;
6846         }
6847         break;
6848
6849       default:
6850         break;
6851
6852       case EditPosition:
6853         /* EditPosition, empty square, or different color piece;
6854            click-click move is possible */
6855         if (toX == -2 || toY == -2) {
6856             boards[0][fromY][fromX] = EmptySquare;
6857             DrawPosition(FALSE, boards[currentMove]);
6858             return;
6859         } else if (toX >= 0 && toY >= 0) {
6860             boards[0][toY][toX] = boards[0][fromY][fromX];
6861             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6862                 if(boards[0][fromY][0] != EmptySquare) {
6863                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6864                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6865                 }
6866             } else
6867             if(fromX == BOARD_RGHT+1) {
6868                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6869                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6870                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6871                 }
6872             } else
6873             boards[0][fromY][fromX] = gatingPiece;
6874             DrawPosition(FALSE, boards[currentMove]);
6875             return;
6876         }
6877         return;
6878     }
6879
6880     if(toX < 0 || toY < 0) return;
6881     pup = boards[currentMove][toY][toX];
6882
6883     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6884     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6885          if( pup != EmptySquare ) return;
6886          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6887            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6888                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6889            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6890            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6891            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6892            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6893          fromY = DROP_RANK;
6894     }
6895
6896     /* [HGM] always test for legality, to get promotion info */
6897     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6898                                          fromY, fromX, toY, toX, promoChar);
6899
6900     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6901
6902     /* [HGM] but possibly ignore an IllegalMove result */
6903     if (appData.testLegality) {
6904         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6905             DisplayMoveError(_("Illegal move"));
6906             return;
6907         }
6908     }
6909
6910     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6911         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6912              ClearPremoveHighlights(); // was included
6913         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6914         return;
6915     }
6916
6917     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6918 }
6919
6920 /* Common tail of UserMoveEvent and DropMenuEvent */
6921 int
6922 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6923 {
6924     char *bookHit = 0;
6925
6926     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6927         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6928         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6929         if(WhiteOnMove(currentMove)) {
6930             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6931         } else {
6932             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6933         }
6934     }
6935
6936     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6937        move type in caller when we know the move is a legal promotion */
6938     if(moveType == NormalMove && promoChar)
6939         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6940
6941     /* [HGM] <popupFix> The following if has been moved here from
6942        UserMoveEvent(). Because it seemed to belong here (why not allow
6943        piece drops in training games?), and because it can only be
6944        performed after it is known to what we promote. */
6945     if (gameMode == Training) {
6946       /* compare the move played on the board to the next move in the
6947        * game. If they match, display the move and the opponent's response.
6948        * If they don't match, display an error message.
6949        */
6950       int saveAnimate;
6951       Board testBoard;
6952       CopyBoard(testBoard, boards[currentMove]);
6953       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6954
6955       if (CompareBoards(testBoard, boards[currentMove+1])) {
6956         ForwardInner(currentMove+1);
6957
6958         /* Autoplay the opponent's response.
6959          * if appData.animate was TRUE when Training mode was entered,
6960          * the response will be animated.
6961          */
6962         saveAnimate = appData.animate;
6963         appData.animate = animateTraining;
6964         ForwardInner(currentMove+1);
6965         appData.animate = saveAnimate;
6966
6967         /* check for the end of the game */
6968         if (currentMove >= forwardMostMove) {
6969           gameMode = PlayFromGameFile;
6970           ModeHighlight();
6971           SetTrainingModeOff();
6972           DisplayInformation(_("End of game"));
6973         }
6974       } else {
6975         DisplayError(_("Incorrect move"), 0);
6976       }
6977       return 1;
6978     }
6979
6980   /* Ok, now we know that the move is good, so we can kill
6981      the previous line in Analysis Mode */
6982   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6983                                 && currentMove < forwardMostMove) {
6984     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6985     else forwardMostMove = currentMove;
6986   }
6987
6988   ClearMap();
6989
6990   /* If we need the chess program but it's dead, restart it */
6991   ResurrectChessProgram();
6992
6993   /* A user move restarts a paused game*/
6994   if (pausing)
6995     PauseEvent();
6996
6997   thinkOutput[0] = NULLCHAR;
6998
6999   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7000
7001   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7002     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7003     return 1;
7004   }
7005
7006   if (gameMode == BeginningOfGame) {
7007     if (appData.noChessProgram) {
7008       gameMode = EditGame;
7009       SetGameInfo();
7010     } else {
7011       char buf[MSG_SIZ];
7012       gameMode = MachinePlaysBlack;
7013       StartClocks();
7014       SetGameInfo();
7015       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7016       DisplayTitle(buf);
7017       if (first.sendName) {
7018         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7019         SendToProgram(buf, &first);
7020       }
7021       StartClocks();
7022     }
7023     ModeHighlight();
7024   }
7025
7026   /* Relay move to ICS or chess engine */
7027   if (appData.icsActive) {
7028     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7029         gameMode == IcsExamining) {
7030       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7031         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7032         SendToICS("draw ");
7033         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7034       }
7035       // also send plain move, in case ICS does not understand atomic claims
7036       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7037       ics_user_moved = 1;
7038     }
7039   } else {
7040     if (first.sendTime && (gameMode == BeginningOfGame ||
7041                            gameMode == MachinePlaysWhite ||
7042                            gameMode == MachinePlaysBlack)) {
7043       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7044     }
7045     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7046          // [HGM] book: if program might be playing, let it use book
7047         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7048         first.maybeThinking = TRUE;
7049     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7050         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7051         SendBoard(&first, currentMove+1);
7052         if(second.analyzing) {
7053             if(!second.useSetboard) SendToProgram("undo\n", &second);
7054             SendBoard(&second, currentMove+1);
7055         }
7056     } else {
7057         SendMoveToProgram(forwardMostMove-1, &first);
7058         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7059     }
7060     if (currentMove == cmailOldMove + 1) {
7061       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7062     }
7063   }
7064
7065   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7066
7067   switch (gameMode) {
7068   case EditGame:
7069     if(appData.testLegality)
7070     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7071     case MT_NONE:
7072     case MT_CHECK:
7073       break;
7074     case MT_CHECKMATE:
7075     case MT_STAINMATE:
7076       if (WhiteOnMove(currentMove)) {
7077         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7078       } else {
7079         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7080       }
7081       break;
7082     case MT_STALEMATE:
7083       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7084       break;
7085     }
7086     break;
7087
7088   case MachinePlaysBlack:
7089   case MachinePlaysWhite:
7090     /* disable certain menu options while machine is thinking */
7091     SetMachineThinkingEnables();
7092     break;
7093
7094   default:
7095     break;
7096   }
7097
7098   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7099   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7100
7101   if(bookHit) { // [HGM] book: simulate book reply
7102         static char bookMove[MSG_SIZ]; // a bit generous?
7103
7104         programStats.nodes = programStats.depth = programStats.time =
7105         programStats.score = programStats.got_only_move = 0;
7106         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7107
7108         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7109         strcat(bookMove, bookHit);
7110         HandleMachineMove(bookMove, &first);
7111   }
7112   return 1;
7113 }
7114
7115 void
7116 MarkByFEN(char *fen)
7117 {
7118         int r, f;
7119         if(!appData.markers || !appData.highlightDragging) return;
7120         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7121         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7122         while(*fen) {
7123             int s = 0;
7124             marker[r][f] = 0;
7125             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7126             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7127             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7128             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7129             if(*fen == 'T') marker[r][f++] = 0; else
7130             if(*fen == 'Y') marker[r][f++] = 1; else
7131             if(*fen == 'G') marker[r][f++] = 3; else
7132             if(*fen == 'B') marker[r][f++] = 4; else
7133             if(*fen == 'C') marker[r][f++] = 5; else
7134             if(*fen == 'M') marker[r][f++] = 6; else
7135             if(*fen == 'W') marker[r][f++] = 7; else
7136             if(*fen == 'D') marker[r][f++] = 8; else
7137             if(*fen == 'R') marker[r][f++] = 2; else {
7138                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7139               f += s; fen -= s>0;
7140             }
7141             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7142             if(r < 0) break;
7143             fen++;
7144         }
7145         DrawPosition(TRUE, NULL);
7146 }
7147
7148 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7149
7150 void
7151 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7152 {
7153     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7154     Markers *m = (Markers *) closure;
7155     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7156         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7157                          || kind == WhiteCapturesEnPassant
7158                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7159     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7160 }
7161
7162 void
7163 MarkTargetSquares (int clear)
7164 {
7165   int x, y, sum=0;
7166   if(clear) { // no reason to ever suppress clearing
7167     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;
7168     if(!sum) return; // nothing was cleared,no redraw needed
7169   } else {
7170     int capt = 0;
7171     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7172        !appData.testLegality || gameMode == EditPosition) return;
7173     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7174     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7175       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7176       if(capt)
7177       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7178     }
7179   }
7180   DrawPosition(FALSE, NULL);
7181 }
7182
7183 int
7184 Explode (Board board, int fromX, int fromY, int toX, int toY)
7185 {
7186     if(gameInfo.variant == VariantAtomic &&
7187        (board[toY][toX] != EmptySquare ||                     // capture?
7188         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7189                          board[fromY][fromX] == BlackPawn   )
7190       )) {
7191         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7192         return TRUE;
7193     }
7194     return FALSE;
7195 }
7196
7197 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7198
7199 int
7200 CanPromote (ChessSquare piece, int y)
7201 {
7202         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7203         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7204         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7205            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7206            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7207          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7208         return (piece == BlackPawn && y == 1 ||
7209                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7210                 piece == BlackLance && y == 1 ||
7211                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7212 }
7213
7214 void
7215 HoverEvent (int xPix, int yPix, int x, int y)
7216 {
7217         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7218         int r, f;
7219         if(!first.highlight) return;
7220         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7221         if(x == oldX && y == oldY) return; // only do something if we enter new square
7222         oldFromX = fromX; oldFromY = fromY;
7223         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7224           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7225             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7226         else if(oldX != x || oldY != y) {
7227           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7228           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7229             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7230           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7231             char buf[MSG_SIZ];
7232             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7233             SendToProgram(buf, &first);
7234           }
7235           oldX = x; oldY = y;
7236 //        SetHighlights(fromX, fromY, x, y);
7237         }
7238 }
7239
7240 void ReportClick(char *action, int x, int y)
7241 {
7242         char buf[MSG_SIZ]; // Inform engine of what user does
7243         int r, f;
7244         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7245           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7246         if(!first.highlight || gameMode == EditPosition) return;
7247         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7248         SendToProgram(buf, &first);
7249 }
7250
7251 void
7252 LeftClick (ClickType clickType, int xPix, int yPix)
7253 {
7254     int x, y;
7255     Boolean saveAnimate;
7256     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7257     char promoChoice = NULLCHAR;
7258     ChessSquare piece;
7259     static TimeMark lastClickTime, prevClickTime;
7260
7261     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7262
7263     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7264
7265     if (clickType == Press) ErrorPopDown();
7266     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7267
7268     x = EventToSquare(xPix, BOARD_WIDTH);
7269     y = EventToSquare(yPix, BOARD_HEIGHT);
7270     if (!flipView && y >= 0) {
7271         y = BOARD_HEIGHT - 1 - y;
7272     }
7273     if (flipView && x >= 0) {
7274         x = BOARD_WIDTH - 1 - x;
7275     }
7276
7277     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7278         defaultPromoChoice = promoSweep;
7279         promoSweep = EmptySquare;   // terminate sweep
7280         promoDefaultAltered = TRUE;
7281         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7282     }
7283
7284     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7285         if(clickType == Release) return; // ignore upclick of click-click destination
7286         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7287         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7288         if(gameInfo.holdingsWidth &&
7289                 (WhiteOnMove(currentMove)
7290                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7291                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7292             // click in right holdings, for determining promotion piece
7293             ChessSquare p = boards[currentMove][y][x];
7294             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7295             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7296             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7297                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7298                 fromX = fromY = -1;
7299                 return;
7300             }
7301         }
7302         DrawPosition(FALSE, boards[currentMove]);
7303         return;
7304     }
7305
7306     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7307     if(clickType == Press
7308             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7309               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7310               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7311         return;
7312
7313     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7314         // could be static click on premove from-square: abort premove
7315         gotPremove = 0;
7316         ClearPremoveHighlights();
7317     }
7318
7319     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7320         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7321
7322     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7323         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7324                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7325         defaultPromoChoice = DefaultPromoChoice(side);
7326     }
7327
7328     autoQueen = appData.alwaysPromoteToQueen;
7329
7330     if (fromX == -1) {
7331       int originalY = y;
7332       gatingPiece = EmptySquare;
7333       if (clickType != Press) {
7334         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7335             DragPieceEnd(xPix, yPix); dragging = 0;
7336             DrawPosition(FALSE, NULL);
7337         }
7338         return;
7339       }
7340       doubleClick = FALSE;
7341       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7342         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7343       }
7344       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7345       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7346          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7347          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7348             /* First square */
7349             if (OKToStartUserMove(fromX, fromY)) {
7350                 second = 0;
7351                 ReportClick("lift", x, y);
7352                 MarkTargetSquares(0);
7353                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7354                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7355                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7356                     promoSweep = defaultPromoChoice;
7357                     selectFlag = 0; lastX = xPix; lastY = yPix;
7358                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7359                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7360                 }
7361                 if (appData.highlightDragging) {
7362                     SetHighlights(fromX, fromY, -1, -1);
7363                 } else {
7364                     ClearHighlights();
7365                 }
7366             } else fromX = fromY = -1;
7367             return;
7368         }
7369     }
7370
7371     /* fromX != -1 */
7372     if (clickType == Press && gameMode != EditPosition) {
7373         ChessSquare fromP;
7374         ChessSquare toP;
7375         int frc;
7376
7377         // ignore off-board to clicks
7378         if(y < 0 || x < 0) return;
7379
7380         /* Check if clicking again on the same color piece */
7381         fromP = boards[currentMove][fromY][fromX];
7382         toP = boards[currentMove][y][x];
7383         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7384         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7385            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7386              WhitePawn <= toP && toP <= WhiteKing &&
7387              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7388              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7389             (BlackPawn <= fromP && fromP <= BlackKing &&
7390              BlackPawn <= toP && toP <= BlackKing &&
7391              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7392              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7393             /* Clicked again on same color piece -- changed his mind */
7394             second = (x == fromX && y == fromY);
7395             killX = killY = -1;
7396             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7397                 second = FALSE; // first double-click rather than scond click
7398                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7399             }
7400             promoDefaultAltered = FALSE;
7401             MarkTargetSquares(1);
7402            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7403             if (appData.highlightDragging) {
7404                 SetHighlights(x, y, -1, -1);
7405             } else {
7406                 ClearHighlights();
7407             }
7408             if (OKToStartUserMove(x, y)) {
7409                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7410                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7411                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7412                  gatingPiece = boards[currentMove][fromY][fromX];
7413                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7414                 fromX = x;
7415                 fromY = y; dragging = 1;
7416                 ReportClick("lift", x, y);
7417                 MarkTargetSquares(0);
7418                 DragPieceBegin(xPix, yPix, FALSE);
7419                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7420                     promoSweep = defaultPromoChoice;
7421                     selectFlag = 0; lastX = xPix; lastY = yPix;
7422                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7423                 }
7424             }
7425            }
7426            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7427            second = FALSE;
7428         }
7429         // ignore clicks on holdings
7430         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7431     }
7432
7433     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7434         DragPieceEnd(xPix, yPix); dragging = 0;
7435         if(clearFlag) {
7436             // a deferred attempt to click-click move an empty square on top of a piece
7437             boards[currentMove][y][x] = EmptySquare;
7438             ClearHighlights();
7439             DrawPosition(FALSE, boards[currentMove]);
7440             fromX = fromY = -1; clearFlag = 0;
7441             return;
7442         }
7443         if (appData.animateDragging) {
7444             /* Undo animation damage if any */
7445             DrawPosition(FALSE, NULL);
7446         }
7447         if (second || sweepSelecting) {
7448             /* Second up/down in same square; just abort move */
7449             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7450             second = sweepSelecting = 0;
7451             fromX = fromY = -1;
7452             gatingPiece = EmptySquare;
7453             MarkTargetSquares(1);
7454             ClearHighlights();
7455             gotPremove = 0;
7456             ClearPremoveHighlights();
7457         } else {
7458             /* First upclick in same square; start click-click mode */
7459             SetHighlights(x, y, -1, -1);
7460         }
7461         return;
7462     }
7463
7464     clearFlag = 0;
7465
7466     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7467         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7468         DisplayMessage(_("only marked squares are legal"),"");
7469         DrawPosition(TRUE, NULL);
7470         return; // ignore to-click
7471     }
7472
7473     /* we now have a different from- and (possibly off-board) to-square */
7474     /* Completed move */
7475     if(!sweepSelecting) {
7476         toX = x;
7477         toY = y;
7478     }
7479
7480     saveAnimate = appData.animate;
7481     if (clickType == Press) {
7482         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7483             // must be Edit Position mode with empty-square selected
7484             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7485             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7486             return;
7487         }
7488         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7489             return;
7490         }
7491         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7492             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7493         } else
7494         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7495         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7496           if(appData.sweepSelect) {
7497             ChessSquare piece = boards[currentMove][fromY][fromX];
7498             promoSweep = defaultPromoChoice;
7499             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7500             selectFlag = 0; lastX = xPix; lastY = yPix;
7501             Sweep(0); // Pawn that is going to promote: preview promotion piece
7502             sweepSelecting = 1;
7503             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7504             MarkTargetSquares(1);
7505           }
7506           return; // promo popup appears on up-click
7507         }
7508         /* Finish clickclick move */
7509         if (appData.animate || appData.highlightLastMove) {
7510             SetHighlights(fromX, fromY, toX, toY);
7511         } else {
7512             ClearHighlights();
7513         }
7514     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7515         sweepSelecting = 0;
7516         if (appData.animate || appData.highlightLastMove) {
7517             SetHighlights(fromX, fromY, toX, toY);
7518         } else {
7519             ClearHighlights();
7520         }
7521     } else {
7522 #if 0
7523 // [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
7524         /* Finish drag move */
7525         if (appData.highlightLastMove) {
7526             SetHighlights(fromX, fromY, toX, toY);
7527         } else {
7528             ClearHighlights();
7529         }
7530 #endif
7531         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7532           dragging *= 2;            // flag button-less dragging if we are dragging
7533           MarkTargetSquares(1);
7534           if(x == killX && y == killY) killX = killY = -1; else {
7535             killX = x; killY = y;     //remeber this square as intermediate
7536             ReportClick("put", x, y); // and inform engine
7537             ReportClick("lift", x, y);
7538             MarkTargetSquares(0);
7539             return;
7540           }
7541         }
7542         DragPieceEnd(xPix, yPix); dragging = 0;
7543         /* Don't animate move and drag both */
7544         appData.animate = FALSE;
7545     }
7546
7547     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7548     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7549         ChessSquare piece = boards[currentMove][fromY][fromX];
7550         if(gameMode == EditPosition && piece != EmptySquare &&
7551            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7552             int n;
7553
7554             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7555                 n = PieceToNumber(piece - (int)BlackPawn);
7556                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7557                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7558                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7559             } else
7560             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7561                 n = PieceToNumber(piece);
7562                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7563                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7564                 boards[currentMove][n][BOARD_WIDTH-2]++;
7565             }
7566             boards[currentMove][fromY][fromX] = EmptySquare;
7567         }
7568         ClearHighlights();
7569         fromX = fromY = -1;
7570         MarkTargetSquares(1);
7571         DrawPosition(TRUE, boards[currentMove]);
7572         return;
7573     }
7574
7575     // off-board moves should not be highlighted
7576     if(x < 0 || y < 0) ClearHighlights();
7577     else ReportClick("put", x, y);
7578
7579     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7580
7581     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7582         SetHighlights(fromX, fromY, toX, toY);
7583         MarkTargetSquares(1);
7584         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7585             // [HGM] super: promotion to captured piece selected from holdings
7586             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7587             promotionChoice = TRUE;
7588             // kludge follows to temporarily execute move on display, without promoting yet
7589             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7590             boards[currentMove][toY][toX] = p;
7591             DrawPosition(FALSE, boards[currentMove]);
7592             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7593             boards[currentMove][toY][toX] = q;
7594             DisplayMessage("Click in holdings to choose piece", "");
7595             return;
7596         }
7597         PromotionPopUp();
7598     } else {
7599         int oldMove = currentMove;
7600         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7601         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7602         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7603         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7604            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7605             DrawPosition(TRUE, boards[currentMove]);
7606         MarkTargetSquares(1);
7607         fromX = fromY = -1;
7608     }
7609     appData.animate = saveAnimate;
7610     if (appData.animate || appData.animateDragging) {
7611         /* Undo animation damage if needed */
7612         DrawPosition(FALSE, NULL);
7613     }
7614 }
7615
7616 int
7617 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7618 {   // front-end-free part taken out of PieceMenuPopup
7619     int whichMenu; int xSqr, ySqr;
7620
7621     if(seekGraphUp) { // [HGM] seekgraph
7622         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7623         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7624         return -2;
7625     }
7626
7627     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7628          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7629         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7630         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7631         if(action == Press)   {
7632             originalFlip = flipView;
7633             flipView = !flipView; // temporarily flip board to see game from partners perspective
7634             DrawPosition(TRUE, partnerBoard);
7635             DisplayMessage(partnerStatus, "");
7636             partnerUp = TRUE;
7637         } else if(action == Release) {
7638             flipView = originalFlip;
7639             DrawPosition(TRUE, boards[currentMove]);
7640             partnerUp = FALSE;
7641         }
7642         return -2;
7643     }
7644
7645     xSqr = EventToSquare(x, BOARD_WIDTH);
7646     ySqr = EventToSquare(y, BOARD_HEIGHT);
7647     if (action == Release) {
7648         if(pieceSweep != EmptySquare) {
7649             EditPositionMenuEvent(pieceSweep, toX, toY);
7650             pieceSweep = EmptySquare;
7651         } else UnLoadPV(); // [HGM] pv
7652     }
7653     if (action != Press) return -2; // return code to be ignored
7654     switch (gameMode) {
7655       case IcsExamining:
7656         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7657       case EditPosition:
7658         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7659         if (xSqr < 0 || ySqr < 0) return -1;
7660         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7661         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7662         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7663         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7664         NextPiece(0);
7665         return 2; // grab
7666       case IcsObserving:
7667         if(!appData.icsEngineAnalyze) return -1;
7668       case IcsPlayingWhite:
7669       case IcsPlayingBlack:
7670         if(!appData.zippyPlay) goto noZip;
7671       case AnalyzeMode:
7672       case AnalyzeFile:
7673       case MachinePlaysWhite:
7674       case MachinePlaysBlack:
7675       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7676         if (!appData.dropMenu) {
7677           LoadPV(x, y);
7678           return 2; // flag front-end to grab mouse events
7679         }
7680         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7681            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7682       case EditGame:
7683       noZip:
7684         if (xSqr < 0 || ySqr < 0) return -1;
7685         if (!appData.dropMenu || appData.testLegality &&
7686             gameInfo.variant != VariantBughouse &&
7687             gameInfo.variant != VariantCrazyhouse) return -1;
7688         whichMenu = 1; // drop menu
7689         break;
7690       default:
7691         return -1;
7692     }
7693
7694     if (((*fromX = xSqr) < 0) ||
7695         ((*fromY = ySqr) < 0)) {
7696         *fromX = *fromY = -1;
7697         return -1;
7698     }
7699     if (flipView)
7700       *fromX = BOARD_WIDTH - 1 - *fromX;
7701     else
7702       *fromY = BOARD_HEIGHT - 1 - *fromY;
7703
7704     return whichMenu;
7705 }
7706
7707 void
7708 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7709 {
7710 //    char * hint = lastHint;
7711     FrontEndProgramStats stats;
7712
7713     stats.which = cps == &first ? 0 : 1;
7714     stats.depth = cpstats->depth;
7715     stats.nodes = cpstats->nodes;
7716     stats.score = cpstats->score;
7717     stats.time = cpstats->time;
7718     stats.pv = cpstats->movelist;
7719     stats.hint = lastHint;
7720     stats.an_move_index = 0;
7721     stats.an_move_count = 0;
7722
7723     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7724         stats.hint = cpstats->move_name;
7725         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7726         stats.an_move_count = cpstats->nr_moves;
7727     }
7728
7729     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
7730
7731     SetProgramStats( &stats );
7732 }
7733
7734 void
7735 ClearEngineOutputPane (int which)
7736 {
7737     static FrontEndProgramStats dummyStats;
7738     dummyStats.which = which;
7739     dummyStats.pv = "#";
7740     SetProgramStats( &dummyStats );
7741 }
7742
7743 #define MAXPLAYERS 500
7744
7745 char *
7746 TourneyStandings (int display)
7747 {
7748     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7749     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7750     char result, *p, *names[MAXPLAYERS];
7751
7752     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7753         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7754     names[0] = p = strdup(appData.participants);
7755     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7756
7757     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7758
7759     while(result = appData.results[nr]) {
7760         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7761         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7762         wScore = bScore = 0;
7763         switch(result) {
7764           case '+': wScore = 2; break;
7765           case '-': bScore = 2; break;
7766           case '=': wScore = bScore = 1; break;
7767           case ' ':
7768           case '*': return strdup("busy"); // tourney not finished
7769         }
7770         score[w] += wScore;
7771         score[b] += bScore;
7772         games[w]++;
7773         games[b]++;
7774         nr++;
7775     }
7776     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7777     for(w=0; w<nPlayers; w++) {
7778         bScore = -1;
7779         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7780         ranking[w] = b; points[w] = bScore; score[b] = -2;
7781     }
7782     p = malloc(nPlayers*34+1);
7783     for(w=0; w<nPlayers && w<display; w++)
7784         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7785     free(names[0]);
7786     return p;
7787 }
7788
7789 void
7790 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7791 {       // count all piece types
7792         int p, f, r;
7793         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7794         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7795         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7796                 p = board[r][f];
7797                 pCnt[p]++;
7798                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7799                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7800                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7801                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7802                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7803                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7804         }
7805 }
7806
7807 int
7808 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7809 {
7810         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7811         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7812
7813         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7814         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7815         if(myPawns == 2 && nMine == 3) // KPP
7816             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7817         if(myPawns == 1 && nMine == 2) // KP
7818             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7819         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7820             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7821         if(myPawns) return FALSE;
7822         if(pCnt[WhiteRook+side])
7823             return pCnt[BlackRook-side] ||
7824                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7825                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7826                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7827         if(pCnt[WhiteCannon+side]) {
7828             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7829             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7830         }
7831         if(pCnt[WhiteKnight+side])
7832             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7833         return FALSE;
7834 }
7835
7836 int
7837 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7838 {
7839         VariantClass v = gameInfo.variant;
7840
7841         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7842         if(v == VariantShatranj) return TRUE; // always winnable through baring
7843         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7844         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7845
7846         if(v == VariantXiangqi) {
7847                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7848
7849                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7850                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7851                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7852                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7853                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7854                 if(stale) // we have at least one last-rank P plus perhaps C
7855                     return majors // KPKX
7856                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7857                 else // KCA*E*
7858                     return pCnt[WhiteFerz+side] // KCAK
7859                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7860                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7861                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7862
7863         } else if(v == VariantKnightmate) {
7864                 if(nMine == 1) return FALSE;
7865                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7866         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7867                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7868
7869                 if(nMine == 1) return FALSE; // bare King
7870                 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
7871                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7872                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7873                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7874                 if(pCnt[WhiteKnight+side])
7875                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7876                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7877                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7878                 if(nBishops)
7879                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7880                 if(pCnt[WhiteAlfil+side])
7881                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7882                 if(pCnt[WhiteWazir+side])
7883                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7884         }
7885
7886         return TRUE;
7887 }
7888
7889 int
7890 CompareWithRights (Board b1, Board b2)
7891 {
7892     int rights = 0;
7893     if(!CompareBoards(b1, b2)) return FALSE;
7894     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7895     /* compare castling rights */
7896     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7897            rights++; /* King lost rights, while rook still had them */
7898     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7899         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7900            rights++; /* but at least one rook lost them */
7901     }
7902     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7903            rights++;
7904     if( b1[CASTLING][5] != NoRights ) {
7905         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7906            rights++;
7907     }
7908     return rights == 0;
7909 }
7910
7911 int
7912 Adjudicate (ChessProgramState *cps)
7913 {       // [HGM] some adjudications useful with buggy engines
7914         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7915         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7916         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7917         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7918         int k, drop, count = 0; static int bare = 1;
7919         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7920         Boolean canAdjudicate = !appData.icsActive;
7921
7922         // most tests only when we understand the game, i.e. legality-checking on
7923             if( appData.testLegality )
7924             {   /* [HGM] Some more adjudications for obstinate engines */
7925                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7926                 static int moveCount = 6;
7927                 ChessMove result;
7928                 char *reason = NULL;
7929
7930                 /* Count what is on board. */
7931                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7932
7933                 /* Some material-based adjudications that have to be made before stalemate test */
7934                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7935                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7936                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7937                      if(canAdjudicate && appData.checkMates) {
7938                          if(engineOpponent)
7939                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7940                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7941                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7942                          return 1;
7943                      }
7944                 }
7945
7946                 /* Bare King in Shatranj (loses) or Losers (wins) */
7947                 if( nrW == 1 || nrB == 1) {
7948                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7949                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7950                      if(canAdjudicate && appData.checkMates) {
7951                          if(engineOpponent)
7952                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7953                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7954                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7955                          return 1;
7956                      }
7957                   } else
7958                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7959                   {    /* bare King */
7960                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7961                         if(canAdjudicate && appData.checkMates) {
7962                             /* but only adjudicate if adjudication enabled */
7963                             if(engineOpponent)
7964                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7965                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7966                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7967                             return 1;
7968                         }
7969                   }
7970                 } else bare = 1;
7971
7972
7973             // don't wait for engine to announce game end if we can judge ourselves
7974             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7975               case MT_CHECK:
7976                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7977                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7978                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7979                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7980                             checkCnt++;
7981                         if(checkCnt >= 2) {
7982                             reason = "Xboard adjudication: 3rd check";
7983                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7984                             break;
7985                         }
7986                     }
7987                 }
7988               case MT_NONE:
7989               default:
7990                 break;
7991               case MT_STALEMATE:
7992               case MT_STAINMATE:
7993                 reason = "Xboard adjudication: Stalemate";
7994                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7995                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7996                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7997                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7998                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7999                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8000                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8001                                                                         EP_CHECKMATE : EP_WINS);
8002                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8003                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8004                 }
8005                 break;
8006               case MT_CHECKMATE:
8007                 reason = "Xboard adjudication: Checkmate";
8008                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8009                 if(gameInfo.variant == VariantShogi) {
8010                     if(forwardMostMove > backwardMostMove
8011                        && moveList[forwardMostMove-1][1] == '@'
8012                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8013                         reason = "XBoard adjudication: pawn-drop mate";
8014                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8015                     }
8016                 }
8017                 break;
8018             }
8019
8020                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8021                     case EP_STALEMATE:
8022                         result = GameIsDrawn; break;
8023                     case EP_CHECKMATE:
8024                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8025                     case EP_WINS:
8026                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8027                     default:
8028                         result = EndOfFile;
8029                 }
8030                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8031                     if(engineOpponent)
8032                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8033                     GameEnds( result, reason, GE_XBOARD );
8034                     return 1;
8035                 }
8036
8037                 /* Next absolutely insufficient mating material. */
8038                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8039                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8040                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8041
8042                      /* always flag draws, for judging claims */
8043                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8044
8045                      if(canAdjudicate && appData.materialDraws) {
8046                          /* but only adjudicate them if adjudication enabled */
8047                          if(engineOpponent) {
8048                            SendToProgram("force\n", engineOpponent); // suppress reply
8049                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8050                          }
8051                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8052                          return 1;
8053                      }
8054                 }
8055
8056                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8057                 if(gameInfo.variant == VariantXiangqi ?
8058                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8059                  : nrW + nrB == 4 &&
8060                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8061                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8062                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8063                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8064                    ) ) {
8065                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8066                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8067                           if(engineOpponent) {
8068                             SendToProgram("force\n", engineOpponent); // suppress reply
8069                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8070                           }
8071                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8072                           return 1;
8073                      }
8074                 } else moveCount = 6;
8075             }
8076
8077         // Repetition draws and 50-move rule can be applied independently of legality testing
8078
8079                 /* Check for rep-draws */
8080                 count = 0;
8081                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8082                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8083                 for(k = forwardMostMove-2;
8084                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8085                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8086                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8087                     k-=2)
8088                 {   int rights=0;
8089                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8090                         /* compare castling rights */
8091                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8092                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8093                                 rights++; /* King lost rights, while rook still had them */
8094                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8095                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8096                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8097                                    rights++; /* but at least one rook lost them */
8098                         }
8099                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8100                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8101                                 rights++;
8102                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8103                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8104                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8105                                    rights++;
8106                         }
8107                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8108                             && appData.drawRepeats > 1) {
8109                              /* adjudicate after user-specified nr of repeats */
8110                              int result = GameIsDrawn;
8111                              char *details = "XBoard adjudication: repetition draw";
8112                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8113                                 // [HGM] xiangqi: check for forbidden perpetuals
8114                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8115                                 for(m=forwardMostMove; m>k; m-=2) {
8116                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8117                                         ourPerpetual = 0; // the current mover did not always check
8118                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8119                                         hisPerpetual = 0; // the opponent did not always check
8120                                 }
8121                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8122                                                                         ourPerpetual, hisPerpetual);
8123                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8124                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8125                                     details = "Xboard adjudication: perpetual checking";
8126                                 } else
8127                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8128                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8129                                 } else
8130                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8131                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8132                                         result = BlackWins;
8133                                         details = "Xboard adjudication: repetition";
8134                                     }
8135                                 } else // it must be XQ
8136                                 // Now check for perpetual chases
8137                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8138                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8139                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8140                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8141                                         static char resdet[MSG_SIZ];
8142                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8143                                         details = resdet;
8144                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8145                                     } else
8146                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8147                                         break; // Abort repetition-checking loop.
8148                                 }
8149                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8150                              }
8151                              if(engineOpponent) {
8152                                SendToProgram("force\n", engineOpponent); // suppress reply
8153                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8154                              }
8155                              GameEnds( result, details, GE_XBOARD );
8156                              return 1;
8157                         }
8158                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8159                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8160                     }
8161                 }
8162
8163                 /* Now we test for 50-move draws. Determine ply count */
8164                 count = forwardMostMove;
8165                 /* look for last irreversble move */
8166                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8167                     count--;
8168                 /* if we hit starting position, add initial plies */
8169                 if( count == backwardMostMove )
8170                     count -= initialRulePlies;
8171                 count = forwardMostMove - count;
8172                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8173                         // adjust reversible move counter for checks in Xiangqi
8174                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8175                         if(i < backwardMostMove) i = backwardMostMove;
8176                         while(i <= forwardMostMove) {
8177                                 lastCheck = inCheck; // check evasion does not count
8178                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8179                                 if(inCheck || lastCheck) count--; // check does not count
8180                                 i++;
8181                         }
8182                 }
8183                 if( count >= 100)
8184                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8185                          /* this is used to judge if draw claims are legal */
8186                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8187                          if(engineOpponent) {
8188                            SendToProgram("force\n", engineOpponent); // suppress reply
8189                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8190                          }
8191                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8192                          return 1;
8193                 }
8194
8195                 /* if draw offer is pending, treat it as a draw claim
8196                  * when draw condition present, to allow engines a way to
8197                  * claim draws before making their move to avoid a race
8198                  * condition occurring after their move
8199                  */
8200                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8201                          char *p = NULL;
8202                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8203                              p = "Draw claim: 50-move rule";
8204                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8205                              p = "Draw claim: 3-fold repetition";
8206                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8207                              p = "Draw claim: insufficient mating material";
8208                          if( p != NULL && canAdjudicate) {
8209                              if(engineOpponent) {
8210                                SendToProgram("force\n", engineOpponent); // suppress reply
8211                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8212                              }
8213                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8214                              return 1;
8215                          }
8216                 }
8217
8218                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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, "Xboard adjudication: long game", GE_XBOARD );
8224                     return 1;
8225                 }
8226         return 0;
8227 }
8228
8229 char *
8230 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8231 {   // [HGM] book: this routine intercepts moves to simulate book replies
8232     char *bookHit = NULL;
8233
8234     //first determine if the incoming move brings opponent into his book
8235     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8236         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8237     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8238     if(bookHit != NULL && !cps->bookSuspend) {
8239         // make sure opponent is not going to reply after receiving move to book position
8240         SendToProgram("force\n", cps);
8241         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8242     }
8243     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8244     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8245     // now arrange restart after book miss
8246     if(bookHit) {
8247         // after a book hit we never send 'go', and the code after the call to this routine
8248         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8249         char buf[MSG_SIZ], *move = bookHit;
8250         if(cps->useSAN) {
8251             int fromX, fromY, toX, toY;
8252             char promoChar;
8253             ChessMove moveType;
8254             move = buf + 30;
8255             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8256                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8257                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8258                                     PosFlags(forwardMostMove),
8259                                     fromY, fromX, toY, toX, promoChar, move);
8260             } else {
8261                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8262                 bookHit = NULL;
8263             }
8264         }
8265         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8266         SendToProgram(buf, cps);
8267         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8268     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8269         SendToProgram("go\n", cps);
8270         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8271     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8272         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8273             SendToProgram("go\n", cps);
8274         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8275     }
8276     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8277 }
8278
8279 int
8280 LoadError (char *errmess, ChessProgramState *cps)
8281 {   // unloads engine and switches back to -ncp mode if it was first
8282     if(cps->initDone) return FALSE;
8283     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8284     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8285     cps->pr = NoProc;
8286     if(cps == &first) {
8287         appData.noChessProgram = TRUE;
8288         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8289         gameMode = BeginningOfGame; ModeHighlight();
8290         SetNCPMode();
8291     }
8292     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8293     DisplayMessage("", ""); // erase waiting message
8294     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8295     return TRUE;
8296 }
8297
8298 char *savedMessage;
8299 ChessProgramState *savedState;
8300 void
8301 DeferredBookMove (void)
8302 {
8303         if(savedState->lastPing != savedState->lastPong)
8304                     ScheduleDelayedEvent(DeferredBookMove, 10);
8305         else
8306         HandleMachineMove(savedMessage, savedState);
8307 }
8308
8309 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8310 static ChessProgramState *stalledEngine;
8311 static char stashedInputMove[MSG_SIZ];
8312
8313 void
8314 HandleMachineMove (char *message, ChessProgramState *cps)
8315 {
8316     static char firstLeg[20];
8317     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8318     char realname[MSG_SIZ];
8319     int fromX, fromY, toX, toY;
8320     ChessMove moveType;
8321     char promoChar, roar;
8322     char *p, *pv=buf1;
8323     int machineWhite, oldError;
8324     char *bookHit;
8325
8326     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8327         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8328         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8329             DisplayError(_("Invalid pairing from pairing engine"), 0);
8330             return;
8331         }
8332         pairingReceived = 1;
8333         NextMatchGame();
8334         return; // Skim the pairing messages here.
8335     }
8336
8337     oldError = cps->userError; cps->userError = 0;
8338
8339 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8340     /*
8341      * Kludge to ignore BEL characters
8342      */
8343     while (*message == '\007') message++;
8344
8345     /*
8346      * [HGM] engine debug message: ignore lines starting with '#' character
8347      */
8348     if(cps->debug && *message == '#') return;
8349
8350     /*
8351      * Look for book output
8352      */
8353     if (cps == &first && bookRequested) {
8354         if (message[0] == '\t' || message[0] == ' ') {
8355             /* Part of the book output is here; append it */
8356             strcat(bookOutput, message);
8357             strcat(bookOutput, "  \n");
8358             return;
8359         } else if (bookOutput[0] != NULLCHAR) {
8360             /* All of book output has arrived; display it */
8361             char *p = bookOutput;
8362             while (*p != NULLCHAR) {
8363                 if (*p == '\t') *p = ' ';
8364                 p++;
8365             }
8366             DisplayInformation(bookOutput);
8367             bookRequested = FALSE;
8368             /* Fall through to parse the current output */
8369         }
8370     }
8371
8372     /*
8373      * Look for machine move.
8374      */
8375     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8376         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8377     {
8378         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8379             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8380             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8381             stalledEngine = cps;
8382             if(appData.ponderNextMove) { // bring opponent out of ponder
8383                 if(gameMode == TwoMachinesPlay) {
8384                     if(cps->other->pause)
8385                         PauseEngine(cps->other);
8386                     else
8387                         SendToProgram("easy\n", cps->other);
8388                 }
8389             }
8390             StopClocks();
8391             return;
8392         }
8393
8394         /* This method is only useful on engines that support ping */
8395         if (cps->lastPing != cps->lastPong) {
8396           if (gameMode == BeginningOfGame) {
8397             /* Extra move from before last new; ignore */
8398             if (appData.debugMode) {
8399                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8400             }
8401           } else {
8402             if (appData.debugMode) {
8403                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8404                         cps->which, gameMode);
8405             }
8406
8407             SendToProgram("undo\n", cps);
8408           }
8409           return;
8410         }
8411
8412         switch (gameMode) {
8413           case BeginningOfGame:
8414             /* Extra move from before last reset; ignore */
8415             if (appData.debugMode) {
8416                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8417             }
8418             return;
8419
8420           case EndOfGame:
8421           case IcsIdle:
8422           default:
8423             /* Extra move after we tried to stop.  The mode test is
8424                not a reliable way of detecting this problem, but it's
8425                the best we can do on engines that don't support ping.
8426             */
8427             if (appData.debugMode) {
8428                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8429                         cps->which, gameMode);
8430             }
8431             SendToProgram("undo\n", cps);
8432             return;
8433
8434           case MachinePlaysWhite:
8435           case IcsPlayingWhite:
8436             machineWhite = TRUE;
8437             break;
8438
8439           case MachinePlaysBlack:
8440           case IcsPlayingBlack:
8441             machineWhite = FALSE;
8442             break;
8443
8444           case TwoMachinesPlay:
8445             machineWhite = (cps->twoMachinesColor[0] == 'w');
8446             break;
8447         }
8448         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8449             if (appData.debugMode) {
8450                 fprintf(debugFP,
8451                         "Ignoring move out of turn by %s, gameMode %d"
8452                         ", forwardMost %d\n",
8453                         cps->which, gameMode, forwardMostMove);
8454             }
8455             return;
8456         }
8457
8458         if(cps->alphaRank) AlphaRank(machineMove, 4);
8459
8460         // [HGM] lion: (some very limited) support for Alien protocol
8461         killX = killY = -1;
8462         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8463             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8464             return;
8465         } else if(firstLeg[0]) { // there was a previous leg;
8466             // only support case where same piece makes two step (and don't even test that!)
8467             char buf[20], *p = machineMove+1, *q = buf+1, f;
8468             safeStrCpy(buf, machineMove, 20);
8469             while(isdigit(*q)) q++; // find start of to-square
8470             safeStrCpy(machineMove, firstLeg, 20);
8471             while(isdigit(*p)) p++;
8472             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8473             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8474             firstLeg[0] = NULLCHAR;
8475         }
8476
8477         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8478                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8479             /* Machine move could not be parsed; ignore it. */
8480           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8481                     machineMove, _(cps->which));
8482             DisplayMoveError(buf1);
8483             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8484                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8485             if (gameMode == TwoMachinesPlay) {
8486               GameEnds(machineWhite ? BlackWins : WhiteWins,
8487                        buf1, GE_XBOARD);
8488             }
8489             return;
8490         }
8491
8492         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8493         /* So we have to redo legality test with true e.p. status here,  */
8494         /* to make sure an illegal e.p. capture does not slip through,   */
8495         /* to cause a forfeit on a justified illegal-move complaint      */
8496         /* of the opponent.                                              */
8497         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8498            ChessMove moveType;
8499            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8500                              fromY, fromX, toY, toX, promoChar);
8501             if(moveType == IllegalMove) {
8502               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8503                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8504                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8505                            buf1, GE_XBOARD);
8506                 return;
8507            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8508            /* [HGM] Kludge to handle engines that send FRC-style castling
8509               when they shouldn't (like TSCP-Gothic) */
8510            switch(moveType) {
8511              case WhiteASideCastleFR:
8512              case BlackASideCastleFR:
8513                toX+=2;
8514                currentMoveString[2]++;
8515                break;
8516              case WhiteHSideCastleFR:
8517              case BlackHSideCastleFR:
8518                toX--;
8519                currentMoveString[2]--;
8520                break;
8521              default: ; // nothing to do, but suppresses warning of pedantic compilers
8522            }
8523         }
8524         hintRequested = FALSE;
8525         lastHint[0] = NULLCHAR;
8526         bookRequested = FALSE;
8527         /* Program may be pondering now */
8528         cps->maybeThinking = TRUE;
8529         if (cps->sendTime == 2) cps->sendTime = 1;
8530         if (cps->offeredDraw) cps->offeredDraw--;
8531
8532         /* [AS] Save move info*/
8533         pvInfoList[ forwardMostMove ].score = programStats.score;
8534         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8535         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8536
8537         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8538
8539         /* Test suites abort the 'game' after one move */
8540         if(*appData.finger) {
8541            static FILE *f;
8542            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8543            if(!f) f = fopen(appData.finger, "w");
8544            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8545            else { DisplayFatalError("Bad output file", errno, 0); return; }
8546            free(fen);
8547            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8548         }
8549
8550         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8551         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8552             int count = 0;
8553
8554             while( count < adjudicateLossPlies ) {
8555                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8556
8557                 if( count & 1 ) {
8558                     score = -score; /* Flip score for winning side */
8559                 }
8560
8561                 if( score > adjudicateLossThreshold ) {
8562                     break;
8563                 }
8564
8565                 count++;
8566             }
8567
8568             if( count >= adjudicateLossPlies ) {
8569                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8570
8571                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8572                     "Xboard adjudication",
8573                     GE_XBOARD );
8574
8575                 return;
8576             }
8577         }
8578
8579         if(Adjudicate(cps)) {
8580             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8581             return; // [HGM] adjudicate: for all automatic game ends
8582         }
8583
8584 #if ZIPPY
8585         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8586             first.initDone) {
8587           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8588                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8589                 SendToICS("draw ");
8590                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8591           }
8592           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8593           ics_user_moved = 1;
8594           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8595                 char buf[3*MSG_SIZ];
8596
8597                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8598                         programStats.score / 100.,
8599                         programStats.depth,
8600                         programStats.time / 100.,
8601                         (unsigned int)programStats.nodes,
8602                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8603                         programStats.movelist);
8604                 SendToICS(buf);
8605 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8606           }
8607         }
8608 #endif
8609
8610         /* [AS] Clear stats for next move */
8611         ClearProgramStats();
8612         thinkOutput[0] = NULLCHAR;
8613         hiddenThinkOutputState = 0;
8614
8615         bookHit = NULL;
8616         if (gameMode == TwoMachinesPlay) {
8617             /* [HGM] relaying draw offers moved to after reception of move */
8618             /* and interpreting offer as claim if it brings draw condition */
8619             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8620                 SendToProgram("draw\n", cps->other);
8621             }
8622             if (cps->other->sendTime) {
8623                 SendTimeRemaining(cps->other,
8624                                   cps->other->twoMachinesColor[0] == 'w');
8625             }
8626             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8627             if (firstMove && !bookHit) {
8628                 firstMove = FALSE;
8629                 if (cps->other->useColors) {
8630                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8631                 }
8632                 SendToProgram("go\n", cps->other);
8633             }
8634             cps->other->maybeThinking = TRUE;
8635         }
8636
8637         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8638
8639         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8640
8641         if (!pausing && appData.ringBellAfterMoves) {
8642             if(!roar) RingBell();
8643         }
8644
8645         /*
8646          * Reenable menu items that were disabled while
8647          * machine was thinking
8648          */
8649         if (gameMode != TwoMachinesPlay)
8650             SetUserThinkingEnables();
8651
8652         // [HGM] book: after book hit opponent has received move and is now in force mode
8653         // force the book reply into it, and then fake that it outputted this move by jumping
8654         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8655         if(bookHit) {
8656                 static char bookMove[MSG_SIZ]; // a bit generous?
8657
8658                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8659                 strcat(bookMove, bookHit);
8660                 message = bookMove;
8661                 cps = cps->other;
8662                 programStats.nodes = programStats.depth = programStats.time =
8663                 programStats.score = programStats.got_only_move = 0;
8664                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8665
8666                 if(cps->lastPing != cps->lastPong) {
8667                     savedMessage = message; // args for deferred call
8668                     savedState = cps;
8669                     ScheduleDelayedEvent(DeferredBookMove, 10);
8670                     return;
8671                 }
8672                 goto FakeBookMove;
8673         }
8674
8675         return;
8676     }
8677
8678     /* Set special modes for chess engines.  Later something general
8679      *  could be added here; for now there is just one kludge feature,
8680      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8681      *  when "xboard" is given as an interactive command.
8682      */
8683     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8684         cps->useSigint = FALSE;
8685         cps->useSigterm = FALSE;
8686     }
8687     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8688       ParseFeatures(message+8, cps);
8689       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8690     }
8691
8692     if (!strncmp(message, "setup ", 6) && 
8693         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8694           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8695                                         ) { // [HGM] allow first engine to define opening position
8696       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8697       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8698       *buf = NULLCHAR;
8699       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8700       if(startedFromSetupPosition) return;
8701       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8702       if(dummy >= 3) {
8703         while(message[s] && message[s++] != ' ');
8704         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8705            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8706             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8707             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8708           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8709           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8710         }
8711       }
8712       ParseFEN(boards[0], &dummy, message+s, FALSE);
8713       DrawPosition(TRUE, boards[0]);
8714       startedFromSetupPosition = TRUE;
8715       return;
8716     }
8717     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8718      * want this, I was asked to put it in, and obliged.
8719      */
8720     if (!strncmp(message, "setboard ", 9)) {
8721         Board initial_position;
8722
8723         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8724
8725         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8726             DisplayError(_("Bad FEN received from engine"), 0);
8727             return ;
8728         } else {
8729            Reset(TRUE, FALSE);
8730            CopyBoard(boards[0], initial_position);
8731            initialRulePlies = FENrulePlies;
8732            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8733            else gameMode = MachinePlaysBlack;
8734            DrawPosition(FALSE, boards[currentMove]);
8735         }
8736         return;
8737     }
8738
8739     /*
8740      * Look for communication commands
8741      */
8742     if (!strncmp(message, "telluser ", 9)) {
8743         if(message[9] == '\\' && message[10] == '\\')
8744             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8745         PlayTellSound();
8746         DisplayNote(message + 9);
8747         return;
8748     }
8749     if (!strncmp(message, "tellusererror ", 14)) {
8750         cps->userError = 1;
8751         if(message[14] == '\\' && message[15] == '\\')
8752             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8753         PlayTellSound();
8754         DisplayError(message + 14, 0);
8755         return;
8756     }
8757     if (!strncmp(message, "tellopponent ", 13)) {
8758       if (appData.icsActive) {
8759         if (loggedOn) {
8760           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8761           SendToICS(buf1);
8762         }
8763       } else {
8764         DisplayNote(message + 13);
8765       }
8766       return;
8767     }
8768     if (!strncmp(message, "tellothers ", 11)) {
8769       if (appData.icsActive) {
8770         if (loggedOn) {
8771           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8772           SendToICS(buf1);
8773         }
8774       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8775       return;
8776     }
8777     if (!strncmp(message, "tellall ", 8)) {
8778       if (appData.icsActive) {
8779         if (loggedOn) {
8780           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8781           SendToICS(buf1);
8782         }
8783       } else {
8784         DisplayNote(message + 8);
8785       }
8786       return;
8787     }
8788     if (strncmp(message, "warning", 7) == 0) {
8789         /* Undocumented feature, use tellusererror in new code */
8790         DisplayError(message, 0);
8791         return;
8792     }
8793     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8794         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8795         strcat(realname, " query");
8796         AskQuestion(realname, buf2, buf1, cps->pr);
8797         return;
8798     }
8799     /* Commands from the engine directly to ICS.  We don't allow these to be
8800      *  sent until we are logged on. Crafty kibitzes have been known to
8801      *  interfere with the login process.
8802      */
8803     if (loggedOn) {
8804         if (!strncmp(message, "tellics ", 8)) {
8805             SendToICS(message + 8);
8806             SendToICS("\n");
8807             return;
8808         }
8809         if (!strncmp(message, "tellicsnoalias ", 15)) {
8810             SendToICS(ics_prefix);
8811             SendToICS(message + 15);
8812             SendToICS("\n");
8813             return;
8814         }
8815         /* The following are for backward compatibility only */
8816         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8817             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8818             SendToICS(ics_prefix);
8819             SendToICS(message);
8820             SendToICS("\n");
8821             return;
8822         }
8823     }
8824     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8825         if(initPing == cps->lastPong) {
8826             if(gameInfo.variant == VariantUnknown) {
8827                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8828                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8829                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8830             }
8831             initPing = -1;
8832         }
8833         return;
8834     }
8835     if(!strncmp(message, "highlight ", 10)) {
8836         if(appData.testLegality && appData.markers) return;
8837         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8838         return;
8839     }
8840     if(!strncmp(message, "click ", 6)) {
8841         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8842         if(appData.testLegality || !appData.oneClick) return;
8843         sscanf(message+6, "%c%d%c", &f, &y, &c);
8844         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8845         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8846         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8847         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8848         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8849         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8850             LeftClick(Release, lastLeftX, lastLeftY);
8851         controlKey  = (c == ',');
8852         LeftClick(Press, x, y);
8853         LeftClick(Release, x, y);
8854         first.highlight = f;
8855         return;
8856     }
8857     /*
8858      * If the move is illegal, cancel it and redraw the board.
8859      * Also deal with other error cases.  Matching is rather loose
8860      * here to accommodate engines written before the spec.
8861      */
8862     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8863         strncmp(message, "Error", 5) == 0) {
8864         if (StrStr(message, "name") ||
8865             StrStr(message, "rating") || StrStr(message, "?") ||
8866             StrStr(message, "result") || StrStr(message, "board") ||
8867             StrStr(message, "bk") || StrStr(message, "computer") ||
8868             StrStr(message, "variant") || StrStr(message, "hint") ||
8869             StrStr(message, "random") || StrStr(message, "depth") ||
8870             StrStr(message, "accepted")) {
8871             return;
8872         }
8873         if (StrStr(message, "protover")) {
8874           /* Program is responding to input, so it's apparently done
8875              initializing, and this error message indicates it is
8876              protocol version 1.  So we don't need to wait any longer
8877              for it to initialize and send feature commands. */
8878           FeatureDone(cps, 1);
8879           cps->protocolVersion = 1;
8880           return;
8881         }
8882         cps->maybeThinking = FALSE;
8883
8884         if (StrStr(message, "draw")) {
8885             /* Program doesn't have "draw" command */
8886             cps->sendDrawOffers = 0;
8887             return;
8888         }
8889         if (cps->sendTime != 1 &&
8890             (StrStr(message, "time") || StrStr(message, "otim"))) {
8891           /* Program apparently doesn't have "time" or "otim" command */
8892           cps->sendTime = 0;
8893           return;
8894         }
8895         if (StrStr(message, "analyze")) {
8896             cps->analysisSupport = FALSE;
8897             cps->analyzing = FALSE;
8898 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8899             EditGameEvent(); // [HGM] try to preserve loaded game
8900             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8901             DisplayError(buf2, 0);
8902             return;
8903         }
8904         if (StrStr(message, "(no matching move)st")) {
8905           /* Special kludge for GNU Chess 4 only */
8906           cps->stKludge = TRUE;
8907           SendTimeControl(cps, movesPerSession, timeControl,
8908                           timeIncrement, appData.searchDepth,
8909                           searchTime);
8910           return;
8911         }
8912         if (StrStr(message, "(no matching move)sd")) {
8913           /* Special kludge for GNU Chess 4 only */
8914           cps->sdKludge = TRUE;
8915           SendTimeControl(cps, movesPerSession, timeControl,
8916                           timeIncrement, appData.searchDepth,
8917                           searchTime);
8918           return;
8919         }
8920         if (!StrStr(message, "llegal")) {
8921             return;
8922         }
8923         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8924             gameMode == IcsIdle) return;
8925         if (forwardMostMove <= backwardMostMove) return;
8926         if (pausing) PauseEvent();
8927       if(appData.forceIllegal) {
8928             // [HGM] illegal: machine refused move; force position after move into it
8929           SendToProgram("force\n", cps);
8930           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8931                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8932                 // when black is to move, while there might be nothing on a2 or black
8933                 // might already have the move. So send the board as if white has the move.
8934                 // But first we must change the stm of the engine, as it refused the last move
8935                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8936                 if(WhiteOnMove(forwardMostMove)) {
8937                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8938                     SendBoard(cps, forwardMostMove); // kludgeless board
8939                 } else {
8940                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8941                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8942                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8943                 }
8944           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8945             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8946                  gameMode == TwoMachinesPlay)
8947               SendToProgram("go\n", cps);
8948             return;
8949       } else
8950         if (gameMode == PlayFromGameFile) {
8951             /* Stop reading this game file */
8952             gameMode = EditGame;
8953             ModeHighlight();
8954         }
8955         /* [HGM] illegal-move claim should forfeit game when Xboard */
8956         /* only passes fully legal moves                            */
8957         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8958             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8959                                 "False illegal-move claim", GE_XBOARD );
8960             return; // do not take back move we tested as valid
8961         }
8962         currentMove = forwardMostMove-1;
8963         DisplayMove(currentMove-1); /* before DisplayMoveError */
8964         SwitchClocks(forwardMostMove-1); // [HGM] race
8965         DisplayBothClocks();
8966         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8967                 parseList[currentMove], _(cps->which));
8968         DisplayMoveError(buf1);
8969         DrawPosition(FALSE, boards[currentMove]);
8970
8971         SetUserThinkingEnables();
8972         return;
8973     }
8974     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8975         /* Program has a broken "time" command that
8976            outputs a string not ending in newline.
8977            Don't use it. */
8978         cps->sendTime = 0;
8979     }
8980
8981     /*
8982      * If chess program startup fails, exit with an error message.
8983      * Attempts to recover here are futile. [HGM] Well, we try anyway
8984      */
8985     if ((StrStr(message, "unknown host") != NULL)
8986         || (StrStr(message, "No remote directory") != NULL)
8987         || (StrStr(message, "not found") != NULL)
8988         || (StrStr(message, "No such file") != NULL)
8989         || (StrStr(message, "can't alloc") != NULL)
8990         || (StrStr(message, "Permission denied") != NULL)) {
8991
8992         cps->maybeThinking = FALSE;
8993         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8994                 _(cps->which), cps->program, cps->host, message);
8995         RemoveInputSource(cps->isr);
8996         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8997             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8998             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8999         }
9000         return;
9001     }
9002
9003     /*
9004      * Look for hint output
9005      */
9006     if (sscanf(message, "Hint: %s", buf1) == 1) {
9007         if (cps == &first && hintRequested) {
9008             hintRequested = FALSE;
9009             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9010                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9011                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9012                                     PosFlags(forwardMostMove),
9013                                     fromY, fromX, toY, toX, promoChar, buf1);
9014                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9015                 DisplayInformation(buf2);
9016             } else {
9017                 /* Hint move could not be parsed!? */
9018               snprintf(buf2, sizeof(buf2),
9019                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9020                         buf1, _(cps->which));
9021                 DisplayError(buf2, 0);
9022             }
9023         } else {
9024           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9025         }
9026         return;
9027     }
9028
9029     /*
9030      * Ignore other messages if game is not in progress
9031      */
9032     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9033         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9034
9035     /*
9036      * look for win, lose, draw, or draw offer
9037      */
9038     if (strncmp(message, "1-0", 3) == 0) {
9039         char *p, *q, *r = "";
9040         p = strchr(message, '{');
9041         if (p) {
9042             q = strchr(p, '}');
9043             if (q) {
9044                 *q = NULLCHAR;
9045                 r = p + 1;
9046             }
9047         }
9048         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9049         return;
9050     } else if (strncmp(message, "0-1", 3) == 0) {
9051         char *p, *q, *r = "";
9052         p = strchr(message, '{');
9053         if (p) {
9054             q = strchr(p, '}');
9055             if (q) {
9056                 *q = NULLCHAR;
9057                 r = p + 1;
9058             }
9059         }
9060         /* Kludge for Arasan 4.1 bug */
9061         if (strcmp(r, "Black resigns") == 0) {
9062             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9063             return;
9064         }
9065         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9066         return;
9067     } else if (strncmp(message, "1/2", 3) == 0) {
9068         char *p, *q, *r = "";
9069         p = strchr(message, '{');
9070         if (p) {
9071             q = strchr(p, '}');
9072             if (q) {
9073                 *q = NULLCHAR;
9074                 r = p + 1;
9075             }
9076         }
9077
9078         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9079         return;
9080
9081     } else if (strncmp(message, "White resign", 12) == 0) {
9082         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9083         return;
9084     } else if (strncmp(message, "Black resign", 12) == 0) {
9085         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9086         return;
9087     } else if (strncmp(message, "White matches", 13) == 0 ||
9088                strncmp(message, "Black matches", 13) == 0   ) {
9089         /* [HGM] ignore GNUShogi noises */
9090         return;
9091     } else if (strncmp(message, "White", 5) == 0 &&
9092                message[5] != '(' &&
9093                StrStr(message, "Black") == NULL) {
9094         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9095         return;
9096     } else if (strncmp(message, "Black", 5) == 0 &&
9097                message[5] != '(') {
9098         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9099         return;
9100     } else if (strcmp(message, "resign") == 0 ||
9101                strcmp(message, "computer resigns") == 0) {
9102         switch (gameMode) {
9103           case MachinePlaysBlack:
9104           case IcsPlayingBlack:
9105             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9106             break;
9107           case MachinePlaysWhite:
9108           case IcsPlayingWhite:
9109             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9110             break;
9111           case TwoMachinesPlay:
9112             if (cps->twoMachinesColor[0] == 'w')
9113               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9114             else
9115               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9116             break;
9117           default:
9118             /* can't happen */
9119             break;
9120         }
9121         return;
9122     } else if (strncmp(message, "opponent mates", 14) == 0) {
9123         switch (gameMode) {
9124           case MachinePlaysBlack:
9125           case IcsPlayingBlack:
9126             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9127             break;
9128           case MachinePlaysWhite:
9129           case IcsPlayingWhite:
9130             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9131             break;
9132           case TwoMachinesPlay:
9133             if (cps->twoMachinesColor[0] == 'w')
9134               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9135             else
9136               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9137             break;
9138           default:
9139             /* can't happen */
9140             break;
9141         }
9142         return;
9143     } else if (strncmp(message, "computer mates", 14) == 0) {
9144         switch (gameMode) {
9145           case MachinePlaysBlack:
9146           case IcsPlayingBlack:
9147             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9148             break;
9149           case MachinePlaysWhite:
9150           case IcsPlayingWhite:
9151             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9152             break;
9153           case TwoMachinesPlay:
9154             if (cps->twoMachinesColor[0] == 'w')
9155               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9156             else
9157               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9158             break;
9159           default:
9160             /* can't happen */
9161             break;
9162         }
9163         return;
9164     } else if (strncmp(message, "checkmate", 9) == 0) {
9165         if (WhiteOnMove(forwardMostMove)) {
9166             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9167         } else {
9168             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9169         }
9170         return;
9171     } else if (strstr(message, "Draw") != NULL ||
9172                strstr(message, "game is a draw") != NULL) {
9173         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9174         return;
9175     } else if (strstr(message, "offer") != NULL &&
9176                strstr(message, "draw") != NULL) {
9177 #if ZIPPY
9178         if (appData.zippyPlay && first.initDone) {
9179             /* Relay offer to ICS */
9180             SendToICS(ics_prefix);
9181             SendToICS("draw\n");
9182         }
9183 #endif
9184         cps->offeredDraw = 2; /* valid until this engine moves twice */
9185         if (gameMode == TwoMachinesPlay) {
9186             if (cps->other->offeredDraw) {
9187                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9188             /* [HGM] in two-machine mode we delay relaying draw offer      */
9189             /* until after we also have move, to see if it is really claim */
9190             }
9191         } else if (gameMode == MachinePlaysWhite ||
9192                    gameMode == MachinePlaysBlack) {
9193           if (userOfferedDraw) {
9194             DisplayInformation(_("Machine accepts your draw offer"));
9195             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9196           } else {
9197             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9198           }
9199         }
9200     }
9201
9202
9203     /*
9204      * Look for thinking output
9205      */
9206     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9207           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9208                                 ) {
9209         int plylev, mvleft, mvtot, curscore, time;
9210         char mvname[MOVE_LEN];
9211         u64 nodes; // [DM]
9212         char plyext;
9213         int ignore = FALSE;
9214         int prefixHint = FALSE;
9215         mvname[0] = NULLCHAR;
9216
9217         switch (gameMode) {
9218           case MachinePlaysBlack:
9219           case IcsPlayingBlack:
9220             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9221             break;
9222           case MachinePlaysWhite:
9223           case IcsPlayingWhite:
9224             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9225             break;
9226           case AnalyzeMode:
9227           case AnalyzeFile:
9228             break;
9229           case IcsObserving: /* [DM] icsEngineAnalyze */
9230             if (!appData.icsEngineAnalyze) ignore = TRUE;
9231             break;
9232           case TwoMachinesPlay:
9233             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9234                 ignore = TRUE;
9235             }
9236             break;
9237           default:
9238             ignore = TRUE;
9239             break;
9240         }
9241
9242         if (!ignore) {
9243             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9244             buf1[0] = NULLCHAR;
9245             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9246                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9247
9248                 if (plyext != ' ' && plyext != '\t') {
9249                     time *= 100;
9250                 }
9251
9252                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9253                 if( cps->scoreIsAbsolute &&
9254                     ( gameMode == MachinePlaysBlack ||
9255                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9256                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9257                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9258                      !WhiteOnMove(currentMove)
9259                     ) )
9260                 {
9261                     curscore = -curscore;
9262                 }
9263
9264                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9265
9266                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9267                         char buf[MSG_SIZ];
9268                         FILE *f;
9269                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9270                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9271                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9272                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9273                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9274                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9275                                 fclose(f);
9276                         }
9277                         else
9278                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9279                           DisplayError(_("failed writing PV"), 0);
9280                 }
9281
9282                 tempStats.depth = plylev;
9283                 tempStats.nodes = nodes;
9284                 tempStats.time = time;
9285                 tempStats.score = curscore;
9286                 tempStats.got_only_move = 0;
9287
9288                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9289                         int ticklen;
9290
9291                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9292                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9293                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9294                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9295                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9296                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9297                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9298                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9299                 }
9300
9301                 /* Buffer overflow protection */
9302                 if (pv[0] != NULLCHAR) {
9303                     if (strlen(pv) >= sizeof(tempStats.movelist)
9304                         && appData.debugMode) {
9305                         fprintf(debugFP,
9306                                 "PV is too long; using the first %u bytes.\n",
9307                                 (unsigned) sizeof(tempStats.movelist) - 1);
9308                     }
9309
9310                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9311                 } else {
9312                     sprintf(tempStats.movelist, " no PV\n");
9313                 }
9314
9315                 if (tempStats.seen_stat) {
9316                     tempStats.ok_to_send = 1;
9317                 }
9318
9319                 if (strchr(tempStats.movelist, '(') != NULL) {
9320                     tempStats.line_is_book = 1;
9321                     tempStats.nr_moves = 0;
9322                     tempStats.moves_left = 0;
9323                 } else {
9324                     tempStats.line_is_book = 0;
9325                 }
9326
9327                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9328                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9329
9330                 SendProgramStatsToFrontend( cps, &tempStats );
9331
9332                 /*
9333                     [AS] Protect the thinkOutput buffer from overflow... this
9334                     is only useful if buf1 hasn't overflowed first!
9335                 */
9336                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9337                          plylev,
9338                          (gameMode == TwoMachinesPlay ?
9339                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9340                          ((double) curscore) / 100.0,
9341                          prefixHint ? lastHint : "",
9342                          prefixHint ? " " : "" );
9343
9344                 if( buf1[0] != NULLCHAR ) {
9345                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9346
9347                     if( strlen(pv) > max_len ) {
9348                         if( appData.debugMode) {
9349                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9350                         }
9351                         pv[max_len+1] = '\0';
9352                     }
9353
9354                     strcat( thinkOutput, pv);
9355                 }
9356
9357                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9358                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9359                     DisplayMove(currentMove - 1);
9360                 }
9361                 return;
9362
9363             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9364                 /* crafty (9.25+) says "(only move) <move>"
9365                  * if there is only 1 legal move
9366                  */
9367                 sscanf(p, "(only move) %s", buf1);
9368                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9369                 sprintf(programStats.movelist, "%s (only move)", buf1);
9370                 programStats.depth = 1;
9371                 programStats.nr_moves = 1;
9372                 programStats.moves_left = 1;
9373                 programStats.nodes = 1;
9374                 programStats.time = 1;
9375                 programStats.got_only_move = 1;
9376
9377                 /* Not really, but we also use this member to
9378                    mean "line isn't going to change" (Crafty
9379                    isn't searching, so stats won't change) */
9380                 programStats.line_is_book = 1;
9381
9382                 SendProgramStatsToFrontend( cps, &programStats );
9383
9384                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9385                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9386                     DisplayMove(currentMove - 1);
9387                 }
9388                 return;
9389             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9390                               &time, &nodes, &plylev, &mvleft,
9391                               &mvtot, mvname) >= 5) {
9392                 /* The stat01: line is from Crafty (9.29+) in response
9393                    to the "." command */
9394                 programStats.seen_stat = 1;
9395                 cps->maybeThinking = TRUE;
9396
9397                 if (programStats.got_only_move || !appData.periodicUpdates)
9398                   return;
9399
9400                 programStats.depth = plylev;
9401                 programStats.time = time;
9402                 programStats.nodes = nodes;
9403                 programStats.moves_left = mvleft;
9404                 programStats.nr_moves = mvtot;
9405                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9406                 programStats.ok_to_send = 1;
9407                 programStats.movelist[0] = '\0';
9408
9409                 SendProgramStatsToFrontend( cps, &programStats );
9410
9411                 return;
9412
9413             } else if (strncmp(message,"++",2) == 0) {
9414                 /* Crafty 9.29+ outputs this */
9415                 programStats.got_fail = 2;
9416                 return;
9417
9418             } else if (strncmp(message,"--",2) == 0) {
9419                 /* Crafty 9.29+ outputs this */
9420                 programStats.got_fail = 1;
9421                 return;
9422
9423             } else if (thinkOutput[0] != NULLCHAR &&
9424                        strncmp(message, "    ", 4) == 0) {
9425                 unsigned message_len;
9426
9427                 p = message;
9428                 while (*p && *p == ' ') p++;
9429
9430                 message_len = strlen( p );
9431
9432                 /* [AS] Avoid buffer overflow */
9433                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9434                     strcat(thinkOutput, " ");
9435                     strcat(thinkOutput, p);
9436                 }
9437
9438                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9439                     strcat(programStats.movelist, " ");
9440                     strcat(programStats.movelist, p);
9441                 }
9442
9443                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9444                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9445                     DisplayMove(currentMove - 1);
9446                 }
9447                 return;
9448             }
9449         }
9450         else {
9451             buf1[0] = NULLCHAR;
9452
9453             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9454                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9455             {
9456                 ChessProgramStats cpstats;
9457
9458                 if (plyext != ' ' && plyext != '\t') {
9459                     time *= 100;
9460                 }
9461
9462                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9463                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9464                     curscore = -curscore;
9465                 }
9466
9467                 cpstats.depth = plylev;
9468                 cpstats.nodes = nodes;
9469                 cpstats.time = time;
9470                 cpstats.score = curscore;
9471                 cpstats.got_only_move = 0;
9472                 cpstats.movelist[0] = '\0';
9473
9474                 if (buf1[0] != NULLCHAR) {
9475                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9476                 }
9477
9478                 cpstats.ok_to_send = 0;
9479                 cpstats.line_is_book = 0;
9480                 cpstats.nr_moves = 0;
9481                 cpstats.moves_left = 0;
9482
9483                 SendProgramStatsToFrontend( cps, &cpstats );
9484             }
9485         }
9486     }
9487 }
9488
9489
9490 /* Parse a game score from the character string "game", and
9491    record it as the history of the current game.  The game
9492    score is NOT assumed to start from the standard position.
9493    The display is not updated in any way.
9494    */
9495 void
9496 ParseGameHistory (char *game)
9497 {
9498     ChessMove moveType;
9499     int fromX, fromY, toX, toY, boardIndex;
9500     char promoChar;
9501     char *p, *q;
9502     char buf[MSG_SIZ];
9503
9504     if (appData.debugMode)
9505       fprintf(debugFP, "Parsing game history: %s\n", game);
9506
9507     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9508     gameInfo.site = StrSave(appData.icsHost);
9509     gameInfo.date = PGNDate();
9510     gameInfo.round = StrSave("-");
9511
9512     /* Parse out names of players */
9513     while (*game == ' ') game++;
9514     p = buf;
9515     while (*game != ' ') *p++ = *game++;
9516     *p = NULLCHAR;
9517     gameInfo.white = StrSave(buf);
9518     while (*game == ' ') game++;
9519     p = buf;
9520     while (*game != ' ' && *game != '\n') *p++ = *game++;
9521     *p = NULLCHAR;
9522     gameInfo.black = StrSave(buf);
9523
9524     /* Parse moves */
9525     boardIndex = blackPlaysFirst ? 1 : 0;
9526     yynewstr(game);
9527     for (;;) {
9528         yyboardindex = boardIndex;
9529         moveType = (ChessMove) Myylex();
9530         switch (moveType) {
9531           case IllegalMove:             /* maybe suicide chess, etc. */
9532   if (appData.debugMode) {
9533     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9534     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9535     setbuf(debugFP, NULL);
9536   }
9537           case WhitePromotion:
9538           case BlackPromotion:
9539           case WhiteNonPromotion:
9540           case BlackNonPromotion:
9541           case NormalMove:
9542           case FirstLeg:
9543           case WhiteCapturesEnPassant:
9544           case BlackCapturesEnPassant:
9545           case WhiteKingSideCastle:
9546           case WhiteQueenSideCastle:
9547           case BlackKingSideCastle:
9548           case BlackQueenSideCastle:
9549           case WhiteKingSideCastleWild:
9550           case WhiteQueenSideCastleWild:
9551           case BlackKingSideCastleWild:
9552           case BlackQueenSideCastleWild:
9553           /* PUSH Fabien */
9554           case WhiteHSideCastleFR:
9555           case WhiteASideCastleFR:
9556           case BlackHSideCastleFR:
9557           case BlackASideCastleFR:
9558           /* POP Fabien */
9559             fromX = currentMoveString[0] - AAA;
9560             fromY = currentMoveString[1] - ONE;
9561             toX = currentMoveString[2] - AAA;
9562             toY = currentMoveString[3] - ONE;
9563             promoChar = currentMoveString[4];
9564             break;
9565           case WhiteDrop:
9566           case BlackDrop:
9567             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9568             fromX = moveType == WhiteDrop ?
9569               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9570             (int) CharToPiece(ToLower(currentMoveString[0]));
9571             fromY = DROP_RANK;
9572             toX = currentMoveString[2] - AAA;
9573             toY = currentMoveString[3] - ONE;
9574             promoChar = NULLCHAR;
9575             break;
9576           case AmbiguousMove:
9577             /* bug? */
9578             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9579   if (appData.debugMode) {
9580     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9581     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9582     setbuf(debugFP, NULL);
9583   }
9584             DisplayError(buf, 0);
9585             return;
9586           case ImpossibleMove:
9587             /* bug? */
9588             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9589   if (appData.debugMode) {
9590     fprintf(debugFP, "Impossible 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 EndOfFile:
9597             if (boardIndex < backwardMostMove) {
9598                 /* Oops, gap.  How did that happen? */
9599                 DisplayError(_("Gap in move list"), 0);
9600                 return;
9601             }
9602             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9603             if (boardIndex > forwardMostMove) {
9604                 forwardMostMove = boardIndex;
9605             }
9606             return;
9607           case ElapsedTime:
9608             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9609                 strcat(parseList[boardIndex-1], " ");
9610                 strcat(parseList[boardIndex-1], yy_text);
9611             }
9612             continue;
9613           case Comment:
9614           case PGNTag:
9615           case NAG:
9616           default:
9617             /* ignore */
9618             continue;
9619           case WhiteWins:
9620           case BlackWins:
9621           case GameIsDrawn:
9622           case GameUnfinished:
9623             if (gameMode == IcsExamining) {
9624                 if (boardIndex < backwardMostMove) {
9625                     /* Oops, gap.  How did that happen? */
9626                     return;
9627                 }
9628                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9629                 return;
9630             }
9631             gameInfo.result = moveType;
9632             p = strchr(yy_text, '{');
9633             if (p == NULL) p = strchr(yy_text, '(');
9634             if (p == NULL) {
9635                 p = yy_text;
9636                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9637             } else {
9638                 q = strchr(p, *p == '{' ? '}' : ')');
9639                 if (q != NULL) *q = NULLCHAR;
9640                 p++;
9641             }
9642             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9643             gameInfo.resultDetails = StrSave(p);
9644             continue;
9645         }
9646         if (boardIndex >= forwardMostMove &&
9647             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9648             backwardMostMove = blackPlaysFirst ? 1 : 0;
9649             return;
9650         }
9651         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9652                                  fromY, fromX, toY, toX, promoChar,
9653                                  parseList[boardIndex]);
9654         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9655         /* currentMoveString is set as a side-effect of yylex */
9656         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9657         strcat(moveList[boardIndex], "\n");
9658         boardIndex++;
9659         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9660         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9661           case MT_NONE:
9662           case MT_STALEMATE:
9663           default:
9664             break;
9665           case MT_CHECK:
9666             if(gameInfo.variant != VariantShogi)
9667                 strcat(parseList[boardIndex - 1], "+");
9668             break;
9669           case MT_CHECKMATE:
9670           case MT_STAINMATE:
9671             strcat(parseList[boardIndex - 1], "#");
9672             break;
9673         }
9674     }
9675 }
9676
9677
9678 /* Apply a move to the given board  */
9679 void
9680 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9681 {
9682   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9683   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9684
9685     /* [HGM] compute & store e.p. status and castling rights for new position */
9686     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9687
9688       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9689       oldEP = (signed char)board[EP_STATUS];
9690       board[EP_STATUS] = EP_NONE;
9691
9692   if (fromY == DROP_RANK) {
9693         /* must be first */
9694         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9695             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9696             return;
9697         }
9698         piece = board[toY][toX] = (ChessSquare) fromX;
9699   } else {
9700       ChessSquare victim;
9701       int i;
9702
9703       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9704            victim = board[killY][killX],
9705            board[killY][killX] = EmptySquare,
9706            board[EP_STATUS] = EP_CAPTURE;
9707
9708       if( board[toY][toX] != EmptySquare ) {
9709            board[EP_STATUS] = EP_CAPTURE;
9710            if( (fromX != toX || fromY != toY) && // not igui!
9711                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9712                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9713                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9714            }
9715       }
9716
9717       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9718            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9719                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9720       } else
9721       if( board[fromY][fromX] == WhitePawn ) {
9722            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9723                board[EP_STATUS] = EP_PAWN_MOVE;
9724            if( toY-fromY==2) {
9725                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9726                         gameInfo.variant != VariantBerolina || toX < fromX)
9727                       board[EP_STATUS] = toX | berolina;
9728                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9729                         gameInfo.variant != VariantBerolina || toX > fromX)
9730                       board[EP_STATUS] = toX;
9731            }
9732       } else
9733       if( board[fromY][fromX] == BlackPawn ) {
9734            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9735                board[EP_STATUS] = EP_PAWN_MOVE;
9736            if( toY-fromY== -2) {
9737                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9738                         gameInfo.variant != VariantBerolina || toX < fromX)
9739                       board[EP_STATUS] = toX | berolina;
9740                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9741                         gameInfo.variant != VariantBerolina || toX > fromX)
9742                       board[EP_STATUS] = toX;
9743            }
9744        }
9745
9746        for(i=0; i<nrCastlingRights; i++) {
9747            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9748               board[CASTLING][i] == toX   && castlingRank[i] == toY
9749              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9750        }
9751
9752        if(gameInfo.variant == VariantSChess) { // update virginity
9753            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9754            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9755            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9756            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9757        }
9758
9759      if (fromX == toX && fromY == toY) return;
9760
9761      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9762      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9763      if(gameInfo.variant == VariantKnightmate)
9764          king += (int) WhiteUnicorn - (int) WhiteKing;
9765
9766     /* Code added by Tord: */
9767     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9768     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9769         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9770       board[fromY][fromX] = EmptySquare;
9771       board[toY][toX] = EmptySquare;
9772       if((toX > fromX) != (piece == WhiteRook)) {
9773         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9774       } else {
9775         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9776       }
9777     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9778                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9779       board[fromY][fromX] = EmptySquare;
9780       board[toY][toX] = EmptySquare;
9781       if((toX > fromX) != (piece == BlackRook)) {
9782         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9783       } else {
9784         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9785       }
9786     /* End of code added by Tord */
9787
9788     } else if (board[fromY][fromX] == king
9789         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9790         && toY == fromY && toX > fromX+1) {
9791         board[fromY][fromX] = EmptySquare;
9792         board[toY][toX] = king;
9793         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9794         board[fromY][BOARD_RGHT-1] = EmptySquare;
9795     } else if (board[fromY][fromX] == king
9796         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9797                && toY == fromY && toX < fromX-1) {
9798         board[fromY][fromX] = EmptySquare;
9799         board[toY][toX] = king;
9800         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9801         board[fromY][BOARD_LEFT] = EmptySquare;
9802     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9803                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9804                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9805                ) {
9806         /* white pawn promotion */
9807         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9808         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9809             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9810         board[fromY][fromX] = EmptySquare;
9811     } else if ((fromY >= BOARD_HEIGHT>>1)
9812                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9813                && (toX != fromX)
9814                && gameInfo.variant != VariantXiangqi
9815                && gameInfo.variant != VariantBerolina
9816                && (board[fromY][fromX] == WhitePawn)
9817                && (board[toY][toX] == EmptySquare)) {
9818         board[fromY][fromX] = EmptySquare;
9819         board[toY][toX] = WhitePawn;
9820         captured = board[toY - 1][toX];
9821         board[toY - 1][toX] = EmptySquare;
9822     } else if ((fromY == BOARD_HEIGHT-4)
9823                && (toX == fromX)
9824                && gameInfo.variant == VariantBerolina
9825                && (board[fromY][fromX] == WhitePawn)
9826                && (board[toY][toX] == EmptySquare)) {
9827         board[fromY][fromX] = EmptySquare;
9828         board[toY][toX] = WhitePawn;
9829         if(oldEP & EP_BEROLIN_A) {
9830                 captured = board[fromY][fromX-1];
9831                 board[fromY][fromX-1] = EmptySquare;
9832         }else{  captured = board[fromY][fromX+1];
9833                 board[fromY][fromX+1] = EmptySquare;
9834         }
9835     } else if (board[fromY][fromX] == king
9836         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9837                && toY == fromY && toX > fromX+1) {
9838         board[fromY][fromX] = EmptySquare;
9839         board[toY][toX] = king;
9840         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9841         board[fromY][BOARD_RGHT-1] = EmptySquare;
9842     } else if (board[fromY][fromX] == king
9843         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9844                && toY == fromY && toX < fromX-1) {
9845         board[fromY][fromX] = EmptySquare;
9846         board[toY][toX] = king;
9847         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9848         board[fromY][BOARD_LEFT] = EmptySquare;
9849     } else if (fromY == 7 && fromX == 3
9850                && board[fromY][fromX] == BlackKing
9851                && toY == 7 && toX == 5) {
9852         board[fromY][fromX] = EmptySquare;
9853         board[toY][toX] = BlackKing;
9854         board[fromY][7] = EmptySquare;
9855         board[toY][4] = BlackRook;
9856     } else if (fromY == 7 && fromX == 3
9857                && board[fromY][fromX] == BlackKing
9858                && toY == 7 && toX == 1) {
9859         board[fromY][fromX] = EmptySquare;
9860         board[toY][toX] = BlackKing;
9861         board[fromY][0] = EmptySquare;
9862         board[toY][2] = BlackRook;
9863     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9864                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9865                && toY < promoRank && promoChar
9866                ) {
9867         /* black pawn promotion */
9868         board[toY][toX] = CharToPiece(ToLower(promoChar));
9869         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9870             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9871         board[fromY][fromX] = EmptySquare;
9872     } else if ((fromY < BOARD_HEIGHT>>1)
9873                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9874                && (toX != fromX)
9875                && gameInfo.variant != VariantXiangqi
9876                && gameInfo.variant != VariantBerolina
9877                && (board[fromY][fromX] == BlackPawn)
9878                && (board[toY][toX] == EmptySquare)) {
9879         board[fromY][fromX] = EmptySquare;
9880         board[toY][toX] = BlackPawn;
9881         captured = board[toY + 1][toX];
9882         board[toY + 1][toX] = EmptySquare;
9883     } else if ((fromY == 3)
9884                && (toX == fromX)
9885                && gameInfo.variant == VariantBerolina
9886                && (board[fromY][fromX] == BlackPawn)
9887                && (board[toY][toX] == EmptySquare)) {
9888         board[fromY][fromX] = EmptySquare;
9889         board[toY][toX] = BlackPawn;
9890         if(oldEP & EP_BEROLIN_A) {
9891                 captured = board[fromY][fromX-1];
9892                 board[fromY][fromX-1] = EmptySquare;
9893         }else{  captured = board[fromY][fromX+1];
9894                 board[fromY][fromX+1] = EmptySquare;
9895         }
9896     } else {
9897         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9898         board[fromY][fromX] = EmptySquare;
9899         board[toY][toX] = piece;
9900     }
9901   }
9902
9903     if (gameInfo.holdingsWidth != 0) {
9904
9905       /* !!A lot more code needs to be written to support holdings  */
9906       /* [HGM] OK, so I have written it. Holdings are stored in the */
9907       /* penultimate board files, so they are automaticlly stored   */
9908       /* in the game history.                                       */
9909       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9910                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9911         /* Delete from holdings, by decreasing count */
9912         /* and erasing image if necessary            */
9913         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9914         if(p < (int) BlackPawn) { /* white drop */
9915              p -= (int)WhitePawn;
9916                  p = PieceToNumber((ChessSquare)p);
9917              if(p >= gameInfo.holdingsSize) p = 0;
9918              if(--board[p][BOARD_WIDTH-2] <= 0)
9919                   board[p][BOARD_WIDTH-1] = EmptySquare;
9920              if((int)board[p][BOARD_WIDTH-2] < 0)
9921                         board[p][BOARD_WIDTH-2] = 0;
9922         } else {                  /* black drop */
9923              p -= (int)BlackPawn;
9924                  p = PieceToNumber((ChessSquare)p);
9925              if(p >= gameInfo.holdingsSize) p = 0;
9926              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9927                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9928              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9929                         board[BOARD_HEIGHT-1-p][1] = 0;
9930         }
9931       }
9932       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9933           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9934         /* [HGM] holdings: Add to holdings, if holdings exist */
9935         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9936                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9937                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9938         }
9939         p = (int) captured;
9940         if (p >= (int) BlackPawn) {
9941           p -= (int)BlackPawn;
9942           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9943                   /* in Shogi restore piece to its original  first */
9944                   captured = (ChessSquare) (DEMOTED captured);
9945                   p = DEMOTED p;
9946           }
9947           p = PieceToNumber((ChessSquare)p);
9948           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9949           board[p][BOARD_WIDTH-2]++;
9950           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9951         } else {
9952           p -= (int)WhitePawn;
9953           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9954                   captured = (ChessSquare) (DEMOTED captured);
9955                   p = DEMOTED p;
9956           }
9957           p = PieceToNumber((ChessSquare)p);
9958           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9959           board[BOARD_HEIGHT-1-p][1]++;
9960           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9961         }
9962       }
9963     } else if (gameInfo.variant == VariantAtomic) {
9964       if (captured != EmptySquare) {
9965         int y, x;
9966         for (y = toY-1; y <= toY+1; y++) {
9967           for (x = toX-1; x <= toX+1; x++) {
9968             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9969                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9970               board[y][x] = EmptySquare;
9971             }
9972           }
9973         }
9974         board[toY][toX] = EmptySquare;
9975       }
9976     }
9977     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9978         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9979     } else
9980     if(promoChar == '+') {
9981         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9982         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9983         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
9984           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
9985     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9986         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9987         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9988            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9989         board[toY][toX] = newPiece;
9990     }
9991     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9992                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9993         // [HGM] superchess: take promotion piece out of holdings
9994         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9995         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9996             if(!--board[k][BOARD_WIDTH-2])
9997                 board[k][BOARD_WIDTH-1] = EmptySquare;
9998         } else {
9999             if(!--board[BOARD_HEIGHT-1-k][1])
10000                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10001         }
10002     }
10003 }
10004
10005 /* Updates forwardMostMove */
10006 void
10007 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10008 {
10009     int x = toX, y = toY;
10010     char *s = parseList[forwardMostMove];
10011     ChessSquare p = boards[forwardMostMove][toY][toX];
10012 //    forwardMostMove++; // [HGM] bare: moved downstream
10013
10014     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10015     (void) CoordsToAlgebraic(boards[forwardMostMove],
10016                              PosFlags(forwardMostMove),
10017                              fromY, fromX, y, x, promoChar,
10018                              s);
10019     if(killX >= 0 && killY >= 0)
10020         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10021
10022     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10023         int timeLeft; static int lastLoadFlag=0; int king, piece;
10024         piece = boards[forwardMostMove][fromY][fromX];
10025         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10026         if(gameInfo.variant == VariantKnightmate)
10027             king += (int) WhiteUnicorn - (int) WhiteKing;
10028         if(forwardMostMove == 0) {
10029             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10030                 fprintf(serverMoves, "%s;", UserName());
10031             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10032                 fprintf(serverMoves, "%s;", second.tidy);
10033             fprintf(serverMoves, "%s;", first.tidy);
10034             if(gameMode == MachinePlaysWhite)
10035                 fprintf(serverMoves, "%s;", UserName());
10036             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10037                 fprintf(serverMoves, "%s;", second.tidy);
10038         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10039         lastLoadFlag = loadFlag;
10040         // print base move
10041         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10042         // print castling suffix
10043         if( toY == fromY && piece == king ) {
10044             if(toX-fromX > 1)
10045                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10046             if(fromX-toX >1)
10047                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10048         }
10049         // e.p. suffix
10050         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10051              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10052              boards[forwardMostMove][toY][toX] == EmptySquare
10053              && fromX != toX && fromY != toY)
10054                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10055         // promotion suffix
10056         if(promoChar != NULLCHAR) {
10057             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10058                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10059                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10060             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10061         }
10062         if(!loadFlag) {
10063                 char buf[MOVE_LEN*2], *p; int len;
10064             fprintf(serverMoves, "/%d/%d",
10065                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10066             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10067             else                      timeLeft = blackTimeRemaining/1000;
10068             fprintf(serverMoves, "/%d", timeLeft);
10069                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10070                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10071                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10072                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10073             fprintf(serverMoves, "/%s", buf);
10074         }
10075         fflush(serverMoves);
10076     }
10077
10078     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10079         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10080       return;
10081     }
10082     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10083     if (commentList[forwardMostMove+1] != NULL) {
10084         free(commentList[forwardMostMove+1]);
10085         commentList[forwardMostMove+1] = NULL;
10086     }
10087     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10088     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10089     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10090     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10091     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10092     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10093     adjustedClock = FALSE;
10094     gameInfo.result = GameUnfinished;
10095     if (gameInfo.resultDetails != NULL) {
10096         free(gameInfo.resultDetails);
10097         gameInfo.resultDetails = NULL;
10098     }
10099     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10100                               moveList[forwardMostMove - 1]);
10101     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10102       case MT_NONE:
10103       case MT_STALEMATE:
10104       default:
10105         break;
10106       case MT_CHECK:
10107         if(gameInfo.variant != VariantShogi)
10108             strcat(parseList[forwardMostMove - 1], "+");
10109         break;
10110       case MT_CHECKMATE:
10111       case MT_STAINMATE:
10112         strcat(parseList[forwardMostMove - 1], "#");
10113         break;
10114     }
10115 }
10116
10117 /* Updates currentMove if not pausing */
10118 void
10119 ShowMove (int fromX, int fromY, int toX, int toY)
10120 {
10121     int instant = (gameMode == PlayFromGameFile) ?
10122         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10123     if(appData.noGUI) return;
10124     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10125         if (!instant) {
10126             if (forwardMostMove == currentMove + 1) {
10127                 AnimateMove(boards[forwardMostMove - 1],
10128                             fromX, fromY, toX, toY);
10129             }
10130         }
10131         currentMove = forwardMostMove;
10132     }
10133
10134     killX = killY = -1; // [HGM] lion: used up
10135
10136     if (instant) return;
10137
10138     DisplayMove(currentMove - 1);
10139     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10140             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10141                 SetHighlights(fromX, fromY, toX, toY);
10142             }
10143     }
10144     DrawPosition(FALSE, boards[currentMove]);
10145     DisplayBothClocks();
10146     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10147 }
10148
10149 void
10150 SendEgtPath (ChessProgramState *cps)
10151 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10152         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10153
10154         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10155
10156         while(*p) {
10157             char c, *q = name+1, *r, *s;
10158
10159             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10160             while(*p && *p != ',') *q++ = *p++;
10161             *q++ = ':'; *q = 0;
10162             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10163                 strcmp(name, ",nalimov:") == 0 ) {
10164                 // take nalimov path from the menu-changeable option first, if it is defined
10165               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10166                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10167             } else
10168             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10169                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10170                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10171                 s = r = StrStr(s, ":") + 1; // beginning of path info
10172                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10173                 c = *r; *r = 0;             // temporarily null-terminate path info
10174                     *--q = 0;               // strip of trailig ':' from name
10175                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10176                 *r = c;
10177                 SendToProgram(buf,cps);     // send egtbpath command for this format
10178             }
10179             if(*p == ',') p++; // read away comma to position for next format name
10180         }
10181 }
10182
10183 static int
10184 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10185 {
10186       int width = 8, height = 8, holdings = 0;             // most common sizes
10187       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10188       // correct the deviations default for each variant
10189       if( v == VariantXiangqi ) width = 9,  height = 10;
10190       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10191       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10192       if( v == VariantCapablanca || v == VariantCapaRandom ||
10193           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10194                                 width = 10;
10195       if( v == VariantCourier ) width = 12;
10196       if( v == VariantSuper )                            holdings = 8;
10197       if( v == VariantGreat )   width = 10,              holdings = 8;
10198       if( v == VariantSChess )                           holdings = 7;
10199       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10200       if( v == VariantChuChess) width = 10, height = 10;
10201       if( v == VariantChu )     width = 12, height = 12;
10202       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10203              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10204              holdingsSize >= 0 && holdingsSize != holdings;
10205 }
10206
10207 char variantError[MSG_SIZ];
10208
10209 char *
10210 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10211 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10212       char *p, *variant = VariantName(v);
10213       static char b[MSG_SIZ];
10214       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10215            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10216                                                holdingsSize, variant); // cook up sized variant name
10217            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10218            if(StrStr(list, b) == NULL) {
10219                // specific sized variant not known, check if general sizing allowed
10220                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10221                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10222                             boardWidth, boardHeight, holdingsSize, engine);
10223                    return NULL;
10224                }
10225                /* [HGM] here we really should compare with the maximum supported board size */
10226            }
10227       } else snprintf(b, MSG_SIZ,"%s", variant);
10228       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10229       p = StrStr(list, b);
10230       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10231       if(p == NULL) {
10232           // occurs not at all in list, or only as sub-string
10233           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10234           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10235               int l = strlen(variantError);
10236               char *q;
10237               while(p != list && p[-1] != ',') p--;
10238               q = strchr(p, ',');
10239               if(q) *q = NULLCHAR;
10240               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10241               if(q) *q= ',';
10242           }
10243           return NULL;
10244       }
10245       return b;
10246 }
10247
10248 void
10249 InitChessProgram (ChessProgramState *cps, int setup)
10250 /* setup needed to setup FRC opening position */
10251 {
10252     char buf[MSG_SIZ], *b;
10253     if (appData.noChessProgram) return;
10254     hintRequested = FALSE;
10255     bookRequested = FALSE;
10256
10257     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10258     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10259     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10260     if(cps->memSize) { /* [HGM] memory */
10261       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10262         SendToProgram(buf, cps);
10263     }
10264     SendEgtPath(cps); /* [HGM] EGT */
10265     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10266       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10267         SendToProgram(buf, cps);
10268     }
10269
10270     SendToProgram(cps->initString, cps);
10271     if (gameInfo.variant != VariantNormal &&
10272         gameInfo.variant != VariantLoadable
10273         /* [HGM] also send variant if board size non-standard */
10274         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10275
10276       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10277                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10278       if (b == NULL) {
10279         DisplayFatalError(variantError, 0, 1);
10280         return;
10281       }
10282
10283       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10284       SendToProgram(buf, cps);
10285     }
10286     currentlyInitializedVariant = gameInfo.variant;
10287
10288     /* [HGM] send opening position in FRC to first engine */
10289     if(setup) {
10290           SendToProgram("force\n", cps);
10291           SendBoard(cps, 0);
10292           /* engine is now in force mode! Set flag to wake it up after first move. */
10293           setboardSpoiledMachineBlack = 1;
10294     }
10295
10296     if (cps->sendICS) {
10297       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10298       SendToProgram(buf, cps);
10299     }
10300     cps->maybeThinking = FALSE;
10301     cps->offeredDraw = 0;
10302     if (!appData.icsActive) {
10303         SendTimeControl(cps, movesPerSession, timeControl,
10304                         timeIncrement, appData.searchDepth,
10305                         searchTime);
10306     }
10307     if (appData.showThinking
10308         // [HGM] thinking: four options require thinking output to be sent
10309         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10310                                 ) {
10311         SendToProgram("post\n", cps);
10312     }
10313     SendToProgram("hard\n", cps);
10314     if (!appData.ponderNextMove) {
10315         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10316            it without being sure what state we are in first.  "hard"
10317            is not a toggle, so that one is OK.
10318          */
10319         SendToProgram("easy\n", cps);
10320     }
10321     if (cps->usePing) {
10322       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10323       SendToProgram(buf, cps);
10324     }
10325     cps->initDone = TRUE;
10326     ClearEngineOutputPane(cps == &second);
10327 }
10328
10329
10330 void
10331 ResendOptions (ChessProgramState *cps)
10332 { // send the stored value of the options
10333   int i;
10334   char buf[MSG_SIZ];
10335   Option *opt = cps->option;
10336   for(i=0; i<cps->nrOptions; i++, opt++) {
10337       switch(opt->type) {
10338         case Spin:
10339         case Slider:
10340         case CheckBox:
10341             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10342           break;
10343         case ComboBox:
10344           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10345           break;
10346         default:
10347             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10348           break;
10349         case Button:
10350         case SaveButton:
10351           continue;
10352       }
10353       SendToProgram(buf, cps);
10354   }
10355 }
10356
10357 void
10358 StartChessProgram (ChessProgramState *cps)
10359 {
10360     char buf[MSG_SIZ];
10361     int err;
10362
10363     if (appData.noChessProgram) return;
10364     cps->initDone = FALSE;
10365
10366     if (strcmp(cps->host, "localhost") == 0) {
10367         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10368     } else if (*appData.remoteShell == NULLCHAR) {
10369         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10370     } else {
10371         if (*appData.remoteUser == NULLCHAR) {
10372           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10373                     cps->program);
10374         } else {
10375           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10376                     cps->host, appData.remoteUser, cps->program);
10377         }
10378         err = StartChildProcess(buf, "", &cps->pr);
10379     }
10380
10381     if (err != 0) {
10382       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10383         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10384         if(cps != &first) return;
10385         appData.noChessProgram = TRUE;
10386         ThawUI();
10387         SetNCPMode();
10388 //      DisplayFatalError(buf, err, 1);
10389 //      cps->pr = NoProc;
10390 //      cps->isr = NULL;
10391         return;
10392     }
10393
10394     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10395     if (cps->protocolVersion > 1) {
10396       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10397       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10398         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10399         cps->comboCnt = 0;  //                and values of combo boxes
10400       }
10401       SendToProgram(buf, cps);
10402       if(cps->reload) ResendOptions(cps);
10403     } else {
10404       SendToProgram("xboard\n", cps);
10405     }
10406 }
10407
10408 void
10409 TwoMachinesEventIfReady P((void))
10410 {
10411   static int curMess = 0;
10412   if (first.lastPing != first.lastPong) {
10413     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10414     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10415     return;
10416   }
10417   if (second.lastPing != second.lastPong) {
10418     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10419     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10420     return;
10421   }
10422   DisplayMessage("", ""); curMess = 0;
10423   TwoMachinesEvent();
10424 }
10425
10426 char *
10427 MakeName (char *template)
10428 {
10429     time_t clock;
10430     struct tm *tm;
10431     static char buf[MSG_SIZ];
10432     char *p = buf;
10433     int i;
10434
10435     clock = time((time_t *)NULL);
10436     tm = localtime(&clock);
10437
10438     while(*p++ = *template++) if(p[-1] == '%') {
10439         switch(*template++) {
10440           case 0:   *p = 0; return buf;
10441           case 'Y': i = tm->tm_year+1900; break;
10442           case 'y': i = tm->tm_year-100; break;
10443           case 'M': i = tm->tm_mon+1; break;
10444           case 'd': i = tm->tm_mday; break;
10445           case 'h': i = tm->tm_hour; break;
10446           case 'm': i = tm->tm_min; break;
10447           case 's': i = tm->tm_sec; break;
10448           default:  i = 0;
10449         }
10450         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10451     }
10452     return buf;
10453 }
10454
10455 int
10456 CountPlayers (char *p)
10457 {
10458     int n = 0;
10459     while(p = strchr(p, '\n')) p++, n++; // count participants
10460     return n;
10461 }
10462
10463 FILE *
10464 WriteTourneyFile (char *results, FILE *f)
10465 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10466     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10467     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10468         // create a file with tournament description
10469         fprintf(f, "-participants {%s}\n", appData.participants);
10470         fprintf(f, "-seedBase %d\n", appData.seedBase);
10471         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10472         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10473         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10474         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10475         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10476         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10477         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10478         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10479         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10480         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10481         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10482         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10483         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10484         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10485         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10486         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10487         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10488         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10489         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10490         fprintf(f, "-smpCores %d\n", appData.smpCores);
10491         if(searchTime > 0)
10492                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10493         else {
10494                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10495                 fprintf(f, "-tc %s\n", appData.timeControl);
10496                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10497         }
10498         fprintf(f, "-results \"%s\"\n", results);
10499     }
10500     return f;
10501 }
10502
10503 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10504
10505 void
10506 Substitute (char *participants, int expunge)
10507 {
10508     int i, changed, changes=0, nPlayers=0;
10509     char *p, *q, *r, buf[MSG_SIZ];
10510     if(participants == NULL) return;
10511     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10512     r = p = participants; q = appData.participants;
10513     while(*p && *p == *q) {
10514         if(*p == '\n') r = p+1, nPlayers++;
10515         p++; q++;
10516     }
10517     if(*p) { // difference
10518         while(*p && *p++ != '\n');
10519         while(*q && *q++ != '\n');
10520       changed = nPlayers;
10521         changes = 1 + (strcmp(p, q) != 0);
10522     }
10523     if(changes == 1) { // a single engine mnemonic was changed
10524         q = r; while(*q) nPlayers += (*q++ == '\n');
10525         p = buf; while(*r && (*p = *r++) != '\n') p++;
10526         *p = NULLCHAR;
10527         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10528         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10529         if(mnemonic[i]) { // The substitute is valid
10530             FILE *f;
10531             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10532                 flock(fileno(f), LOCK_EX);
10533                 ParseArgsFromFile(f);
10534                 fseek(f, 0, SEEK_SET);
10535                 FREE(appData.participants); appData.participants = participants;
10536                 if(expunge) { // erase results of replaced engine
10537                     int len = strlen(appData.results), w, b, dummy;
10538                     for(i=0; i<len; i++) {
10539                         Pairing(i, nPlayers, &w, &b, &dummy);
10540                         if((w == changed || b == changed) && appData.results[i] == '*') {
10541                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10542                             fclose(f);
10543                             return;
10544                         }
10545                     }
10546                     for(i=0; i<len; i++) {
10547                         Pairing(i, nPlayers, &w, &b, &dummy);
10548                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10549                     }
10550                 }
10551                 WriteTourneyFile(appData.results, f);
10552                 fclose(f); // release lock
10553                 return;
10554             }
10555         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10556     }
10557     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10558     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10559     free(participants);
10560     return;
10561 }
10562
10563 int
10564 CheckPlayers (char *participants)
10565 {
10566         int i;
10567         char buf[MSG_SIZ], *p;
10568         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10569         while(p = strchr(participants, '\n')) {
10570             *p = NULLCHAR;
10571             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10572             if(!mnemonic[i]) {
10573                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10574                 *p = '\n';
10575                 DisplayError(buf, 0);
10576                 return 1;
10577             }
10578             *p = '\n';
10579             participants = p + 1;
10580         }
10581         return 0;
10582 }
10583
10584 int
10585 CreateTourney (char *name)
10586 {
10587         FILE *f;
10588         if(matchMode && strcmp(name, appData.tourneyFile)) {
10589              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10590         }
10591         if(name[0] == NULLCHAR) {
10592             if(appData.participants[0])
10593                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10594             return 0;
10595         }
10596         f = fopen(name, "r");
10597         if(f) { // file exists
10598             ASSIGN(appData.tourneyFile, name);
10599             ParseArgsFromFile(f); // parse it
10600         } else {
10601             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10602             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10603                 DisplayError(_("Not enough participants"), 0);
10604                 return 0;
10605             }
10606             if(CheckPlayers(appData.participants)) return 0;
10607             ASSIGN(appData.tourneyFile, name);
10608             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10609             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10610         }
10611         fclose(f);
10612         appData.noChessProgram = FALSE;
10613         appData.clockMode = TRUE;
10614         SetGNUMode();
10615         return 1;
10616 }
10617
10618 int
10619 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10620 {
10621     char buf[MSG_SIZ], *p, *q;
10622     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10623     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10624     skip = !all && group[0]; // if group requested, we start in skip mode
10625     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10626         p = names; q = buf; header = 0;
10627         while(*p && *p != '\n') *q++ = *p++;
10628         *q = 0;
10629         if(*p == '\n') p++;
10630         if(buf[0] == '#') {
10631             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10632             depth++; // we must be entering a new group
10633             if(all) continue; // suppress printing group headers when complete list requested
10634             header = 1;
10635             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10636         }
10637         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10638         if(engineList[i]) free(engineList[i]);
10639         engineList[i] = strdup(buf);
10640         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10641         if(engineMnemonic[i]) free(engineMnemonic[i]);
10642         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10643             strcat(buf, " (");
10644             sscanf(q + 8, "%s", buf + strlen(buf));
10645             strcat(buf, ")");
10646         }
10647         engineMnemonic[i] = strdup(buf);
10648         i++;
10649     }
10650     engineList[i] = engineMnemonic[i] = NULL;
10651     return i;
10652 }
10653
10654 // following implemented as macro to avoid type limitations
10655 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10656
10657 void
10658 SwapEngines (int n)
10659 {   // swap settings for first engine and other engine (so far only some selected options)
10660     int h;
10661     char *p;
10662     if(n == 0) return;
10663     SWAP(directory, p)
10664     SWAP(chessProgram, p)
10665     SWAP(isUCI, h)
10666     SWAP(hasOwnBookUCI, h)
10667     SWAP(protocolVersion, h)
10668     SWAP(reuse, h)
10669     SWAP(scoreIsAbsolute, h)
10670     SWAP(timeOdds, h)
10671     SWAP(logo, p)
10672     SWAP(pgnName, p)
10673     SWAP(pvSAN, h)
10674     SWAP(engOptions, p)
10675     SWAP(engInitString, p)
10676     SWAP(computerString, p)
10677     SWAP(features, p)
10678     SWAP(fenOverride, p)
10679     SWAP(NPS, h)
10680     SWAP(accumulateTC, h)
10681     SWAP(host, p)
10682 }
10683
10684 int
10685 GetEngineLine (char *s, int n)
10686 {
10687     int i;
10688     char buf[MSG_SIZ];
10689     extern char *icsNames;
10690     if(!s || !*s) return 0;
10691     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10692     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10693     if(!mnemonic[i]) return 0;
10694     if(n == 11) return 1; // just testing if there was a match
10695     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10696     if(n == 1) SwapEngines(n);
10697     ParseArgsFromString(buf);
10698     if(n == 1) SwapEngines(n);
10699     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10700         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10701         ParseArgsFromString(buf);
10702     }
10703     return 1;
10704 }
10705
10706 int
10707 SetPlayer (int player, char *p)
10708 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10709     int i;
10710     char buf[MSG_SIZ], *engineName;
10711     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10712     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10713     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10714     if(mnemonic[i]) {
10715         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10716         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10717         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10718         ParseArgsFromString(buf);
10719     } else { // no engine with this nickname is installed!
10720         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10721         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10722         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10723         ModeHighlight();
10724         DisplayError(buf, 0);
10725         return 0;
10726     }
10727     free(engineName);
10728     return i;
10729 }
10730
10731 char *recentEngines;
10732
10733 void
10734 RecentEngineEvent (int nr)
10735 {
10736     int n;
10737 //    SwapEngines(1); // bump first to second
10738 //    ReplaceEngine(&second, 1); // and load it there
10739     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10740     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10741     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10742         ReplaceEngine(&first, 0);
10743         FloatToFront(&appData.recentEngineList, command[n]);
10744     }
10745 }
10746
10747 int
10748 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10749 {   // determine players from game number
10750     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10751
10752     if(appData.tourneyType == 0) {
10753         roundsPerCycle = (nPlayers - 1) | 1;
10754         pairingsPerRound = nPlayers / 2;
10755     } else if(appData.tourneyType > 0) {
10756         roundsPerCycle = nPlayers - appData.tourneyType;
10757         pairingsPerRound = appData.tourneyType;
10758     }
10759     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10760     gamesPerCycle = gamesPerRound * roundsPerCycle;
10761     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10762     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10763     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10764     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10765     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10766     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10767
10768     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10769     if(appData.roundSync) *syncInterval = gamesPerRound;
10770
10771     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10772
10773     if(appData.tourneyType == 0) {
10774         if(curPairing == (nPlayers-1)/2 ) {
10775             *whitePlayer = curRound;
10776             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10777         } else {
10778             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10779             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10780             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10781             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10782         }
10783     } else if(appData.tourneyType > 1) {
10784         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10785         *whitePlayer = curRound + appData.tourneyType;
10786     } else if(appData.tourneyType > 0) {
10787         *whitePlayer = curPairing;
10788         *blackPlayer = curRound + appData.tourneyType;
10789     }
10790
10791     // take care of white/black alternation per round.
10792     // For cycles and games this is already taken care of by default, derived from matchGame!
10793     return curRound & 1;
10794 }
10795
10796 int
10797 NextTourneyGame (int nr, int *swapColors)
10798 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10799     char *p, *q;
10800     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10801     FILE *tf;
10802     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10803     tf = fopen(appData.tourneyFile, "r");
10804     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10805     ParseArgsFromFile(tf); fclose(tf);
10806     InitTimeControls(); // TC might be altered from tourney file
10807
10808     nPlayers = CountPlayers(appData.participants); // count participants
10809     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10810     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10811
10812     if(syncInterval) {
10813         p = q = appData.results;
10814         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10815         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10816             DisplayMessage(_("Waiting for other game(s)"),"");
10817             waitingForGame = TRUE;
10818             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10819             return 0;
10820         }
10821         waitingForGame = FALSE;
10822     }
10823
10824     if(appData.tourneyType < 0) {
10825         if(nr>=0 && !pairingReceived) {
10826             char buf[1<<16];
10827             if(pairing.pr == NoProc) {
10828                 if(!appData.pairingEngine[0]) {
10829                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10830                     return 0;
10831                 }
10832                 StartChessProgram(&pairing); // starts the pairing engine
10833             }
10834             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10835             SendToProgram(buf, &pairing);
10836             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10837             SendToProgram(buf, &pairing);
10838             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10839         }
10840         pairingReceived = 0;                              // ... so we continue here
10841         *swapColors = 0;
10842         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10843         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10844         matchGame = 1; roundNr = nr / syncInterval + 1;
10845     }
10846
10847     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10848
10849     // redefine engines, engine dir, etc.
10850     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10851     if(first.pr == NoProc) {
10852       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10853       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10854     }
10855     if(second.pr == NoProc) {
10856       SwapEngines(1);
10857       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10858       SwapEngines(1);         // and make that valid for second engine by swapping
10859       InitEngine(&second, 1);
10860     }
10861     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10862     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10863     return OK;
10864 }
10865
10866 void
10867 NextMatchGame ()
10868 {   // performs game initialization that does not invoke engines, and then tries to start the game
10869     int res, firstWhite, swapColors = 0;
10870     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10871     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
10872         char buf[MSG_SIZ];
10873         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10874         if(strcmp(buf, currentDebugFile)) { // name has changed
10875             FILE *f = fopen(buf, "w");
10876             if(f) { // if opening the new file failed, just keep using the old one
10877                 ASSIGN(currentDebugFile, buf);
10878                 fclose(debugFP);
10879                 debugFP = f;
10880             }
10881             if(appData.serverFileName) {
10882                 if(serverFP) fclose(serverFP);
10883                 serverFP = fopen(appData.serverFileName, "w");
10884                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10885                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10886             }
10887         }
10888     }
10889     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10890     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10891     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10892     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10893     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10894     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10895     Reset(FALSE, first.pr != NoProc);
10896     res = LoadGameOrPosition(matchGame); // setup game
10897     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10898     if(!res) return; // abort when bad game/pos file
10899     TwoMachinesEvent();
10900 }
10901
10902 void
10903 UserAdjudicationEvent (int result)
10904 {
10905     ChessMove gameResult = GameIsDrawn;
10906
10907     if( result > 0 ) {
10908         gameResult = WhiteWins;
10909     }
10910     else if( result < 0 ) {
10911         gameResult = BlackWins;
10912     }
10913
10914     if( gameMode == TwoMachinesPlay ) {
10915         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10916     }
10917 }
10918
10919
10920 // [HGM] save: calculate checksum of game to make games easily identifiable
10921 int
10922 StringCheckSum (char *s)
10923 {
10924         int i = 0;
10925         if(s==NULL) return 0;
10926         while(*s) i = i*259 + *s++;
10927         return i;
10928 }
10929
10930 int
10931 GameCheckSum ()
10932 {
10933         int i, sum=0;
10934         for(i=backwardMostMove; i<forwardMostMove; i++) {
10935                 sum += pvInfoList[i].depth;
10936                 sum += StringCheckSum(parseList[i]);
10937                 sum += StringCheckSum(commentList[i]);
10938                 sum *= 261;
10939         }
10940         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10941         return sum + StringCheckSum(commentList[i]);
10942 } // end of save patch
10943
10944 void
10945 GameEnds (ChessMove result, char *resultDetails, int whosays)
10946 {
10947     GameMode nextGameMode;
10948     int isIcsGame;
10949     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10950
10951     if(endingGame) return; /* [HGM] crash: forbid recursion */
10952     endingGame = 1;
10953     if(twoBoards) { // [HGM] dual: switch back to one board
10954         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10955         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10956     }
10957     if (appData.debugMode) {
10958       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10959               result, resultDetails ? resultDetails : "(null)", whosays);
10960     }
10961
10962     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10963
10964     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10965
10966     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10967         /* If we are playing on ICS, the server decides when the
10968            game is over, but the engine can offer to draw, claim
10969            a draw, or resign.
10970          */
10971 #if ZIPPY
10972         if (appData.zippyPlay && first.initDone) {
10973             if (result == GameIsDrawn) {
10974                 /* In case draw still needs to be claimed */
10975                 SendToICS(ics_prefix);
10976                 SendToICS("draw\n");
10977             } else if (StrCaseStr(resultDetails, "resign")) {
10978                 SendToICS(ics_prefix);
10979                 SendToICS("resign\n");
10980             }
10981         }
10982 #endif
10983         endingGame = 0; /* [HGM] crash */
10984         return;
10985     }
10986
10987     /* If we're loading the game from a file, stop */
10988     if (whosays == GE_FILE) {
10989       (void) StopLoadGameTimer();
10990       gameFileFP = NULL;
10991     }
10992
10993     /* Cancel draw offers */
10994     first.offeredDraw = second.offeredDraw = 0;
10995
10996     /* If this is an ICS game, only ICS can really say it's done;
10997        if not, anyone can. */
10998     isIcsGame = (gameMode == IcsPlayingWhite ||
10999                  gameMode == IcsPlayingBlack ||
11000                  gameMode == IcsObserving    ||
11001                  gameMode == IcsExamining);
11002
11003     if (!isIcsGame || whosays == GE_ICS) {
11004         /* OK -- not an ICS game, or ICS said it was done */
11005         StopClocks();
11006         if (!isIcsGame && !appData.noChessProgram)
11007           SetUserThinkingEnables();
11008
11009         /* [HGM] if a machine claims the game end we verify this claim */
11010         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11011             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11012                 char claimer;
11013                 ChessMove trueResult = (ChessMove) -1;
11014
11015                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11016                                             first.twoMachinesColor[0] :
11017                                             second.twoMachinesColor[0] ;
11018
11019                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11020                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11021                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11022                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11023                 } else
11024                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11025                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11026                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11027                 } else
11028                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11029                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11030                 }
11031
11032                 // now verify win claims, but not in drop games, as we don't understand those yet
11033                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11034                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11035                     (result == WhiteWins && claimer == 'w' ||
11036                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11037                       if (appData.debugMode) {
11038                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11039                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11040                       }
11041                       if(result != trueResult) {
11042                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11043                               result = claimer == 'w' ? BlackWins : WhiteWins;
11044                               resultDetails = buf;
11045                       }
11046                 } else
11047                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11048                     && (forwardMostMove <= backwardMostMove ||
11049                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11050                         (claimer=='b')==(forwardMostMove&1))
11051                                                                                   ) {
11052                       /* [HGM] verify: draws that were not flagged are false claims */
11053                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11054                       result = claimer == 'w' ? BlackWins : WhiteWins;
11055                       resultDetails = buf;
11056                 }
11057                 /* (Claiming a loss is accepted no questions asked!) */
11058             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11059                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11060                 result = GameUnfinished;
11061                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11062             }
11063             /* [HGM] bare: don't allow bare King to win */
11064             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11065                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11066                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11067                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11068                && result != GameIsDrawn)
11069             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11070                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11071                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11072                         if(p >= 0 && p <= (int)WhiteKing) k++;
11073                 }
11074                 if (appData.debugMode) {
11075                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11076                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11077                 }
11078                 if(k <= 1) {
11079                         result = GameIsDrawn;
11080                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11081                         resultDetails = buf;
11082                 }
11083             }
11084         }
11085
11086
11087         if(serverMoves != NULL && !loadFlag) { char c = '=';
11088             if(result==WhiteWins) c = '+';
11089             if(result==BlackWins) c = '-';
11090             if(resultDetails != NULL)
11091                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11092         }
11093         if (resultDetails != NULL) {
11094             gameInfo.result = result;
11095             gameInfo.resultDetails = StrSave(resultDetails);
11096
11097             /* display last move only if game was not loaded from file */
11098             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11099                 DisplayMove(currentMove - 1);
11100
11101             if (forwardMostMove != 0) {
11102                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11103                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11104                                                                 ) {
11105                     if (*appData.saveGameFile != NULLCHAR) {
11106                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11107                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11108                         else
11109                         SaveGameToFile(appData.saveGameFile, TRUE);
11110                     } else if (appData.autoSaveGames) {
11111                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11112                     }
11113                     if (*appData.savePositionFile != NULLCHAR) {
11114                         SavePositionToFile(appData.savePositionFile);
11115                     }
11116                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11117                 }
11118             }
11119
11120             /* Tell program how game ended in case it is learning */
11121             /* [HGM] Moved this to after saving the PGN, just in case */
11122             /* engine died and we got here through time loss. In that */
11123             /* case we will get a fatal error writing the pipe, which */
11124             /* would otherwise lose us the PGN.                       */
11125             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11126             /* output during GameEnds should never be fatal anymore   */
11127             if (gameMode == MachinePlaysWhite ||
11128                 gameMode == MachinePlaysBlack ||
11129                 gameMode == TwoMachinesPlay ||
11130                 gameMode == IcsPlayingWhite ||
11131                 gameMode == IcsPlayingBlack ||
11132                 gameMode == BeginningOfGame) {
11133                 char buf[MSG_SIZ];
11134                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11135                         resultDetails);
11136                 if (first.pr != NoProc) {
11137                     SendToProgram(buf, &first);
11138                 }
11139                 if (second.pr != NoProc &&
11140                     gameMode == TwoMachinesPlay) {
11141                     SendToProgram(buf, &second);
11142                 }
11143             }
11144         }
11145
11146         if (appData.icsActive) {
11147             if (appData.quietPlay &&
11148                 (gameMode == IcsPlayingWhite ||
11149                  gameMode == IcsPlayingBlack)) {
11150                 SendToICS(ics_prefix);
11151                 SendToICS("set shout 1\n");
11152             }
11153             nextGameMode = IcsIdle;
11154             ics_user_moved = FALSE;
11155             /* clean up premove.  It's ugly when the game has ended and the
11156              * premove highlights are still on the board.
11157              */
11158             if (gotPremove) {
11159               gotPremove = FALSE;
11160               ClearPremoveHighlights();
11161               DrawPosition(FALSE, boards[currentMove]);
11162             }
11163             if (whosays == GE_ICS) {
11164                 switch (result) {
11165                 case WhiteWins:
11166                     if (gameMode == IcsPlayingWhite)
11167                         PlayIcsWinSound();
11168                     else if(gameMode == IcsPlayingBlack)
11169                         PlayIcsLossSound();
11170                     break;
11171                 case BlackWins:
11172                     if (gameMode == IcsPlayingBlack)
11173                         PlayIcsWinSound();
11174                     else if(gameMode == IcsPlayingWhite)
11175                         PlayIcsLossSound();
11176                     break;
11177                 case GameIsDrawn:
11178                     PlayIcsDrawSound();
11179                     break;
11180                 default:
11181                     PlayIcsUnfinishedSound();
11182                 }
11183             }
11184             if(appData.quitNext) { ExitEvent(0); return; }
11185         } else if (gameMode == EditGame ||
11186                    gameMode == PlayFromGameFile ||
11187                    gameMode == AnalyzeMode ||
11188                    gameMode == AnalyzeFile) {
11189             nextGameMode = gameMode;
11190         } else {
11191             nextGameMode = EndOfGame;
11192         }
11193         pausing = FALSE;
11194         ModeHighlight();
11195     } else {
11196         nextGameMode = gameMode;
11197     }
11198
11199     if (appData.noChessProgram) {
11200         gameMode = nextGameMode;
11201         ModeHighlight();
11202         endingGame = 0; /* [HGM] crash */
11203         return;
11204     }
11205
11206     if (first.reuse) {
11207         /* Put first chess program into idle state */
11208         if (first.pr != NoProc &&
11209             (gameMode == MachinePlaysWhite ||
11210              gameMode == MachinePlaysBlack ||
11211              gameMode == TwoMachinesPlay ||
11212              gameMode == IcsPlayingWhite ||
11213              gameMode == IcsPlayingBlack ||
11214              gameMode == BeginningOfGame)) {
11215             SendToProgram("force\n", &first);
11216             if (first.usePing) {
11217               char buf[MSG_SIZ];
11218               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11219               SendToProgram(buf, &first);
11220             }
11221         }
11222     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11223         /* Kill off first chess program */
11224         if (first.isr != NULL)
11225           RemoveInputSource(first.isr);
11226         first.isr = NULL;
11227
11228         if (first.pr != NoProc) {
11229             ExitAnalyzeMode();
11230             DoSleep( appData.delayBeforeQuit );
11231             SendToProgram("quit\n", &first);
11232             DoSleep( appData.delayAfterQuit );
11233             DestroyChildProcess(first.pr, first.useSigterm);
11234             first.reload = TRUE;
11235         }
11236         first.pr = NoProc;
11237     }
11238     if (second.reuse) {
11239         /* Put second chess program into idle state */
11240         if (second.pr != NoProc &&
11241             gameMode == TwoMachinesPlay) {
11242             SendToProgram("force\n", &second);
11243             if (second.usePing) {
11244               char buf[MSG_SIZ];
11245               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11246               SendToProgram(buf, &second);
11247             }
11248         }
11249     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11250         /* Kill off second chess program */
11251         if (second.isr != NULL)
11252           RemoveInputSource(second.isr);
11253         second.isr = NULL;
11254
11255         if (second.pr != NoProc) {
11256             DoSleep( appData.delayBeforeQuit );
11257             SendToProgram("quit\n", &second);
11258             DoSleep( appData.delayAfterQuit );
11259             DestroyChildProcess(second.pr, second.useSigterm);
11260             second.reload = TRUE;
11261         }
11262         second.pr = NoProc;
11263     }
11264
11265     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11266         char resChar = '=';
11267         switch (result) {
11268         case WhiteWins:
11269           resChar = '+';
11270           if (first.twoMachinesColor[0] == 'w') {
11271             first.matchWins++;
11272           } else {
11273             second.matchWins++;
11274           }
11275           break;
11276         case BlackWins:
11277           resChar = '-';
11278           if (first.twoMachinesColor[0] == 'b') {
11279             first.matchWins++;
11280           } else {
11281             second.matchWins++;
11282           }
11283           break;
11284         case GameUnfinished:
11285           resChar = ' ';
11286         default:
11287           break;
11288         }
11289
11290         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11291         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11292             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11293             ReserveGame(nextGame, resChar); // sets nextGame
11294             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11295             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11296         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11297
11298         if (nextGame <= appData.matchGames && !abortMatch) {
11299             gameMode = nextGameMode;
11300             matchGame = nextGame; // this will be overruled in tourney mode!
11301             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11302             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11303             endingGame = 0; /* [HGM] crash */
11304             return;
11305         } else {
11306             gameMode = nextGameMode;
11307             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11308                      first.tidy, second.tidy,
11309                      first.matchWins, second.matchWins,
11310                      appData.matchGames - (first.matchWins + second.matchWins));
11311             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11312             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11313             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11314             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11315                 first.twoMachinesColor = "black\n";
11316                 second.twoMachinesColor = "white\n";
11317             } else {
11318                 first.twoMachinesColor = "white\n";
11319                 second.twoMachinesColor = "black\n";
11320             }
11321         }
11322     }
11323     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11324         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11325       ExitAnalyzeMode();
11326     gameMode = nextGameMode;
11327     ModeHighlight();
11328     endingGame = 0;  /* [HGM] crash */
11329     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11330         if(matchMode == TRUE) { // match through command line: exit with or without popup
11331             if(ranking) {
11332                 ToNrEvent(forwardMostMove);
11333                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11334                 else ExitEvent(0);
11335             } else DisplayFatalError(buf, 0, 0);
11336         } else { // match through menu; just stop, with or without popup
11337             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11338             ModeHighlight();
11339             if(ranking){
11340                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11341             } else DisplayNote(buf);
11342       }
11343       if(ranking) free(ranking);
11344     }
11345 }
11346
11347 /* Assumes program was just initialized (initString sent).
11348    Leaves program in force mode. */
11349 void
11350 FeedMovesToProgram (ChessProgramState *cps, int upto)
11351 {
11352     int i;
11353
11354     if (appData.debugMode)
11355       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11356               startedFromSetupPosition ? "position and " : "",
11357               backwardMostMove, upto, cps->which);
11358     if(currentlyInitializedVariant != gameInfo.variant) {
11359       char buf[MSG_SIZ];
11360         // [HGM] variantswitch: make engine aware of new variant
11361         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11362                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11363                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11364         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11365         SendToProgram(buf, cps);
11366         currentlyInitializedVariant = gameInfo.variant;
11367     }
11368     SendToProgram("force\n", cps);
11369     if (startedFromSetupPosition) {
11370         SendBoard(cps, backwardMostMove);
11371     if (appData.debugMode) {
11372         fprintf(debugFP, "feedMoves\n");
11373     }
11374     }
11375     for (i = backwardMostMove; i < upto; i++) {
11376         SendMoveToProgram(i, cps);
11377     }
11378 }
11379
11380
11381 int
11382 ResurrectChessProgram ()
11383 {
11384      /* The chess program may have exited.
11385         If so, restart it and feed it all the moves made so far. */
11386     static int doInit = 0;
11387
11388     if (appData.noChessProgram) return 1;
11389
11390     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11391         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11392         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11393         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11394     } else {
11395         if (first.pr != NoProc) return 1;
11396         StartChessProgram(&first);
11397     }
11398     InitChessProgram(&first, FALSE);
11399     FeedMovesToProgram(&first, currentMove);
11400
11401     if (!first.sendTime) {
11402         /* can't tell gnuchess what its clock should read,
11403            so we bow to its notion. */
11404         ResetClocks();
11405         timeRemaining[0][currentMove] = whiteTimeRemaining;
11406         timeRemaining[1][currentMove] = blackTimeRemaining;
11407     }
11408
11409     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11410                 appData.icsEngineAnalyze) && first.analysisSupport) {
11411       SendToProgram("analyze\n", &first);
11412       first.analyzing = TRUE;
11413     }
11414     return 1;
11415 }
11416
11417 /*
11418  * Button procedures
11419  */
11420 void
11421 Reset (int redraw, int init)
11422 {
11423     int i;
11424
11425     if (appData.debugMode) {
11426         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11427                 redraw, init, gameMode);
11428     }
11429     CleanupTail(); // [HGM] vari: delete any stored variations
11430     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11431     pausing = pauseExamInvalid = FALSE;
11432     startedFromSetupPosition = blackPlaysFirst = FALSE;
11433     firstMove = TRUE;
11434     whiteFlag = blackFlag = FALSE;
11435     userOfferedDraw = FALSE;
11436     hintRequested = bookRequested = FALSE;
11437     first.maybeThinking = FALSE;
11438     second.maybeThinking = FALSE;
11439     first.bookSuspend = FALSE; // [HGM] book
11440     second.bookSuspend = FALSE;
11441     thinkOutput[0] = NULLCHAR;
11442     lastHint[0] = NULLCHAR;
11443     ClearGameInfo(&gameInfo);
11444     gameInfo.variant = StringToVariant(appData.variant);
11445     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11446     ics_user_moved = ics_clock_paused = FALSE;
11447     ics_getting_history = H_FALSE;
11448     ics_gamenum = -1;
11449     white_holding[0] = black_holding[0] = NULLCHAR;
11450     ClearProgramStats();
11451     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11452
11453     ResetFrontEnd();
11454     ClearHighlights();
11455     flipView = appData.flipView;
11456     ClearPremoveHighlights();
11457     gotPremove = FALSE;
11458     alarmSounded = FALSE;
11459     killX = killY = -1; // [HGM] lion
11460
11461     GameEnds(EndOfFile, NULL, GE_PLAYER);
11462     if(appData.serverMovesName != NULL) {
11463         /* [HGM] prepare to make moves file for broadcasting */
11464         clock_t t = clock();
11465         if(serverMoves != NULL) fclose(serverMoves);
11466         serverMoves = fopen(appData.serverMovesName, "r");
11467         if(serverMoves != NULL) {
11468             fclose(serverMoves);
11469             /* delay 15 sec before overwriting, so all clients can see end */
11470             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11471         }
11472         serverMoves = fopen(appData.serverMovesName, "w");
11473     }
11474
11475     ExitAnalyzeMode();
11476     gameMode = BeginningOfGame;
11477     ModeHighlight();
11478     if(appData.icsActive) gameInfo.variant = VariantNormal;
11479     currentMove = forwardMostMove = backwardMostMove = 0;
11480     MarkTargetSquares(1);
11481     InitPosition(redraw);
11482     for (i = 0; i < MAX_MOVES; i++) {
11483         if (commentList[i] != NULL) {
11484             free(commentList[i]);
11485             commentList[i] = NULL;
11486         }
11487     }
11488     ResetClocks();
11489     timeRemaining[0][0] = whiteTimeRemaining;
11490     timeRemaining[1][0] = blackTimeRemaining;
11491
11492     if (first.pr == NoProc) {
11493         StartChessProgram(&first);
11494     }
11495     if (init) {
11496             InitChessProgram(&first, startedFromSetupPosition);
11497     }
11498     DisplayTitle("");
11499     DisplayMessage("", "");
11500     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11501     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11502     ClearMap();        // [HGM] exclude: invalidate map
11503 }
11504
11505 void
11506 AutoPlayGameLoop ()
11507 {
11508     for (;;) {
11509         if (!AutoPlayOneMove())
11510           return;
11511         if (matchMode || appData.timeDelay == 0)
11512           continue;
11513         if (appData.timeDelay < 0)
11514           return;
11515         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11516         break;
11517     }
11518 }
11519
11520 void
11521 AnalyzeNextGame()
11522 {
11523     ReloadGame(1); // next game
11524 }
11525
11526 int
11527 AutoPlayOneMove ()
11528 {
11529     int fromX, fromY, toX, toY;
11530
11531     if (appData.debugMode) {
11532       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11533     }
11534
11535     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11536       return FALSE;
11537
11538     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11539       pvInfoList[currentMove].depth = programStats.depth;
11540       pvInfoList[currentMove].score = programStats.score;
11541       pvInfoList[currentMove].time  = 0;
11542       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11543       else { // append analysis of final position as comment
11544         char buf[MSG_SIZ];
11545         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11546         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11547       }
11548       programStats.depth = 0;
11549     }
11550
11551     if (currentMove >= forwardMostMove) {
11552       if(gameMode == AnalyzeFile) {
11553           if(appData.loadGameIndex == -1) {
11554             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11555           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11556           } else {
11557           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11558         }
11559       }
11560 //      gameMode = EndOfGame;
11561 //      ModeHighlight();
11562
11563       /* [AS] Clear current move marker at the end of a game */
11564       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11565
11566       return FALSE;
11567     }
11568
11569     toX = moveList[currentMove][2] - AAA;
11570     toY = moveList[currentMove][3] - ONE;
11571
11572     if (moveList[currentMove][1] == '@') {
11573         if (appData.highlightLastMove) {
11574             SetHighlights(-1, -1, toX, toY);
11575         }
11576     } else {
11577         fromX = moveList[currentMove][0] - AAA;
11578         fromY = moveList[currentMove][1] - ONE;
11579
11580         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11581
11582         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11583
11584         if (appData.highlightLastMove) {
11585             SetHighlights(fromX, fromY, toX, toY);
11586         }
11587     }
11588     DisplayMove(currentMove);
11589     SendMoveToProgram(currentMove++, &first);
11590     DisplayBothClocks();
11591     DrawPosition(FALSE, boards[currentMove]);
11592     // [HGM] PV info: always display, routine tests if empty
11593     DisplayComment(currentMove - 1, commentList[currentMove]);
11594     return TRUE;
11595 }
11596
11597
11598 int
11599 LoadGameOneMove (ChessMove readAhead)
11600 {
11601     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11602     char promoChar = NULLCHAR;
11603     ChessMove moveType;
11604     char move[MSG_SIZ];
11605     char *p, *q;
11606
11607     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11608         gameMode != AnalyzeMode && gameMode != Training) {
11609         gameFileFP = NULL;
11610         return FALSE;
11611     }
11612
11613     yyboardindex = forwardMostMove;
11614     if (readAhead != EndOfFile) {
11615       moveType = readAhead;
11616     } else {
11617       if (gameFileFP == NULL)
11618           return FALSE;
11619       moveType = (ChessMove) Myylex();
11620     }
11621
11622     done = FALSE;
11623     switch (moveType) {
11624       case Comment:
11625         if (appData.debugMode)
11626           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11627         p = yy_text;
11628
11629         /* append the comment but don't display it */
11630         AppendComment(currentMove, p, FALSE);
11631         return TRUE;
11632
11633       case WhiteCapturesEnPassant:
11634       case BlackCapturesEnPassant:
11635       case WhitePromotion:
11636       case BlackPromotion:
11637       case WhiteNonPromotion:
11638       case BlackNonPromotion:
11639       case NormalMove:
11640       case FirstLeg:
11641       case WhiteKingSideCastle:
11642       case WhiteQueenSideCastle:
11643       case BlackKingSideCastle:
11644       case BlackQueenSideCastle:
11645       case WhiteKingSideCastleWild:
11646       case WhiteQueenSideCastleWild:
11647       case BlackKingSideCastleWild:
11648       case BlackQueenSideCastleWild:
11649       /* PUSH Fabien */
11650       case WhiteHSideCastleFR:
11651       case WhiteASideCastleFR:
11652       case BlackHSideCastleFR:
11653       case BlackASideCastleFR:
11654       /* POP Fabien */
11655         if (appData.debugMode)
11656           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11657         fromX = currentMoveString[0] - AAA;
11658         fromY = currentMoveString[1] - ONE;
11659         toX = currentMoveString[2] - AAA;
11660         toY = currentMoveString[3] - ONE;
11661         promoChar = currentMoveString[4];
11662         if(promoChar == ';') promoChar = NULLCHAR;
11663         break;
11664
11665       case WhiteDrop:
11666       case BlackDrop:
11667         if (appData.debugMode)
11668           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11669         fromX = moveType == WhiteDrop ?
11670           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11671         (int) CharToPiece(ToLower(currentMoveString[0]));
11672         fromY = DROP_RANK;
11673         toX = currentMoveString[2] - AAA;
11674         toY = currentMoveString[3] - ONE;
11675         break;
11676
11677       case WhiteWins:
11678       case BlackWins:
11679       case GameIsDrawn:
11680       case GameUnfinished:
11681         if (appData.debugMode)
11682           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11683         p = strchr(yy_text, '{');
11684         if (p == NULL) p = strchr(yy_text, '(');
11685         if (p == NULL) {
11686             p = yy_text;
11687             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11688         } else {
11689             q = strchr(p, *p == '{' ? '}' : ')');
11690             if (q != NULL) *q = NULLCHAR;
11691             p++;
11692         }
11693         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11694         GameEnds(moveType, p, GE_FILE);
11695         done = TRUE;
11696         if (cmailMsgLoaded) {
11697             ClearHighlights();
11698             flipView = WhiteOnMove(currentMove);
11699             if (moveType == GameUnfinished) flipView = !flipView;
11700             if (appData.debugMode)
11701               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11702         }
11703         break;
11704
11705       case EndOfFile:
11706         if (appData.debugMode)
11707           fprintf(debugFP, "Parser hit end of file\n");
11708         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11709           case MT_NONE:
11710           case MT_CHECK:
11711             break;
11712           case MT_CHECKMATE:
11713           case MT_STAINMATE:
11714             if (WhiteOnMove(currentMove)) {
11715                 GameEnds(BlackWins, "Black mates", GE_FILE);
11716             } else {
11717                 GameEnds(WhiteWins, "White mates", GE_FILE);
11718             }
11719             break;
11720           case MT_STALEMATE:
11721             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11722             break;
11723         }
11724         done = TRUE;
11725         break;
11726
11727       case MoveNumberOne:
11728         if (lastLoadGameStart == GNUChessGame) {
11729             /* GNUChessGames have numbers, but they aren't move numbers */
11730             if (appData.debugMode)
11731               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11732                       yy_text, (int) moveType);
11733             return LoadGameOneMove(EndOfFile); /* tail recursion */
11734         }
11735         /* else fall thru */
11736
11737       case XBoardGame:
11738       case GNUChessGame:
11739       case PGNTag:
11740         /* Reached start of next game in file */
11741         if (appData.debugMode)
11742           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11743         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11744           case MT_NONE:
11745           case MT_CHECK:
11746             break;
11747           case MT_CHECKMATE:
11748           case MT_STAINMATE:
11749             if (WhiteOnMove(currentMove)) {
11750                 GameEnds(BlackWins, "Black mates", GE_FILE);
11751             } else {
11752                 GameEnds(WhiteWins, "White mates", GE_FILE);
11753             }
11754             break;
11755           case MT_STALEMATE:
11756             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11757             break;
11758         }
11759         done = TRUE;
11760         break;
11761
11762       case PositionDiagram:     /* should not happen; ignore */
11763       case ElapsedTime:         /* ignore */
11764       case NAG:                 /* ignore */
11765         if (appData.debugMode)
11766           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11767                   yy_text, (int) moveType);
11768         return LoadGameOneMove(EndOfFile); /* tail recursion */
11769
11770       case IllegalMove:
11771         if (appData.testLegality) {
11772             if (appData.debugMode)
11773               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11774             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11775                     (forwardMostMove / 2) + 1,
11776                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11777             DisplayError(move, 0);
11778             done = TRUE;
11779         } else {
11780             if (appData.debugMode)
11781               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11782                       yy_text, currentMoveString);
11783             fromX = currentMoveString[0] - AAA;
11784             fromY = currentMoveString[1] - ONE;
11785             toX = currentMoveString[2] - AAA;
11786             toY = currentMoveString[3] - ONE;
11787             promoChar = currentMoveString[4];
11788         }
11789         break;
11790
11791       case AmbiguousMove:
11792         if (appData.debugMode)
11793           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11794         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11795                 (forwardMostMove / 2) + 1,
11796                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11797         DisplayError(move, 0);
11798         done = TRUE;
11799         break;
11800
11801       default:
11802       case ImpossibleMove:
11803         if (appData.debugMode)
11804           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11805         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11806                 (forwardMostMove / 2) + 1,
11807                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11808         DisplayError(move, 0);
11809         done = TRUE;
11810         break;
11811     }
11812
11813     if (done) {
11814         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11815             DrawPosition(FALSE, boards[currentMove]);
11816             DisplayBothClocks();
11817             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11818               DisplayComment(currentMove - 1, commentList[currentMove]);
11819         }
11820         (void) StopLoadGameTimer();
11821         gameFileFP = NULL;
11822         cmailOldMove = forwardMostMove;
11823         return FALSE;
11824     } else {
11825         /* currentMoveString is set as a side-effect of yylex */
11826
11827         thinkOutput[0] = NULLCHAR;
11828         MakeMove(fromX, fromY, toX, toY, promoChar);
11829         killX = killY = -1; // [HGM] lion: used up
11830         currentMove = forwardMostMove;
11831         return TRUE;
11832     }
11833 }
11834
11835 /* Load the nth game from the given file */
11836 int
11837 LoadGameFromFile (char *filename, int n, char *title, int useList)
11838 {
11839     FILE *f;
11840     char buf[MSG_SIZ];
11841
11842     if (strcmp(filename, "-") == 0) {
11843         f = stdin;
11844         title = "stdin";
11845     } else {
11846         f = fopen(filename, "rb");
11847         if (f == NULL) {
11848           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11849             DisplayError(buf, errno);
11850             return FALSE;
11851         }
11852     }
11853     if (fseek(f, 0, 0) == -1) {
11854         /* f is not seekable; probably a pipe */
11855         useList = FALSE;
11856     }
11857     if (useList && n == 0) {
11858         int error = GameListBuild(f);
11859         if (error) {
11860             DisplayError(_("Cannot build game list"), error);
11861         } else if (!ListEmpty(&gameList) &&
11862                    ((ListGame *) gameList.tailPred)->number > 1) {
11863             GameListPopUp(f, title);
11864             return TRUE;
11865         }
11866         GameListDestroy();
11867         n = 1;
11868     }
11869     if (n == 0) n = 1;
11870     return LoadGame(f, n, title, FALSE);
11871 }
11872
11873
11874 void
11875 MakeRegisteredMove ()
11876 {
11877     int fromX, fromY, toX, toY;
11878     char promoChar;
11879     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11880         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11881           case CMAIL_MOVE:
11882           case CMAIL_DRAW:
11883             if (appData.debugMode)
11884               fprintf(debugFP, "Restoring %s for game %d\n",
11885                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11886
11887             thinkOutput[0] = NULLCHAR;
11888             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11889             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11890             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11891             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11892             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11893             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11894             MakeMove(fromX, fromY, toX, toY, promoChar);
11895             ShowMove(fromX, fromY, toX, toY);
11896
11897             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11898               case MT_NONE:
11899               case MT_CHECK:
11900                 break;
11901
11902               case MT_CHECKMATE:
11903               case MT_STAINMATE:
11904                 if (WhiteOnMove(currentMove)) {
11905                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11906                 } else {
11907                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11908                 }
11909                 break;
11910
11911               case MT_STALEMATE:
11912                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11913                 break;
11914             }
11915
11916             break;
11917
11918           case CMAIL_RESIGN:
11919             if (WhiteOnMove(currentMove)) {
11920                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11921             } else {
11922                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11923             }
11924             break;
11925
11926           case CMAIL_ACCEPT:
11927             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11928             break;
11929
11930           default:
11931             break;
11932         }
11933     }
11934
11935     return;
11936 }
11937
11938 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11939 int
11940 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11941 {
11942     int retVal;
11943
11944     if (gameNumber > nCmailGames) {
11945         DisplayError(_("No more games in this message"), 0);
11946         return FALSE;
11947     }
11948     if (f == lastLoadGameFP) {
11949         int offset = gameNumber - lastLoadGameNumber;
11950         if (offset == 0) {
11951             cmailMsg[0] = NULLCHAR;
11952             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11953                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11954                 nCmailMovesRegistered--;
11955             }
11956             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11957             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11958                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11959             }
11960         } else {
11961             if (! RegisterMove()) return FALSE;
11962         }
11963     }
11964
11965     retVal = LoadGame(f, gameNumber, title, useList);
11966
11967     /* Make move registered during previous look at this game, if any */
11968     MakeRegisteredMove();
11969
11970     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11971         commentList[currentMove]
11972           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11973         DisplayComment(currentMove - 1, commentList[currentMove]);
11974     }
11975
11976     return retVal;
11977 }
11978
11979 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11980 int
11981 ReloadGame (int offset)
11982 {
11983     int gameNumber = lastLoadGameNumber + offset;
11984     if (lastLoadGameFP == NULL) {
11985         DisplayError(_("No game has been loaded yet"), 0);
11986         return FALSE;
11987     }
11988     if (gameNumber <= 0) {
11989         DisplayError(_("Can't back up any further"), 0);
11990         return FALSE;
11991     }
11992     if (cmailMsgLoaded) {
11993         return CmailLoadGame(lastLoadGameFP, gameNumber,
11994                              lastLoadGameTitle, lastLoadGameUseList);
11995     } else {
11996         return LoadGame(lastLoadGameFP, gameNumber,
11997                         lastLoadGameTitle, lastLoadGameUseList);
11998     }
11999 }
12000
12001 int keys[EmptySquare+1];
12002
12003 int
12004 PositionMatches (Board b1, Board b2)
12005 {
12006     int r, f, sum=0;
12007     switch(appData.searchMode) {
12008         case 1: return CompareWithRights(b1, b2);
12009         case 2:
12010             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12011                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12012             }
12013             return TRUE;
12014         case 3:
12015             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12016               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12017                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12018             }
12019             return sum==0;
12020         case 4:
12021             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12022                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12023             }
12024             return sum==0;
12025     }
12026     return TRUE;
12027 }
12028
12029 #define Q_PROMO  4
12030 #define Q_EP     3
12031 #define Q_BCASTL 2
12032 #define Q_WCASTL 1
12033
12034 int pieceList[256], quickBoard[256];
12035 ChessSquare pieceType[256] = { EmptySquare };
12036 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12037 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12038 int soughtTotal, turn;
12039 Boolean epOK, flipSearch;
12040
12041 typedef struct {
12042     unsigned char piece, to;
12043 } Move;
12044
12045 #define DSIZE (250000)
12046
12047 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12048 Move *moveDatabase = initialSpace;
12049 unsigned int movePtr, dataSize = DSIZE;
12050
12051 int
12052 MakePieceList (Board board, int *counts)
12053 {
12054     int r, f, n=Q_PROMO, total=0;
12055     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12056     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12057         int sq = f + (r<<4);
12058         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12059             quickBoard[sq] = ++n;
12060             pieceList[n] = sq;
12061             pieceType[n] = board[r][f];
12062             counts[board[r][f]]++;
12063             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12064             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12065             total++;
12066         }
12067     }
12068     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12069     return total;
12070 }
12071
12072 void
12073 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12074 {
12075     int sq = fromX + (fromY<<4);
12076     int piece = quickBoard[sq];
12077     quickBoard[sq] = 0;
12078     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12079     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12080         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12081         moveDatabase[movePtr++].piece = Q_WCASTL;
12082         quickBoard[sq] = piece;
12083         piece = quickBoard[from]; quickBoard[from] = 0;
12084         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12085     } else
12086     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12087         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12088         moveDatabase[movePtr++].piece = Q_BCASTL;
12089         quickBoard[sq] = piece;
12090         piece = quickBoard[from]; quickBoard[from] = 0;
12091         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12092     } else
12093     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12094         quickBoard[(fromY<<4)+toX] = 0;
12095         moveDatabase[movePtr].piece = Q_EP;
12096         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12097         moveDatabase[movePtr].to = sq;
12098     } else
12099     if(promoPiece != pieceType[piece]) {
12100         moveDatabase[movePtr++].piece = Q_PROMO;
12101         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12102     }
12103     moveDatabase[movePtr].piece = piece;
12104     quickBoard[sq] = piece;
12105     movePtr++;
12106 }
12107
12108 int
12109 PackGame (Board board)
12110 {
12111     Move *newSpace = NULL;
12112     moveDatabase[movePtr].piece = 0; // terminate previous game
12113     if(movePtr > dataSize) {
12114         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12115         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12116         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12117         if(newSpace) {
12118             int i;
12119             Move *p = moveDatabase, *q = newSpace;
12120             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12121             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12122             moveDatabase = newSpace;
12123         } else { // calloc failed, we must be out of memory. Too bad...
12124             dataSize = 0; // prevent calloc events for all subsequent games
12125             return 0;     // and signal this one isn't cached
12126         }
12127     }
12128     movePtr++;
12129     MakePieceList(board, counts);
12130     return movePtr;
12131 }
12132
12133 int
12134 QuickCompare (Board board, int *minCounts, int *maxCounts)
12135 {   // compare according to search mode
12136     int r, f;
12137     switch(appData.searchMode)
12138     {
12139       case 1: // exact position match
12140         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12141         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12142             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12143         }
12144         break;
12145       case 2: // can have extra material on empty squares
12146         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12147             if(board[r][f] == EmptySquare) continue;
12148             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12149         }
12150         break;
12151       case 3: // material with exact Pawn structure
12152         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12153             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12154             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12155         } // fall through to material comparison
12156       case 4: // exact material
12157         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12158         break;
12159       case 6: // material range with given imbalance
12160         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12161         // fall through to range comparison
12162       case 5: // material range
12163         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12164     }
12165     return TRUE;
12166 }
12167
12168 int
12169 QuickScan (Board board, Move *move)
12170 {   // reconstruct game,and compare all positions in it
12171     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12172     do {
12173         int piece = move->piece;
12174         int to = move->to, from = pieceList[piece];
12175         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12176           if(!piece) return -1;
12177           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12178             piece = (++move)->piece;
12179             from = pieceList[piece];
12180             counts[pieceType[piece]]--;
12181             pieceType[piece] = (ChessSquare) move->to;
12182             counts[move->to]++;
12183           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12184             counts[pieceType[quickBoard[to]]]--;
12185             quickBoard[to] = 0; total--;
12186             move++;
12187             continue;
12188           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12189             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12190             from  = pieceList[piece]; // so this must be King
12191             quickBoard[from] = 0;
12192             pieceList[piece] = to;
12193             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12194             quickBoard[from] = 0; // rook
12195             quickBoard[to] = piece;
12196             to = move->to; piece = move->piece;
12197             goto aftercastle;
12198           }
12199         }
12200         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12201         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12202         quickBoard[from] = 0;
12203       aftercastle:
12204         quickBoard[to] = piece;
12205         pieceList[piece] = to;
12206         cnt++; turn ^= 3;
12207         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12208            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12209            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12210                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12211           ) {
12212             static int lastCounts[EmptySquare+1];
12213             int i;
12214             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12215             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12216         } else stretch = 0;
12217         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12218         move++;
12219     } while(1);
12220 }
12221
12222 void
12223 InitSearch ()
12224 {
12225     int r, f;
12226     flipSearch = FALSE;
12227     CopyBoard(soughtBoard, boards[currentMove]);
12228     soughtTotal = MakePieceList(soughtBoard, maxSought);
12229     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12230     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12231     CopyBoard(reverseBoard, boards[currentMove]);
12232     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12233         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12234         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12235         reverseBoard[r][f] = piece;
12236     }
12237     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12238     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12239     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12240                  || (boards[currentMove][CASTLING][2] == NoRights ||
12241                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12242                  && (boards[currentMove][CASTLING][5] == NoRights ||
12243                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12244       ) {
12245         flipSearch = TRUE;
12246         CopyBoard(flipBoard, soughtBoard);
12247         CopyBoard(rotateBoard, reverseBoard);
12248         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12249             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12250             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12251         }
12252     }
12253     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12254     if(appData.searchMode >= 5) {
12255         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12256         MakePieceList(soughtBoard, minSought);
12257         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12258     }
12259     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12260         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12261 }
12262
12263 GameInfo dummyInfo;
12264 static int creatingBook;
12265
12266 int
12267 GameContainsPosition (FILE *f, ListGame *lg)
12268 {
12269     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12270     int fromX, fromY, toX, toY;
12271     char promoChar;
12272     static int initDone=FALSE;
12273
12274     // weed out games based on numerical tag comparison
12275     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12276     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12277     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12278     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12279     if(!initDone) {
12280         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12281         initDone = TRUE;
12282     }
12283     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12284     else CopyBoard(boards[scratch], initialPosition); // default start position
12285     if(lg->moves) {
12286         turn = btm + 1;
12287         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12288         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12289     }
12290     if(btm) plyNr++;
12291     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12292     fseek(f, lg->offset, 0);
12293     yynewfile(f);
12294     while(1) {
12295         yyboardindex = scratch;
12296         quickFlag = plyNr+1;
12297         next = Myylex();
12298         quickFlag = 0;
12299         switch(next) {
12300             case PGNTag:
12301                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12302             default:
12303                 continue;
12304
12305             case XBoardGame:
12306             case GNUChessGame:
12307                 if(plyNr) return -1; // after we have seen moves, this is for new game
12308               continue;
12309
12310             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12311             case ImpossibleMove:
12312             case WhiteWins: // game ends here with these four
12313             case BlackWins:
12314             case GameIsDrawn:
12315             case GameUnfinished:
12316                 return -1;
12317
12318             case IllegalMove:
12319                 if(appData.testLegality) return -1;
12320             case WhiteCapturesEnPassant:
12321             case BlackCapturesEnPassant:
12322             case WhitePromotion:
12323             case BlackPromotion:
12324             case WhiteNonPromotion:
12325             case BlackNonPromotion:
12326             case NormalMove:
12327             case FirstLeg:
12328             case WhiteKingSideCastle:
12329             case WhiteQueenSideCastle:
12330             case BlackKingSideCastle:
12331             case BlackQueenSideCastle:
12332             case WhiteKingSideCastleWild:
12333             case WhiteQueenSideCastleWild:
12334             case BlackKingSideCastleWild:
12335             case BlackQueenSideCastleWild:
12336             case WhiteHSideCastleFR:
12337             case WhiteASideCastleFR:
12338             case BlackHSideCastleFR:
12339             case BlackASideCastleFR:
12340                 fromX = currentMoveString[0] - AAA;
12341                 fromY = currentMoveString[1] - ONE;
12342                 toX = currentMoveString[2] - AAA;
12343                 toY = currentMoveString[3] - ONE;
12344                 promoChar = currentMoveString[4];
12345                 break;
12346             case WhiteDrop:
12347             case BlackDrop:
12348                 fromX = next == WhiteDrop ?
12349                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12350                   (int) CharToPiece(ToLower(currentMoveString[0]));
12351                 fromY = DROP_RANK;
12352                 toX = currentMoveString[2] - AAA;
12353                 toY = currentMoveString[3] - ONE;
12354                 promoChar = 0;
12355                 break;
12356         }
12357         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12358         plyNr++;
12359         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12360         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12361         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12362         if(appData.findMirror) {
12363             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12364             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12365         }
12366     }
12367 }
12368
12369 /* Load the nth game from open file f */
12370 int
12371 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12372 {
12373     ChessMove cm;
12374     char buf[MSG_SIZ];
12375     int gn = gameNumber;
12376     ListGame *lg = NULL;
12377     int numPGNTags = 0;
12378     int err, pos = -1;
12379     GameMode oldGameMode;
12380     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12381
12382     if (appData.debugMode)
12383         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12384
12385     if (gameMode == Training )
12386         SetTrainingModeOff();
12387
12388     oldGameMode = gameMode;
12389     if (gameMode != BeginningOfGame) {
12390       Reset(FALSE, TRUE);
12391     }
12392     killX = killY = -1; // [HGM] lion: in case we did not Reset
12393
12394     gameFileFP = f;
12395     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12396         fclose(lastLoadGameFP);
12397     }
12398
12399     if (useList) {
12400         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12401
12402         if (lg) {
12403             fseek(f, lg->offset, 0);
12404             GameListHighlight(gameNumber);
12405             pos = lg->position;
12406             gn = 1;
12407         }
12408         else {
12409             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12410               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12411             else
12412             DisplayError(_("Game number out of range"), 0);
12413             return FALSE;
12414         }
12415     } else {
12416         GameListDestroy();
12417         if (fseek(f, 0, 0) == -1) {
12418             if (f == lastLoadGameFP ?
12419                 gameNumber == lastLoadGameNumber + 1 :
12420                 gameNumber == 1) {
12421                 gn = 1;
12422             } else {
12423                 DisplayError(_("Can't seek on game file"), 0);
12424                 return FALSE;
12425             }
12426         }
12427     }
12428     lastLoadGameFP = f;
12429     lastLoadGameNumber = gameNumber;
12430     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12431     lastLoadGameUseList = useList;
12432
12433     yynewfile(f);
12434
12435     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12436       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12437                 lg->gameInfo.black);
12438             DisplayTitle(buf);
12439     } else if (*title != NULLCHAR) {
12440         if (gameNumber > 1) {
12441           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12442             DisplayTitle(buf);
12443         } else {
12444             DisplayTitle(title);
12445         }
12446     }
12447
12448     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12449         gameMode = PlayFromGameFile;
12450         ModeHighlight();
12451     }
12452
12453     currentMove = forwardMostMove = backwardMostMove = 0;
12454     CopyBoard(boards[0], initialPosition);
12455     StopClocks();
12456
12457     /*
12458      * Skip the first gn-1 games in the file.
12459      * Also skip over anything that precedes an identifiable
12460      * start of game marker, to avoid being confused by
12461      * garbage at the start of the file.  Currently
12462      * recognized start of game markers are the move number "1",
12463      * the pattern "gnuchess .* game", the pattern
12464      * "^[#;%] [^ ]* game file", and a PGN tag block.
12465      * A game that starts with one of the latter two patterns
12466      * will also have a move number 1, possibly
12467      * following a position diagram.
12468      * 5-4-02: Let's try being more lenient and allowing a game to
12469      * start with an unnumbered move.  Does that break anything?
12470      */
12471     cm = lastLoadGameStart = EndOfFile;
12472     while (gn > 0) {
12473         yyboardindex = forwardMostMove;
12474         cm = (ChessMove) Myylex();
12475         switch (cm) {
12476           case EndOfFile:
12477             if (cmailMsgLoaded) {
12478                 nCmailGames = CMAIL_MAX_GAMES - gn;
12479             } else {
12480                 Reset(TRUE, TRUE);
12481                 DisplayError(_("Game not found in file"), 0);
12482             }
12483             return FALSE;
12484
12485           case GNUChessGame:
12486           case XBoardGame:
12487             gn--;
12488             lastLoadGameStart = cm;
12489             break;
12490
12491           case MoveNumberOne:
12492             switch (lastLoadGameStart) {
12493               case GNUChessGame:
12494               case XBoardGame:
12495               case PGNTag:
12496                 break;
12497               case MoveNumberOne:
12498               case EndOfFile:
12499                 gn--;           /* count this game */
12500                 lastLoadGameStart = cm;
12501                 break;
12502               default:
12503                 /* impossible */
12504                 break;
12505             }
12506             break;
12507
12508           case PGNTag:
12509             switch (lastLoadGameStart) {
12510               case GNUChessGame:
12511               case PGNTag:
12512               case MoveNumberOne:
12513               case EndOfFile:
12514                 gn--;           /* count this game */
12515                 lastLoadGameStart = cm;
12516                 break;
12517               case XBoardGame:
12518                 lastLoadGameStart = cm; /* game counted already */
12519                 break;
12520               default:
12521                 /* impossible */
12522                 break;
12523             }
12524             if (gn > 0) {
12525                 do {
12526                     yyboardindex = forwardMostMove;
12527                     cm = (ChessMove) Myylex();
12528                 } while (cm == PGNTag || cm == Comment);
12529             }
12530             break;
12531
12532           case WhiteWins:
12533           case BlackWins:
12534           case GameIsDrawn:
12535             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12536                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12537                     != CMAIL_OLD_RESULT) {
12538                     nCmailResults ++ ;
12539                     cmailResult[  CMAIL_MAX_GAMES
12540                                 - gn - 1] = CMAIL_OLD_RESULT;
12541                 }
12542             }
12543             break;
12544
12545           case NormalMove:
12546           case FirstLeg:
12547             /* Only a NormalMove can be at the start of a game
12548              * without a position diagram. */
12549             if (lastLoadGameStart == EndOfFile ) {
12550               gn--;
12551               lastLoadGameStart = MoveNumberOne;
12552             }
12553             break;
12554
12555           default:
12556             break;
12557         }
12558     }
12559
12560     if (appData.debugMode)
12561       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12562
12563     if (cm == XBoardGame) {
12564         /* Skip any header junk before position diagram and/or move 1 */
12565         for (;;) {
12566             yyboardindex = forwardMostMove;
12567             cm = (ChessMove) Myylex();
12568
12569             if (cm == EndOfFile ||
12570                 cm == GNUChessGame || cm == XBoardGame) {
12571                 /* Empty game; pretend end-of-file and handle later */
12572                 cm = EndOfFile;
12573                 break;
12574             }
12575
12576             if (cm == MoveNumberOne || cm == PositionDiagram ||
12577                 cm == PGNTag || cm == Comment)
12578               break;
12579         }
12580     } else if (cm == GNUChessGame) {
12581         if (gameInfo.event != NULL) {
12582             free(gameInfo.event);
12583         }
12584         gameInfo.event = StrSave(yy_text);
12585     }
12586
12587     startedFromSetupPosition = FALSE;
12588     while (cm == PGNTag) {
12589         if (appData.debugMode)
12590           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12591         err = ParsePGNTag(yy_text, &gameInfo);
12592         if (!err) numPGNTags++;
12593
12594         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12595         if(gameInfo.variant != oldVariant) {
12596             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12597             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12598             InitPosition(TRUE);
12599             oldVariant = gameInfo.variant;
12600             if (appData.debugMode)
12601               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12602         }
12603
12604
12605         if (gameInfo.fen != NULL) {
12606           Board initial_position;
12607           startedFromSetupPosition = TRUE;
12608           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12609             Reset(TRUE, TRUE);
12610             DisplayError(_("Bad FEN position in file"), 0);
12611             return FALSE;
12612           }
12613           CopyBoard(boards[0], initial_position);
12614           if (blackPlaysFirst) {
12615             currentMove = forwardMostMove = backwardMostMove = 1;
12616             CopyBoard(boards[1], initial_position);
12617             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12618             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12619             timeRemaining[0][1] = whiteTimeRemaining;
12620             timeRemaining[1][1] = blackTimeRemaining;
12621             if (commentList[0] != NULL) {
12622               commentList[1] = commentList[0];
12623               commentList[0] = NULL;
12624             }
12625           } else {
12626             currentMove = forwardMostMove = backwardMostMove = 0;
12627           }
12628           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12629           {   int i;
12630               initialRulePlies = FENrulePlies;
12631               for( i=0; i< nrCastlingRights; i++ )
12632                   initialRights[i] = initial_position[CASTLING][i];
12633           }
12634           yyboardindex = forwardMostMove;
12635           free(gameInfo.fen);
12636           gameInfo.fen = NULL;
12637         }
12638
12639         yyboardindex = forwardMostMove;
12640         cm = (ChessMove) Myylex();
12641
12642         /* Handle comments interspersed among the tags */
12643         while (cm == Comment) {
12644             char *p;
12645             if (appData.debugMode)
12646               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12647             p = yy_text;
12648             AppendComment(currentMove, p, FALSE);
12649             yyboardindex = forwardMostMove;
12650             cm = (ChessMove) Myylex();
12651         }
12652     }
12653
12654     /* don't rely on existence of Event tag since if game was
12655      * pasted from clipboard the Event tag may not exist
12656      */
12657     if (numPGNTags > 0){
12658         char *tags;
12659         if (gameInfo.variant == VariantNormal) {
12660           VariantClass v = StringToVariant(gameInfo.event);
12661           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12662           if(v < VariantShogi) gameInfo.variant = v;
12663         }
12664         if (!matchMode) {
12665           if( appData.autoDisplayTags ) {
12666             tags = PGNTags(&gameInfo);
12667             TagsPopUp(tags, CmailMsg());
12668             free(tags);
12669           }
12670         }
12671     } else {
12672         /* Make something up, but don't display it now */
12673         SetGameInfo();
12674         TagsPopDown();
12675     }
12676
12677     if (cm == PositionDiagram) {
12678         int i, j;
12679         char *p;
12680         Board initial_position;
12681
12682         if (appData.debugMode)
12683           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12684
12685         if (!startedFromSetupPosition) {
12686             p = yy_text;
12687             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12688               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12689                 switch (*p) {
12690                   case '{':
12691                   case '[':
12692                   case '-':
12693                   case ' ':
12694                   case '\t':
12695                   case '\n':
12696                   case '\r':
12697                     break;
12698                   default:
12699                     initial_position[i][j++] = CharToPiece(*p);
12700                     break;
12701                 }
12702             while (*p == ' ' || *p == '\t' ||
12703                    *p == '\n' || *p == '\r') p++;
12704
12705             if (strncmp(p, "black", strlen("black"))==0)
12706               blackPlaysFirst = TRUE;
12707             else
12708               blackPlaysFirst = FALSE;
12709             startedFromSetupPosition = TRUE;
12710
12711             CopyBoard(boards[0], initial_position);
12712             if (blackPlaysFirst) {
12713                 currentMove = forwardMostMove = backwardMostMove = 1;
12714                 CopyBoard(boards[1], initial_position);
12715                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12716                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12717                 timeRemaining[0][1] = whiteTimeRemaining;
12718                 timeRemaining[1][1] = blackTimeRemaining;
12719                 if (commentList[0] != NULL) {
12720                     commentList[1] = commentList[0];
12721                     commentList[0] = NULL;
12722                 }
12723             } else {
12724                 currentMove = forwardMostMove = backwardMostMove = 0;
12725             }
12726         }
12727         yyboardindex = forwardMostMove;
12728         cm = (ChessMove) Myylex();
12729     }
12730
12731   if(!creatingBook) {
12732     if (first.pr == NoProc) {
12733         StartChessProgram(&first);
12734     }
12735     InitChessProgram(&first, FALSE);
12736     SendToProgram("force\n", &first);
12737     if (startedFromSetupPosition) {
12738         SendBoard(&first, forwardMostMove);
12739     if (appData.debugMode) {
12740         fprintf(debugFP, "Load Game\n");
12741     }
12742         DisplayBothClocks();
12743     }
12744   }
12745
12746     /* [HGM] server: flag to write setup moves in broadcast file as one */
12747     loadFlag = appData.suppressLoadMoves;
12748
12749     while (cm == Comment) {
12750         char *p;
12751         if (appData.debugMode)
12752           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12753         p = yy_text;
12754         AppendComment(currentMove, p, FALSE);
12755         yyboardindex = forwardMostMove;
12756         cm = (ChessMove) Myylex();
12757     }
12758
12759     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12760         cm == WhiteWins || cm == BlackWins ||
12761         cm == GameIsDrawn || cm == GameUnfinished) {
12762         DisplayMessage("", _("No moves in game"));
12763         if (cmailMsgLoaded) {
12764             if (appData.debugMode)
12765               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12766             ClearHighlights();
12767             flipView = FALSE;
12768         }
12769         DrawPosition(FALSE, boards[currentMove]);
12770         DisplayBothClocks();
12771         gameMode = EditGame;
12772         ModeHighlight();
12773         gameFileFP = NULL;
12774         cmailOldMove = 0;
12775         return TRUE;
12776     }
12777
12778     // [HGM] PV info: routine tests if comment empty
12779     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12780         DisplayComment(currentMove - 1, commentList[currentMove]);
12781     }
12782     if (!matchMode && appData.timeDelay != 0)
12783       DrawPosition(FALSE, boards[currentMove]);
12784
12785     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12786       programStats.ok_to_send = 1;
12787     }
12788
12789     /* if the first token after the PGN tags is a move
12790      * and not move number 1, retrieve it from the parser
12791      */
12792     if (cm != MoveNumberOne)
12793         LoadGameOneMove(cm);
12794
12795     /* load the remaining moves from the file */
12796     while (LoadGameOneMove(EndOfFile)) {
12797       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12798       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12799     }
12800
12801     /* rewind to the start of the game */
12802     currentMove = backwardMostMove;
12803
12804     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12805
12806     if (oldGameMode == AnalyzeFile) {
12807       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12808       AnalyzeFileEvent();
12809     } else
12810     if (oldGameMode == AnalyzeMode) {
12811       AnalyzeFileEvent();
12812     }
12813
12814     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12815         long int w, b; // [HGM] adjourn: restore saved clock times
12816         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12817         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12818             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12819             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12820         }
12821     }
12822
12823     if(creatingBook) return TRUE;
12824     if (!matchMode && pos > 0) {
12825         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12826     } else
12827     if (matchMode || appData.timeDelay == 0) {
12828       ToEndEvent();
12829     } else if (appData.timeDelay > 0) {
12830       AutoPlayGameLoop();
12831     }
12832
12833     if (appData.debugMode)
12834         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12835
12836     loadFlag = 0; /* [HGM] true game starts */
12837     return TRUE;
12838 }
12839
12840 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12841 int
12842 ReloadPosition (int offset)
12843 {
12844     int positionNumber = lastLoadPositionNumber + offset;
12845     if (lastLoadPositionFP == NULL) {
12846         DisplayError(_("No position has been loaded yet"), 0);
12847         return FALSE;
12848     }
12849     if (positionNumber <= 0) {
12850         DisplayError(_("Can't back up any further"), 0);
12851         return FALSE;
12852     }
12853     return LoadPosition(lastLoadPositionFP, positionNumber,
12854                         lastLoadPositionTitle);
12855 }
12856
12857 /* Load the nth position from the given file */
12858 int
12859 LoadPositionFromFile (char *filename, int n, char *title)
12860 {
12861     FILE *f;
12862     char buf[MSG_SIZ];
12863
12864     if (strcmp(filename, "-") == 0) {
12865         return LoadPosition(stdin, n, "stdin");
12866     } else {
12867         f = fopen(filename, "rb");
12868         if (f == NULL) {
12869             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12870             DisplayError(buf, errno);
12871             return FALSE;
12872         } else {
12873             return LoadPosition(f, n, title);
12874         }
12875     }
12876 }
12877
12878 /* Load the nth position from the given open file, and close it */
12879 int
12880 LoadPosition (FILE *f, int positionNumber, char *title)
12881 {
12882     char *p, line[MSG_SIZ];
12883     Board initial_position;
12884     int i, j, fenMode, pn;
12885
12886     if (gameMode == Training )
12887         SetTrainingModeOff();
12888
12889     if (gameMode != BeginningOfGame) {
12890         Reset(FALSE, TRUE);
12891     }
12892     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12893         fclose(lastLoadPositionFP);
12894     }
12895     if (positionNumber == 0) positionNumber = 1;
12896     lastLoadPositionFP = f;
12897     lastLoadPositionNumber = positionNumber;
12898     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12899     if (first.pr == NoProc && !appData.noChessProgram) {
12900       StartChessProgram(&first);
12901       InitChessProgram(&first, FALSE);
12902     }
12903     pn = positionNumber;
12904     if (positionNumber < 0) {
12905         /* Negative position number means to seek to that byte offset */
12906         if (fseek(f, -positionNumber, 0) == -1) {
12907             DisplayError(_("Can't seek on position file"), 0);
12908             return FALSE;
12909         };
12910         pn = 1;
12911     } else {
12912         if (fseek(f, 0, 0) == -1) {
12913             if (f == lastLoadPositionFP ?
12914                 positionNumber == lastLoadPositionNumber + 1 :
12915                 positionNumber == 1) {
12916                 pn = 1;
12917             } else {
12918                 DisplayError(_("Can't seek on position file"), 0);
12919                 return FALSE;
12920             }
12921         }
12922     }
12923     /* See if this file is FEN or old-style xboard */
12924     if (fgets(line, MSG_SIZ, f) == NULL) {
12925         DisplayError(_("Position not found in file"), 0);
12926         return FALSE;
12927     }
12928     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12929     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12930
12931     if (pn >= 2) {
12932         if (fenMode || line[0] == '#') pn--;
12933         while (pn > 0) {
12934             /* skip positions before number pn */
12935             if (fgets(line, MSG_SIZ, f) == NULL) {
12936                 Reset(TRUE, TRUE);
12937                 DisplayError(_("Position not found in file"), 0);
12938                 return FALSE;
12939             }
12940             if (fenMode || line[0] == '#') pn--;
12941         }
12942     }
12943
12944     if (fenMode) {
12945         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12946             DisplayError(_("Bad FEN position in file"), 0);
12947             return FALSE;
12948         }
12949     } else {
12950         (void) fgets(line, MSG_SIZ, f);
12951         (void) fgets(line, MSG_SIZ, f);
12952
12953         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12954             (void) fgets(line, MSG_SIZ, f);
12955             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12956                 if (*p == ' ')
12957                   continue;
12958                 initial_position[i][j++] = CharToPiece(*p);
12959             }
12960         }
12961
12962         blackPlaysFirst = FALSE;
12963         if (!feof(f)) {
12964             (void) fgets(line, MSG_SIZ, f);
12965             if (strncmp(line, "black", strlen("black"))==0)
12966               blackPlaysFirst = TRUE;
12967         }
12968     }
12969     startedFromSetupPosition = TRUE;
12970
12971     CopyBoard(boards[0], initial_position);
12972     if (blackPlaysFirst) {
12973         currentMove = forwardMostMove = backwardMostMove = 1;
12974         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12975         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12976         CopyBoard(boards[1], initial_position);
12977         DisplayMessage("", _("Black to play"));
12978     } else {
12979         currentMove = forwardMostMove = backwardMostMove = 0;
12980         DisplayMessage("", _("White to play"));
12981     }
12982     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12983     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12984         SendToProgram("force\n", &first);
12985         SendBoard(&first, forwardMostMove);
12986     }
12987     if (appData.debugMode) {
12988 int i, j;
12989   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12990   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12991         fprintf(debugFP, "Load Position\n");
12992     }
12993
12994     if (positionNumber > 1) {
12995       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12996         DisplayTitle(line);
12997     } else {
12998         DisplayTitle(title);
12999     }
13000     gameMode = EditGame;
13001     ModeHighlight();
13002     ResetClocks();
13003     timeRemaining[0][1] = whiteTimeRemaining;
13004     timeRemaining[1][1] = blackTimeRemaining;
13005     DrawPosition(FALSE, boards[currentMove]);
13006
13007     return TRUE;
13008 }
13009
13010
13011 void
13012 CopyPlayerNameIntoFileName (char **dest, char *src)
13013 {
13014     while (*src != NULLCHAR && *src != ',') {
13015         if (*src == ' ') {
13016             *(*dest)++ = '_';
13017             src++;
13018         } else {
13019             *(*dest)++ = *src++;
13020         }
13021     }
13022 }
13023
13024 char *
13025 DefaultFileName (char *ext)
13026 {
13027     static char def[MSG_SIZ];
13028     char *p;
13029
13030     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13031         p = def;
13032         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13033         *p++ = '-';
13034         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13035         *p++ = '.';
13036         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13037     } else {
13038         def[0] = NULLCHAR;
13039     }
13040     return def;
13041 }
13042
13043 /* Save the current game to the given file */
13044 int
13045 SaveGameToFile (char *filename, int append)
13046 {
13047     FILE *f;
13048     char buf[MSG_SIZ];
13049     int result, i, t,tot=0;
13050
13051     if (strcmp(filename, "-") == 0) {
13052         return SaveGame(stdout, 0, NULL);
13053     } else {
13054         for(i=0; i<10; i++) { // upto 10 tries
13055              f = fopen(filename, append ? "a" : "w");
13056              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13057              if(f || errno != 13) break;
13058              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13059              tot += t;
13060         }
13061         if (f == NULL) {
13062             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13063             DisplayError(buf, errno);
13064             return FALSE;
13065         } else {
13066             safeStrCpy(buf, lastMsg, MSG_SIZ);
13067             DisplayMessage(_("Waiting for access to save file"), "");
13068             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13069             DisplayMessage(_("Saving game"), "");
13070             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13071             result = SaveGame(f, 0, NULL);
13072             DisplayMessage(buf, "");
13073             return result;
13074         }
13075     }
13076 }
13077
13078 char *
13079 SavePart (char *str)
13080 {
13081     static char buf[MSG_SIZ];
13082     char *p;
13083
13084     p = strchr(str, ' ');
13085     if (p == NULL) return str;
13086     strncpy(buf, str, p - str);
13087     buf[p - str] = NULLCHAR;
13088     return buf;
13089 }
13090
13091 #define PGN_MAX_LINE 75
13092
13093 #define PGN_SIDE_WHITE  0
13094 #define PGN_SIDE_BLACK  1
13095
13096 static int
13097 FindFirstMoveOutOfBook (int side)
13098 {
13099     int result = -1;
13100
13101     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13102         int index = backwardMostMove;
13103         int has_book_hit = 0;
13104
13105         if( (index % 2) != side ) {
13106             index++;
13107         }
13108
13109         while( index < forwardMostMove ) {
13110             /* Check to see if engine is in book */
13111             int depth = pvInfoList[index].depth;
13112             int score = pvInfoList[index].score;
13113             int in_book = 0;
13114
13115             if( depth <= 2 ) {
13116                 in_book = 1;
13117             }
13118             else if( score == 0 && depth == 63 ) {
13119                 in_book = 1; /* Zappa */
13120             }
13121             else if( score == 2 && depth == 99 ) {
13122                 in_book = 1; /* Abrok */
13123             }
13124
13125             has_book_hit += in_book;
13126
13127             if( ! in_book ) {
13128                 result = index;
13129
13130                 break;
13131             }
13132
13133             index += 2;
13134         }
13135     }
13136
13137     return result;
13138 }
13139
13140 void
13141 GetOutOfBookInfo (char * buf)
13142 {
13143     int oob[2];
13144     int i;
13145     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13146
13147     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13148     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13149
13150     *buf = '\0';
13151
13152     if( oob[0] >= 0 || oob[1] >= 0 ) {
13153         for( i=0; i<2; i++ ) {
13154             int idx = oob[i];
13155
13156             if( idx >= 0 ) {
13157                 if( i > 0 && oob[0] >= 0 ) {
13158                     strcat( buf, "   " );
13159                 }
13160
13161                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13162                 sprintf( buf+strlen(buf), "%s%.2f",
13163                     pvInfoList[idx].score >= 0 ? "+" : "",
13164                     pvInfoList[idx].score / 100.0 );
13165             }
13166         }
13167     }
13168 }
13169
13170 /* Save game in PGN style and close the file */
13171 int
13172 SaveGamePGN (FILE *f)
13173 {
13174     int i, offset, linelen, newblock;
13175 //    char *movetext;
13176     char numtext[32];
13177     int movelen, numlen, blank;
13178     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13179
13180     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13181
13182     PrintPGNTags(f, &gameInfo);
13183
13184     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13185
13186     if (backwardMostMove > 0 || startedFromSetupPosition) {
13187         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13188         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13189         fprintf(f, "\n{--------------\n");
13190         PrintPosition(f, backwardMostMove);
13191         fprintf(f, "--------------}\n");
13192         free(fen);
13193     }
13194     else {
13195         /* [AS] Out of book annotation */
13196         if( appData.saveOutOfBookInfo ) {
13197             char buf[64];
13198
13199             GetOutOfBookInfo( buf );
13200
13201             if( buf[0] != '\0' ) {
13202                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13203             }
13204         }
13205
13206         fprintf(f, "\n");
13207     }
13208
13209     i = backwardMostMove;
13210     linelen = 0;
13211     newblock = TRUE;
13212
13213     while (i < forwardMostMove) {
13214         /* Print comments preceding this move */
13215         if (commentList[i] != NULL) {
13216             if (linelen > 0) fprintf(f, "\n");
13217             fprintf(f, "%s", commentList[i]);
13218             linelen = 0;
13219             newblock = TRUE;
13220         }
13221
13222         /* Format move number */
13223         if ((i % 2) == 0)
13224           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13225         else
13226           if (newblock)
13227             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13228           else
13229             numtext[0] = NULLCHAR;
13230
13231         numlen = strlen(numtext);
13232         newblock = FALSE;
13233
13234         /* Print move number */
13235         blank = linelen > 0 && numlen > 0;
13236         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13237             fprintf(f, "\n");
13238             linelen = 0;
13239             blank = 0;
13240         }
13241         if (blank) {
13242             fprintf(f, " ");
13243             linelen++;
13244         }
13245         fprintf(f, "%s", numtext);
13246         linelen += numlen;
13247
13248         /* Get move */
13249         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13250         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13251
13252         /* Print move */
13253         blank = linelen > 0 && movelen > 0;
13254         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13255             fprintf(f, "\n");
13256             linelen = 0;
13257             blank = 0;
13258         }
13259         if (blank) {
13260             fprintf(f, " ");
13261             linelen++;
13262         }
13263         fprintf(f, "%s", move_buffer);
13264         linelen += movelen;
13265
13266         /* [AS] Add PV info if present */
13267         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13268             /* [HGM] add time */
13269             char buf[MSG_SIZ]; int seconds;
13270
13271             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13272
13273             if( seconds <= 0)
13274               buf[0] = 0;
13275             else
13276               if( seconds < 30 )
13277                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13278               else
13279                 {
13280                   seconds = (seconds + 4)/10; // round to full seconds
13281                   if( seconds < 60 )
13282                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13283                   else
13284                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13285                 }
13286
13287             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13288                       pvInfoList[i].score >= 0 ? "+" : "",
13289                       pvInfoList[i].score / 100.0,
13290                       pvInfoList[i].depth,
13291                       buf );
13292
13293             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13294
13295             /* Print score/depth */
13296             blank = linelen > 0 && movelen > 0;
13297             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13298                 fprintf(f, "\n");
13299                 linelen = 0;
13300                 blank = 0;
13301             }
13302             if (blank) {
13303                 fprintf(f, " ");
13304                 linelen++;
13305             }
13306             fprintf(f, "%s", move_buffer);
13307             linelen += movelen;
13308         }
13309
13310         i++;
13311     }
13312
13313     /* Start a new line */
13314     if (linelen > 0) fprintf(f, "\n");
13315
13316     /* Print comments after last move */
13317     if (commentList[i] != NULL) {
13318         fprintf(f, "%s\n", commentList[i]);
13319     }
13320
13321     /* Print result */
13322     if (gameInfo.resultDetails != NULL &&
13323         gameInfo.resultDetails[0] != NULLCHAR) {
13324         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13325         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13326            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13327             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13328         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13329     } else {
13330         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13331     }
13332
13333     fclose(f);
13334     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13335     return TRUE;
13336 }
13337
13338 /* Save game in old style and close the file */
13339 int
13340 SaveGameOldStyle (FILE *f)
13341 {
13342     int i, offset;
13343     time_t tm;
13344
13345     tm = time((time_t *) NULL);
13346
13347     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13348     PrintOpponents(f);
13349
13350     if (backwardMostMove > 0 || startedFromSetupPosition) {
13351         fprintf(f, "\n[--------------\n");
13352         PrintPosition(f, backwardMostMove);
13353         fprintf(f, "--------------]\n");
13354     } else {
13355         fprintf(f, "\n");
13356     }
13357
13358     i = backwardMostMove;
13359     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13360
13361     while (i < forwardMostMove) {
13362         if (commentList[i] != NULL) {
13363             fprintf(f, "[%s]\n", commentList[i]);
13364         }
13365
13366         if ((i % 2) == 1) {
13367             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13368             i++;
13369         } else {
13370             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13371             i++;
13372             if (commentList[i] != NULL) {
13373                 fprintf(f, "\n");
13374                 continue;
13375             }
13376             if (i >= forwardMostMove) {
13377                 fprintf(f, "\n");
13378                 break;
13379             }
13380             fprintf(f, "%s\n", parseList[i]);
13381             i++;
13382         }
13383     }
13384
13385     if (commentList[i] != NULL) {
13386         fprintf(f, "[%s]\n", commentList[i]);
13387     }
13388
13389     /* This isn't really the old style, but it's close enough */
13390     if (gameInfo.resultDetails != NULL &&
13391         gameInfo.resultDetails[0] != NULLCHAR) {
13392         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13393                 gameInfo.resultDetails);
13394     } else {
13395         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13396     }
13397
13398     fclose(f);
13399     return TRUE;
13400 }
13401
13402 /* Save the current game to open file f and close the file */
13403 int
13404 SaveGame (FILE *f, int dummy, char *dummy2)
13405 {
13406     if (gameMode == EditPosition) EditPositionDone(TRUE);
13407     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13408     if (appData.oldSaveStyle)
13409       return SaveGameOldStyle(f);
13410     else
13411       return SaveGamePGN(f);
13412 }
13413
13414 /* Save the current position to the given file */
13415 int
13416 SavePositionToFile (char *filename)
13417 {
13418     FILE *f;
13419     char buf[MSG_SIZ];
13420
13421     if (strcmp(filename, "-") == 0) {
13422         return SavePosition(stdout, 0, NULL);
13423     } else {
13424         f = fopen(filename, "a");
13425         if (f == NULL) {
13426             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13427             DisplayError(buf, errno);
13428             return FALSE;
13429         } else {
13430             safeStrCpy(buf, lastMsg, MSG_SIZ);
13431             DisplayMessage(_("Waiting for access to save file"), "");
13432             flock(fileno(f), LOCK_EX); // [HGM] lock
13433             DisplayMessage(_("Saving position"), "");
13434             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13435             SavePosition(f, 0, NULL);
13436             DisplayMessage(buf, "");
13437             return TRUE;
13438         }
13439     }
13440 }
13441
13442 /* Save the current position to the given open file and close the file */
13443 int
13444 SavePosition (FILE *f, int dummy, char *dummy2)
13445 {
13446     time_t tm;
13447     char *fen;
13448
13449     if (gameMode == EditPosition) EditPositionDone(TRUE);
13450     if (appData.oldSaveStyle) {
13451         tm = time((time_t *) NULL);
13452
13453         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13454         PrintOpponents(f);
13455         fprintf(f, "[--------------\n");
13456         PrintPosition(f, currentMove);
13457         fprintf(f, "--------------]\n");
13458     } else {
13459         fen = PositionToFEN(currentMove, NULL, 1);
13460         fprintf(f, "%s\n", fen);
13461         free(fen);
13462     }
13463     fclose(f);
13464     return TRUE;
13465 }
13466
13467 void
13468 ReloadCmailMsgEvent (int unregister)
13469 {
13470 #if !WIN32
13471     static char *inFilename = NULL;
13472     static char *outFilename;
13473     int i;
13474     struct stat inbuf, outbuf;
13475     int status;
13476
13477     /* Any registered moves are unregistered if unregister is set, */
13478     /* i.e. invoked by the signal handler */
13479     if (unregister) {
13480         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13481             cmailMoveRegistered[i] = FALSE;
13482             if (cmailCommentList[i] != NULL) {
13483                 free(cmailCommentList[i]);
13484                 cmailCommentList[i] = NULL;
13485             }
13486         }
13487         nCmailMovesRegistered = 0;
13488     }
13489
13490     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13491         cmailResult[i] = CMAIL_NOT_RESULT;
13492     }
13493     nCmailResults = 0;
13494
13495     if (inFilename == NULL) {
13496         /* Because the filenames are static they only get malloced once  */
13497         /* and they never get freed                                      */
13498         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13499         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13500
13501         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13502         sprintf(outFilename, "%s.out", appData.cmailGameName);
13503     }
13504
13505     status = stat(outFilename, &outbuf);
13506     if (status < 0) {
13507         cmailMailedMove = FALSE;
13508     } else {
13509         status = stat(inFilename, &inbuf);
13510         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13511     }
13512
13513     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13514        counts the games, notes how each one terminated, etc.
13515
13516        It would be nice to remove this kludge and instead gather all
13517        the information while building the game list.  (And to keep it
13518        in the game list nodes instead of having a bunch of fixed-size
13519        parallel arrays.)  Note this will require getting each game's
13520        termination from the PGN tags, as the game list builder does
13521        not process the game moves.  --mann
13522        */
13523     cmailMsgLoaded = TRUE;
13524     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13525
13526     /* Load first game in the file or popup game menu */
13527     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13528
13529 #endif /* !WIN32 */
13530     return;
13531 }
13532
13533 int
13534 RegisterMove ()
13535 {
13536     FILE *f;
13537     char string[MSG_SIZ];
13538
13539     if (   cmailMailedMove
13540         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13541         return TRUE;            /* Allow free viewing  */
13542     }
13543
13544     /* Unregister move to ensure that we don't leave RegisterMove        */
13545     /* with the move registered when the conditions for registering no   */
13546     /* longer hold                                                       */
13547     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13548         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13549         nCmailMovesRegistered --;
13550
13551         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13552           {
13553               free(cmailCommentList[lastLoadGameNumber - 1]);
13554               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13555           }
13556     }
13557
13558     if (cmailOldMove == -1) {
13559         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13560         return FALSE;
13561     }
13562
13563     if (currentMove > cmailOldMove + 1) {
13564         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13565         return FALSE;
13566     }
13567
13568     if (currentMove < cmailOldMove) {
13569         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13570         return FALSE;
13571     }
13572
13573     if (forwardMostMove > currentMove) {
13574         /* Silently truncate extra moves */
13575         TruncateGame();
13576     }
13577
13578     if (   (currentMove == cmailOldMove + 1)
13579         || (   (currentMove == cmailOldMove)
13580             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13581                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13582         if (gameInfo.result != GameUnfinished) {
13583             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13584         }
13585
13586         if (commentList[currentMove] != NULL) {
13587             cmailCommentList[lastLoadGameNumber - 1]
13588               = StrSave(commentList[currentMove]);
13589         }
13590         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13591
13592         if (appData.debugMode)
13593           fprintf(debugFP, "Saving %s for game %d\n",
13594                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13595
13596         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13597
13598         f = fopen(string, "w");
13599         if (appData.oldSaveStyle) {
13600             SaveGameOldStyle(f); /* also closes the file */
13601
13602             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13603             f = fopen(string, "w");
13604             SavePosition(f, 0, NULL); /* also closes the file */
13605         } else {
13606             fprintf(f, "{--------------\n");
13607             PrintPosition(f, currentMove);
13608             fprintf(f, "--------------}\n\n");
13609
13610             SaveGame(f, 0, NULL); /* also closes the file*/
13611         }
13612
13613         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13614         nCmailMovesRegistered ++;
13615     } else if (nCmailGames == 1) {
13616         DisplayError(_("You have not made a move yet"), 0);
13617         return FALSE;
13618     }
13619
13620     return TRUE;
13621 }
13622
13623 void
13624 MailMoveEvent ()
13625 {
13626 #if !WIN32
13627     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13628     FILE *commandOutput;
13629     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13630     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13631     int nBuffers;
13632     int i;
13633     int archived;
13634     char *arcDir;
13635
13636     if (! cmailMsgLoaded) {
13637         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13638         return;
13639     }
13640
13641     if (nCmailGames == nCmailResults) {
13642         DisplayError(_("No unfinished games"), 0);
13643         return;
13644     }
13645
13646 #if CMAIL_PROHIBIT_REMAIL
13647     if (cmailMailedMove) {
13648       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);
13649         DisplayError(msg, 0);
13650         return;
13651     }
13652 #endif
13653
13654     if (! (cmailMailedMove || RegisterMove())) return;
13655
13656     if (   cmailMailedMove
13657         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13658       snprintf(string, MSG_SIZ, partCommandString,
13659                appData.debugMode ? " -v" : "", appData.cmailGameName);
13660         commandOutput = popen(string, "r");
13661
13662         if (commandOutput == NULL) {
13663             DisplayError(_("Failed to invoke cmail"), 0);
13664         } else {
13665             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13666                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13667             }
13668             if (nBuffers > 1) {
13669                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13670                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13671                 nBytes = MSG_SIZ - 1;
13672             } else {
13673                 (void) memcpy(msg, buffer, nBytes);
13674             }
13675             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13676
13677             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13678                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13679
13680                 archived = TRUE;
13681                 for (i = 0; i < nCmailGames; i ++) {
13682                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13683                         archived = FALSE;
13684                     }
13685                 }
13686                 if (   archived
13687                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13688                         != NULL)) {
13689                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13690                            arcDir,
13691                            appData.cmailGameName,
13692                            gameInfo.date);
13693                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13694                     cmailMsgLoaded = FALSE;
13695                 }
13696             }
13697
13698             DisplayInformation(msg);
13699             pclose(commandOutput);
13700         }
13701     } else {
13702         if ((*cmailMsg) != '\0') {
13703             DisplayInformation(cmailMsg);
13704         }
13705     }
13706
13707     return;
13708 #endif /* !WIN32 */
13709 }
13710
13711 char *
13712 CmailMsg ()
13713 {
13714 #if WIN32
13715     return NULL;
13716 #else
13717     int  prependComma = 0;
13718     char number[5];
13719     char string[MSG_SIZ];       /* Space for game-list */
13720     int  i;
13721
13722     if (!cmailMsgLoaded) return "";
13723
13724     if (cmailMailedMove) {
13725       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13726     } else {
13727         /* Create a list of games left */
13728       snprintf(string, MSG_SIZ, "[");
13729         for (i = 0; i < nCmailGames; i ++) {
13730             if (! (   cmailMoveRegistered[i]
13731                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13732                 if (prependComma) {
13733                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13734                 } else {
13735                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13736                     prependComma = 1;
13737                 }
13738
13739                 strcat(string, number);
13740             }
13741         }
13742         strcat(string, "]");
13743
13744         if (nCmailMovesRegistered + nCmailResults == 0) {
13745             switch (nCmailGames) {
13746               case 1:
13747                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13748                 break;
13749
13750               case 2:
13751                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13752                 break;
13753
13754               default:
13755                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13756                          nCmailGames);
13757                 break;
13758             }
13759         } else {
13760             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13761               case 1:
13762                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13763                          string);
13764                 break;
13765
13766               case 0:
13767                 if (nCmailResults == nCmailGames) {
13768                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13769                 } else {
13770                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13771                 }
13772                 break;
13773
13774               default:
13775                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13776                          string);
13777             }
13778         }
13779     }
13780     return cmailMsg;
13781 #endif /* WIN32 */
13782 }
13783
13784 void
13785 ResetGameEvent ()
13786 {
13787     if (gameMode == Training)
13788       SetTrainingModeOff();
13789
13790     Reset(TRUE, TRUE);
13791     cmailMsgLoaded = FALSE;
13792     if (appData.icsActive) {
13793       SendToICS(ics_prefix);
13794       SendToICS("refresh\n");
13795     }
13796 }
13797
13798 void
13799 ExitEvent (int status)
13800 {
13801     exiting++;
13802     if (exiting > 2) {
13803       /* Give up on clean exit */
13804       exit(status);
13805     }
13806     if (exiting > 1) {
13807       /* Keep trying for clean exit */
13808       return;
13809     }
13810
13811     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13812
13813     if (telnetISR != NULL) {
13814       RemoveInputSource(telnetISR);
13815     }
13816     if (icsPR != NoProc) {
13817       DestroyChildProcess(icsPR, TRUE);
13818     }
13819
13820     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13821     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13822
13823     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13824     /* make sure this other one finishes before killing it!                  */
13825     if(endingGame) { int count = 0;
13826         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13827         while(endingGame && count++ < 10) DoSleep(1);
13828         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13829     }
13830
13831     /* Kill off chess programs */
13832     if (first.pr != NoProc) {
13833         ExitAnalyzeMode();
13834
13835         DoSleep( appData.delayBeforeQuit );
13836         SendToProgram("quit\n", &first);
13837         DoSleep( appData.delayAfterQuit );
13838         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13839     }
13840     if (second.pr != NoProc) {
13841         DoSleep( appData.delayBeforeQuit );
13842         SendToProgram("quit\n", &second);
13843         DoSleep( appData.delayAfterQuit );
13844         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13845     }
13846     if (first.isr != NULL) {
13847         RemoveInputSource(first.isr);
13848     }
13849     if (second.isr != NULL) {
13850         RemoveInputSource(second.isr);
13851     }
13852
13853     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13854     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13855
13856     ShutDownFrontEnd();
13857     exit(status);
13858 }
13859
13860 void
13861 PauseEngine (ChessProgramState *cps)
13862 {
13863     SendToProgram("pause\n", cps);
13864     cps->pause = 2;
13865 }
13866
13867 void
13868 UnPauseEngine (ChessProgramState *cps)
13869 {
13870     SendToProgram("resume\n", cps);
13871     cps->pause = 1;
13872 }
13873
13874 void
13875 PauseEvent ()
13876 {
13877     if (appData.debugMode)
13878         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13879     if (pausing) {
13880         pausing = FALSE;
13881         ModeHighlight();
13882         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13883             StartClocks();
13884             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13885                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13886                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13887             }
13888             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13889             HandleMachineMove(stashedInputMove, stalledEngine);
13890             stalledEngine = NULL;
13891             return;
13892         }
13893         if (gameMode == MachinePlaysWhite ||
13894             gameMode == TwoMachinesPlay   ||
13895             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13896             if(first.pause)  UnPauseEngine(&first);
13897             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13898             if(second.pause) UnPauseEngine(&second);
13899             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13900             StartClocks();
13901         } else {
13902             DisplayBothClocks();
13903         }
13904         if (gameMode == PlayFromGameFile) {
13905             if (appData.timeDelay >= 0)
13906                 AutoPlayGameLoop();
13907         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13908             Reset(FALSE, TRUE);
13909             SendToICS(ics_prefix);
13910             SendToICS("refresh\n");
13911         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13912             ForwardInner(forwardMostMove);
13913         }
13914         pauseExamInvalid = FALSE;
13915     } else {
13916         switch (gameMode) {
13917           default:
13918             return;
13919           case IcsExamining:
13920             pauseExamForwardMostMove = forwardMostMove;
13921             pauseExamInvalid = FALSE;
13922             /* fall through */
13923           case IcsObserving:
13924           case IcsPlayingWhite:
13925           case IcsPlayingBlack:
13926             pausing = TRUE;
13927             ModeHighlight();
13928             return;
13929           case PlayFromGameFile:
13930             (void) StopLoadGameTimer();
13931             pausing = TRUE;
13932             ModeHighlight();
13933             break;
13934           case BeginningOfGame:
13935             if (appData.icsActive) return;
13936             /* else fall through */
13937           case MachinePlaysWhite:
13938           case MachinePlaysBlack:
13939           case TwoMachinesPlay:
13940             if (forwardMostMove == 0)
13941               return;           /* don't pause if no one has moved */
13942             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13943                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13944                 if(onMove->pause) {           // thinking engine can be paused
13945                     PauseEngine(onMove);      // do it
13946                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13947                         PauseEngine(onMove->other);
13948                     else
13949                         SendToProgram("easy\n", onMove->other);
13950                     StopClocks();
13951                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13952             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13953                 if(first.pause) {
13954                     PauseEngine(&first);
13955                     StopClocks();
13956                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13957             } else { // human on move, pause pondering by either method
13958                 if(first.pause)
13959                     PauseEngine(&first);
13960                 else if(appData.ponderNextMove)
13961                     SendToProgram("easy\n", &first);
13962                 StopClocks();
13963             }
13964             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13965           case AnalyzeMode:
13966             pausing = TRUE;
13967             ModeHighlight();
13968             break;
13969         }
13970     }
13971 }
13972
13973 void
13974 EditCommentEvent ()
13975 {
13976     char title[MSG_SIZ];
13977
13978     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13979       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13980     } else {
13981       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13982                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13983                parseList[currentMove - 1]);
13984     }
13985
13986     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13987 }
13988
13989
13990 void
13991 EditTagsEvent ()
13992 {
13993     char *tags = PGNTags(&gameInfo);
13994     bookUp = FALSE;
13995     EditTagsPopUp(tags, NULL);
13996     free(tags);
13997 }
13998
13999 void
14000 ToggleSecond ()
14001 {
14002   if(second.analyzing) {
14003     SendToProgram("exit\n", &second);
14004     second.analyzing = FALSE;
14005   } else {
14006     if (second.pr == NoProc) StartChessProgram(&second);
14007     InitChessProgram(&second, FALSE);
14008     FeedMovesToProgram(&second, currentMove);
14009
14010     SendToProgram("analyze\n", &second);
14011     second.analyzing = TRUE;
14012   }
14013 }
14014
14015 /* Toggle ShowThinking */
14016 void
14017 ToggleShowThinking()
14018 {
14019   appData.showThinking = !appData.showThinking;
14020   ShowThinkingEvent();
14021 }
14022
14023 int
14024 AnalyzeModeEvent ()
14025 {
14026     char buf[MSG_SIZ];
14027
14028     if (!first.analysisSupport) {
14029       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14030       DisplayError(buf, 0);
14031       return 0;
14032     }
14033     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14034     if (appData.icsActive) {
14035         if (gameMode != IcsObserving) {
14036           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14037             DisplayError(buf, 0);
14038             /* secure check */
14039             if (appData.icsEngineAnalyze) {
14040                 if (appData.debugMode)
14041                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14042                 ExitAnalyzeMode();
14043                 ModeHighlight();
14044             }
14045             return 0;
14046         }
14047         /* if enable, user wants to disable icsEngineAnalyze */
14048         if (appData.icsEngineAnalyze) {
14049                 ExitAnalyzeMode();
14050                 ModeHighlight();
14051                 return 0;
14052         }
14053         appData.icsEngineAnalyze = TRUE;
14054         if (appData.debugMode)
14055             fprintf(debugFP, "ICS engine analyze starting... \n");
14056     }
14057
14058     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14059     if (appData.noChessProgram || gameMode == AnalyzeMode)
14060       return 0;
14061
14062     if (gameMode != AnalyzeFile) {
14063         if (!appData.icsEngineAnalyze) {
14064                EditGameEvent();
14065                if (gameMode != EditGame) return 0;
14066         }
14067         if (!appData.showThinking) ToggleShowThinking();
14068         ResurrectChessProgram();
14069         SendToProgram("analyze\n", &first);
14070         first.analyzing = TRUE;
14071         /*first.maybeThinking = TRUE;*/
14072         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14073         EngineOutputPopUp();
14074     }
14075     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14076     pausing = FALSE;
14077     ModeHighlight();
14078     SetGameInfo();
14079
14080     StartAnalysisClock();
14081     GetTimeMark(&lastNodeCountTime);
14082     lastNodeCount = 0;
14083     return 1;
14084 }
14085
14086 void
14087 AnalyzeFileEvent ()
14088 {
14089     if (appData.noChessProgram || gameMode == AnalyzeFile)
14090       return;
14091
14092     if (!first.analysisSupport) {
14093       char buf[MSG_SIZ];
14094       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14095       DisplayError(buf, 0);
14096       return;
14097     }
14098
14099     if (gameMode != AnalyzeMode) {
14100         keepInfo = 1; // mere annotating should not alter PGN tags
14101         EditGameEvent();
14102         keepInfo = 0;
14103         if (gameMode != EditGame) return;
14104         if (!appData.showThinking) ToggleShowThinking();
14105         ResurrectChessProgram();
14106         SendToProgram("analyze\n", &first);
14107         first.analyzing = TRUE;
14108         /*first.maybeThinking = TRUE;*/
14109         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14110         EngineOutputPopUp();
14111     }
14112     gameMode = AnalyzeFile;
14113     pausing = FALSE;
14114     ModeHighlight();
14115
14116     StartAnalysisClock();
14117     GetTimeMark(&lastNodeCountTime);
14118     lastNodeCount = 0;
14119     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14120     AnalysisPeriodicEvent(1);
14121 }
14122
14123 void
14124 MachineWhiteEvent ()
14125 {
14126     char buf[MSG_SIZ];
14127     char *bookHit = NULL;
14128
14129     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14130       return;
14131
14132
14133     if (gameMode == PlayFromGameFile ||
14134         gameMode == TwoMachinesPlay  ||
14135         gameMode == Training         ||
14136         gameMode == AnalyzeMode      ||
14137         gameMode == EndOfGame)
14138         EditGameEvent();
14139
14140     if (gameMode == EditPosition)
14141         EditPositionDone(TRUE);
14142
14143     if (!WhiteOnMove(currentMove)) {
14144         DisplayError(_("It is not White's turn"), 0);
14145         return;
14146     }
14147
14148     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14149       ExitAnalyzeMode();
14150
14151     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14152         gameMode == AnalyzeFile)
14153         TruncateGame();
14154
14155     ResurrectChessProgram();    /* in case it isn't running */
14156     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14157         gameMode = MachinePlaysWhite;
14158         ResetClocks();
14159     } else
14160     gameMode = MachinePlaysWhite;
14161     pausing = FALSE;
14162     ModeHighlight();
14163     SetGameInfo();
14164     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14165     DisplayTitle(buf);
14166     if (first.sendName) {
14167       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14168       SendToProgram(buf, &first);
14169     }
14170     if (first.sendTime) {
14171       if (first.useColors) {
14172         SendToProgram("black\n", &first); /*gnu kludge*/
14173       }
14174       SendTimeRemaining(&first, TRUE);
14175     }
14176     if (first.useColors) {
14177       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14178     }
14179     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14180     SetMachineThinkingEnables();
14181     first.maybeThinking = TRUE;
14182     StartClocks();
14183     firstMove = FALSE;
14184
14185     if (appData.autoFlipView && !flipView) {
14186       flipView = !flipView;
14187       DrawPosition(FALSE, NULL);
14188       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14189     }
14190
14191     if(bookHit) { // [HGM] book: simulate book reply
14192         static char bookMove[MSG_SIZ]; // a bit generous?
14193
14194         programStats.nodes = programStats.depth = programStats.time =
14195         programStats.score = programStats.got_only_move = 0;
14196         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14197
14198         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14199         strcat(bookMove, bookHit);
14200         HandleMachineMove(bookMove, &first);
14201     }
14202 }
14203
14204 void
14205 MachineBlackEvent ()
14206 {
14207   char buf[MSG_SIZ];
14208   char *bookHit = NULL;
14209
14210     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14211         return;
14212
14213
14214     if (gameMode == PlayFromGameFile ||
14215         gameMode == TwoMachinesPlay  ||
14216         gameMode == Training         ||
14217         gameMode == AnalyzeMode      ||
14218         gameMode == EndOfGame)
14219         EditGameEvent();
14220
14221     if (gameMode == EditPosition)
14222         EditPositionDone(TRUE);
14223
14224     if (WhiteOnMove(currentMove)) {
14225         DisplayError(_("It is not Black's turn"), 0);
14226         return;
14227     }
14228
14229     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14230       ExitAnalyzeMode();
14231
14232     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14233         gameMode == AnalyzeFile)
14234         TruncateGame();
14235
14236     ResurrectChessProgram();    /* in case it isn't running */
14237     gameMode = MachinePlaysBlack;
14238     pausing = FALSE;
14239     ModeHighlight();
14240     SetGameInfo();
14241     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14242     DisplayTitle(buf);
14243     if (first.sendName) {
14244       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14245       SendToProgram(buf, &first);
14246     }
14247     if (first.sendTime) {
14248       if (first.useColors) {
14249         SendToProgram("white\n", &first); /*gnu kludge*/
14250       }
14251       SendTimeRemaining(&first, FALSE);
14252     }
14253     if (first.useColors) {
14254       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14255     }
14256     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14257     SetMachineThinkingEnables();
14258     first.maybeThinking = TRUE;
14259     StartClocks();
14260
14261     if (appData.autoFlipView && flipView) {
14262       flipView = !flipView;
14263       DrawPosition(FALSE, NULL);
14264       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14265     }
14266     if(bookHit) { // [HGM] book: simulate book reply
14267         static char bookMove[MSG_SIZ]; // a bit generous?
14268
14269         programStats.nodes = programStats.depth = programStats.time =
14270         programStats.score = programStats.got_only_move = 0;
14271         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14272
14273         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14274         strcat(bookMove, bookHit);
14275         HandleMachineMove(bookMove, &first);
14276     }
14277 }
14278
14279
14280 void
14281 DisplayTwoMachinesTitle ()
14282 {
14283     char buf[MSG_SIZ];
14284     if (appData.matchGames > 0) {
14285         if(appData.tourneyFile[0]) {
14286           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14287                    gameInfo.white, _("vs."), gameInfo.black,
14288                    nextGame+1, appData.matchGames+1,
14289                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14290         } else
14291         if (first.twoMachinesColor[0] == 'w') {
14292           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14293                    gameInfo.white, _("vs."),  gameInfo.black,
14294                    first.matchWins, second.matchWins,
14295                    matchGame - 1 - (first.matchWins + second.matchWins));
14296         } else {
14297           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14298                    gameInfo.white, _("vs."), gameInfo.black,
14299                    second.matchWins, first.matchWins,
14300                    matchGame - 1 - (first.matchWins + second.matchWins));
14301         }
14302     } else {
14303       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14304     }
14305     DisplayTitle(buf);
14306 }
14307
14308 void
14309 SettingsMenuIfReady ()
14310 {
14311   if (second.lastPing != second.lastPong) {
14312     DisplayMessage("", _("Waiting for second chess program"));
14313     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14314     return;
14315   }
14316   ThawUI();
14317   DisplayMessage("", "");
14318   SettingsPopUp(&second);
14319 }
14320
14321 int
14322 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14323 {
14324     char buf[MSG_SIZ];
14325     if (cps->pr == NoProc) {
14326         StartChessProgram(cps);
14327         if (cps->protocolVersion == 1) {
14328           retry();
14329           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14330         } else {
14331           /* kludge: allow timeout for initial "feature" command */
14332           if(retry != TwoMachinesEventIfReady) FreezeUI();
14333           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14334           DisplayMessage("", buf);
14335           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14336         }
14337         return 1;
14338     }
14339     return 0;
14340 }
14341
14342 void
14343 TwoMachinesEvent P((void))
14344 {
14345     int i;
14346     char buf[MSG_SIZ];
14347     ChessProgramState *onmove;
14348     char *bookHit = NULL;
14349     static int stalling = 0;
14350     TimeMark now;
14351     long wait;
14352
14353     if (appData.noChessProgram) return;
14354
14355     switch (gameMode) {
14356       case TwoMachinesPlay:
14357         return;
14358       case MachinePlaysWhite:
14359       case MachinePlaysBlack:
14360         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14361             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14362             return;
14363         }
14364         /* fall through */
14365       case BeginningOfGame:
14366       case PlayFromGameFile:
14367       case EndOfGame:
14368         EditGameEvent();
14369         if (gameMode != EditGame) return;
14370         break;
14371       case EditPosition:
14372         EditPositionDone(TRUE);
14373         break;
14374       case AnalyzeMode:
14375       case AnalyzeFile:
14376         ExitAnalyzeMode();
14377         break;
14378       case EditGame:
14379       default:
14380         break;
14381     }
14382
14383 //    forwardMostMove = currentMove;
14384     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14385     startingEngine = TRUE;
14386
14387     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14388
14389     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14390     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14391       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14392       return;
14393     }
14394     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14395
14396     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14397                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14398         startingEngine = FALSE;
14399         DisplayError("second engine does not play this", 0);
14400         return;
14401     }
14402
14403     if(!stalling) {
14404       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14405       SendToProgram("force\n", &second);
14406       stalling = 1;
14407       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14408       return;
14409     }
14410     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14411     if(appData.matchPause>10000 || appData.matchPause<10)
14412                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14413     wait = SubtractTimeMarks(&now, &pauseStart);
14414     if(wait < appData.matchPause) {
14415         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14416         return;
14417     }
14418     // we are now committed to starting the game
14419     stalling = 0;
14420     DisplayMessage("", "");
14421     if (startedFromSetupPosition) {
14422         SendBoard(&second, backwardMostMove);
14423     if (appData.debugMode) {
14424         fprintf(debugFP, "Two Machines\n");
14425     }
14426     }
14427     for (i = backwardMostMove; i < forwardMostMove; i++) {
14428         SendMoveToProgram(i, &second);
14429     }
14430
14431     gameMode = TwoMachinesPlay;
14432     pausing = startingEngine = FALSE;
14433     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14434     SetGameInfo();
14435     DisplayTwoMachinesTitle();
14436     firstMove = TRUE;
14437     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14438         onmove = &first;
14439     } else {
14440         onmove = &second;
14441     }
14442     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14443     SendToProgram(first.computerString, &first);
14444     if (first.sendName) {
14445       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14446       SendToProgram(buf, &first);
14447     }
14448     SendToProgram(second.computerString, &second);
14449     if (second.sendName) {
14450       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14451       SendToProgram(buf, &second);
14452     }
14453
14454     ResetClocks();
14455     if (!first.sendTime || !second.sendTime) {
14456         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14457         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14458     }
14459     if (onmove->sendTime) {
14460       if (onmove->useColors) {
14461         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14462       }
14463       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14464     }
14465     if (onmove->useColors) {
14466       SendToProgram(onmove->twoMachinesColor, onmove);
14467     }
14468     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14469 //    SendToProgram("go\n", onmove);
14470     onmove->maybeThinking = TRUE;
14471     SetMachineThinkingEnables();
14472
14473     StartClocks();
14474
14475     if(bookHit) { // [HGM] book: simulate book reply
14476         static char bookMove[MSG_SIZ]; // a bit generous?
14477
14478         programStats.nodes = programStats.depth = programStats.time =
14479         programStats.score = programStats.got_only_move = 0;
14480         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14481
14482         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14483         strcat(bookMove, bookHit);
14484         savedMessage = bookMove; // args for deferred call
14485         savedState = onmove;
14486         ScheduleDelayedEvent(DeferredBookMove, 1);
14487     }
14488 }
14489
14490 void
14491 TrainingEvent ()
14492 {
14493     if (gameMode == Training) {
14494       SetTrainingModeOff();
14495       gameMode = PlayFromGameFile;
14496       DisplayMessage("", _("Training mode off"));
14497     } else {
14498       gameMode = Training;
14499       animateTraining = appData.animate;
14500
14501       /* make sure we are not already at the end of the game */
14502       if (currentMove < forwardMostMove) {
14503         SetTrainingModeOn();
14504         DisplayMessage("", _("Training mode on"));
14505       } else {
14506         gameMode = PlayFromGameFile;
14507         DisplayError(_("Already at end of game"), 0);
14508       }
14509     }
14510     ModeHighlight();
14511 }
14512
14513 void
14514 IcsClientEvent ()
14515 {
14516     if (!appData.icsActive) return;
14517     switch (gameMode) {
14518       case IcsPlayingWhite:
14519       case IcsPlayingBlack:
14520       case IcsObserving:
14521       case IcsIdle:
14522       case BeginningOfGame:
14523       case IcsExamining:
14524         return;
14525
14526       case EditGame:
14527         break;
14528
14529       case EditPosition:
14530         EditPositionDone(TRUE);
14531         break;
14532
14533       case AnalyzeMode:
14534       case AnalyzeFile:
14535         ExitAnalyzeMode();
14536         break;
14537
14538       default:
14539         EditGameEvent();
14540         break;
14541     }
14542
14543     gameMode = IcsIdle;
14544     ModeHighlight();
14545     return;
14546 }
14547
14548 void
14549 EditGameEvent ()
14550 {
14551     int i;
14552
14553     switch (gameMode) {
14554       case Training:
14555         SetTrainingModeOff();
14556         break;
14557       case MachinePlaysWhite:
14558       case MachinePlaysBlack:
14559       case BeginningOfGame:
14560         SendToProgram("force\n", &first);
14561         SetUserThinkingEnables();
14562         break;
14563       case PlayFromGameFile:
14564         (void) StopLoadGameTimer();
14565         if (gameFileFP != NULL) {
14566             gameFileFP = NULL;
14567         }
14568         break;
14569       case EditPosition:
14570         EditPositionDone(TRUE);
14571         break;
14572       case AnalyzeMode:
14573       case AnalyzeFile:
14574         ExitAnalyzeMode();
14575         SendToProgram("force\n", &first);
14576         break;
14577       case TwoMachinesPlay:
14578         GameEnds(EndOfFile, NULL, GE_PLAYER);
14579         ResurrectChessProgram();
14580         SetUserThinkingEnables();
14581         break;
14582       case EndOfGame:
14583         ResurrectChessProgram();
14584         break;
14585       case IcsPlayingBlack:
14586       case IcsPlayingWhite:
14587         DisplayError(_("Warning: You are still playing a game"), 0);
14588         break;
14589       case IcsObserving:
14590         DisplayError(_("Warning: You are still observing a game"), 0);
14591         break;
14592       case IcsExamining:
14593         DisplayError(_("Warning: You are still examining a game"), 0);
14594         break;
14595       case IcsIdle:
14596         break;
14597       case EditGame:
14598       default:
14599         return;
14600     }
14601
14602     pausing = FALSE;
14603     StopClocks();
14604     first.offeredDraw = second.offeredDraw = 0;
14605
14606     if (gameMode == PlayFromGameFile) {
14607         whiteTimeRemaining = timeRemaining[0][currentMove];
14608         blackTimeRemaining = timeRemaining[1][currentMove];
14609         DisplayTitle("");
14610     }
14611
14612     if (gameMode == MachinePlaysWhite ||
14613         gameMode == MachinePlaysBlack ||
14614         gameMode == TwoMachinesPlay ||
14615         gameMode == EndOfGame) {
14616         i = forwardMostMove;
14617         while (i > currentMove) {
14618             SendToProgram("undo\n", &first);
14619             i--;
14620         }
14621         if(!adjustedClock) {
14622         whiteTimeRemaining = timeRemaining[0][currentMove];
14623         blackTimeRemaining = timeRemaining[1][currentMove];
14624         DisplayBothClocks();
14625         }
14626         if (whiteFlag || blackFlag) {
14627             whiteFlag = blackFlag = 0;
14628         }
14629         DisplayTitle("");
14630     }
14631
14632     gameMode = EditGame;
14633     ModeHighlight();
14634     SetGameInfo();
14635 }
14636
14637
14638 void
14639 EditPositionEvent ()
14640 {
14641     if (gameMode == EditPosition) {
14642         EditGameEvent();
14643         return;
14644     }
14645
14646     EditGameEvent();
14647     if (gameMode != EditGame) return;
14648
14649     gameMode = EditPosition;
14650     ModeHighlight();
14651     SetGameInfo();
14652     if (currentMove > 0)
14653       CopyBoard(boards[0], boards[currentMove]);
14654
14655     blackPlaysFirst = !WhiteOnMove(currentMove);
14656     ResetClocks();
14657     currentMove = forwardMostMove = backwardMostMove = 0;
14658     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14659     DisplayMove(-1);
14660     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14661 }
14662
14663 void
14664 ExitAnalyzeMode ()
14665 {
14666     /* [DM] icsEngineAnalyze - possible call from other functions */
14667     if (appData.icsEngineAnalyze) {
14668         appData.icsEngineAnalyze = FALSE;
14669
14670         DisplayMessage("",_("Close ICS engine analyze..."));
14671     }
14672     if (first.analysisSupport && first.analyzing) {
14673       SendToBoth("exit\n");
14674       first.analyzing = second.analyzing = FALSE;
14675     }
14676     thinkOutput[0] = NULLCHAR;
14677 }
14678
14679 void
14680 EditPositionDone (Boolean fakeRights)
14681 {
14682     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14683
14684     startedFromSetupPosition = TRUE;
14685     InitChessProgram(&first, FALSE);
14686     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14687       boards[0][EP_STATUS] = EP_NONE;
14688       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14689       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14690         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14691         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14692       } else boards[0][CASTLING][2] = NoRights;
14693       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14694         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14695         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14696       } else boards[0][CASTLING][5] = NoRights;
14697       if(gameInfo.variant == VariantSChess) {
14698         int i;
14699         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14700           boards[0][VIRGIN][i] = 0;
14701           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14702           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14703         }
14704       }
14705     }
14706     SendToProgram("force\n", &first);
14707     if (blackPlaysFirst) {
14708         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14709         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14710         currentMove = forwardMostMove = backwardMostMove = 1;
14711         CopyBoard(boards[1], boards[0]);
14712     } else {
14713         currentMove = forwardMostMove = backwardMostMove = 0;
14714     }
14715     SendBoard(&first, forwardMostMove);
14716     if (appData.debugMode) {
14717         fprintf(debugFP, "EditPosDone\n");
14718     }
14719     DisplayTitle("");
14720     DisplayMessage("", "");
14721     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14722     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14723     gameMode = EditGame;
14724     ModeHighlight();
14725     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14726     ClearHighlights(); /* [AS] */
14727 }
14728
14729 /* Pause for `ms' milliseconds */
14730 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14731 void
14732 TimeDelay (long ms)
14733 {
14734     TimeMark m1, m2;
14735
14736     GetTimeMark(&m1);
14737     do {
14738         GetTimeMark(&m2);
14739     } while (SubtractTimeMarks(&m2, &m1) < ms);
14740 }
14741
14742 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14743 void
14744 SendMultiLineToICS (char *buf)
14745 {
14746     char temp[MSG_SIZ+1], *p;
14747     int len;
14748
14749     len = strlen(buf);
14750     if (len > MSG_SIZ)
14751       len = MSG_SIZ;
14752
14753     strncpy(temp, buf, len);
14754     temp[len] = 0;
14755
14756     p = temp;
14757     while (*p) {
14758         if (*p == '\n' || *p == '\r')
14759           *p = ' ';
14760         ++p;
14761     }
14762
14763     strcat(temp, "\n");
14764     SendToICS(temp);
14765     SendToPlayer(temp, strlen(temp));
14766 }
14767
14768 void
14769 SetWhiteToPlayEvent ()
14770 {
14771     if (gameMode == EditPosition) {
14772         blackPlaysFirst = FALSE;
14773         DisplayBothClocks();    /* works because currentMove is 0 */
14774     } else if (gameMode == IcsExamining) {
14775         SendToICS(ics_prefix);
14776         SendToICS("tomove white\n");
14777     }
14778 }
14779
14780 void
14781 SetBlackToPlayEvent ()
14782 {
14783     if (gameMode == EditPosition) {
14784         blackPlaysFirst = TRUE;
14785         currentMove = 1;        /* kludge */
14786         DisplayBothClocks();
14787         currentMove = 0;
14788     } else if (gameMode == IcsExamining) {
14789         SendToICS(ics_prefix);
14790         SendToICS("tomove black\n");
14791     }
14792 }
14793
14794 void
14795 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14796 {
14797     char buf[MSG_SIZ];
14798     ChessSquare piece = boards[0][y][x];
14799     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14800     static int lastVariant;
14801
14802     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14803
14804     switch (selection) {
14805       case ClearBoard:
14806         CopyBoard(currentBoard, boards[0]);
14807         CopyBoard(menuBoard, initialPosition);
14808         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14809             SendToICS(ics_prefix);
14810             SendToICS("bsetup clear\n");
14811         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14812             SendToICS(ics_prefix);
14813             SendToICS("clearboard\n");
14814         } else {
14815             int nonEmpty = 0;
14816             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14817                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14818                 for (y = 0; y < BOARD_HEIGHT; y++) {
14819                     if (gameMode == IcsExamining) {
14820                         if (boards[currentMove][y][x] != EmptySquare) {
14821                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14822                                     AAA + x, ONE + y);
14823                             SendToICS(buf);
14824                         }
14825                     } else {
14826                         if(boards[0][y][x] != p) nonEmpty++;
14827                         boards[0][y][x] = p;
14828                     }
14829                 }
14830                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14831             }
14832             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14833                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14834                     ChessSquare p = menuBoard[0][x];
14835                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14836                     p = menuBoard[BOARD_HEIGHT-1][x];
14837                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14838                 }
14839                 DisplayMessage("Clicking clock again restores position", "");
14840                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14841                 if(!nonEmpty) { // asked to clear an empty board
14842                     CopyBoard(boards[0], menuBoard);
14843                 } else
14844                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14845                     CopyBoard(boards[0], initialPosition);
14846                 } else
14847                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14848                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14849                     CopyBoard(boards[0], erasedBoard);
14850                 } else
14851                     CopyBoard(erasedBoard, currentBoard);
14852
14853             }
14854         }
14855         if (gameMode == EditPosition) {
14856             DrawPosition(FALSE, boards[0]);
14857         }
14858         break;
14859
14860       case WhitePlay:
14861         SetWhiteToPlayEvent();
14862         break;
14863
14864       case BlackPlay:
14865         SetBlackToPlayEvent();
14866         break;
14867
14868       case EmptySquare:
14869         if (gameMode == IcsExamining) {
14870             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14871             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14872             SendToICS(buf);
14873         } else {
14874             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14875                 if(x == BOARD_LEFT-2) {
14876                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14877                     boards[0][y][1] = 0;
14878                 } else
14879                 if(x == BOARD_RGHT+1) {
14880                     if(y >= gameInfo.holdingsSize) break;
14881                     boards[0][y][BOARD_WIDTH-2] = 0;
14882                 } else break;
14883             }
14884             boards[0][y][x] = EmptySquare;
14885             DrawPosition(FALSE, boards[0]);
14886         }
14887         break;
14888
14889       case PromotePiece:
14890         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14891            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14892             selection = (ChessSquare) (PROMOTED piece);
14893         } else if(piece == EmptySquare) selection = WhiteSilver;
14894         else selection = (ChessSquare)((int)piece - 1);
14895         goto defaultlabel;
14896
14897       case DemotePiece:
14898         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14899            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14900             selection = (ChessSquare) (DEMOTED piece);
14901         } else if(piece == EmptySquare) selection = BlackSilver;
14902         else selection = (ChessSquare)((int)piece + 1);
14903         goto defaultlabel;
14904
14905       case WhiteQueen:
14906       case BlackQueen:
14907         if(gameInfo.variant == VariantShatranj ||
14908            gameInfo.variant == VariantXiangqi  ||
14909            gameInfo.variant == VariantCourier  ||
14910            gameInfo.variant == VariantASEAN    ||
14911            gameInfo.variant == VariantMakruk     )
14912             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14913         goto defaultlabel;
14914
14915       case WhiteKing:
14916       case BlackKing:
14917         if(gameInfo.variant == VariantXiangqi)
14918             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14919         if(gameInfo.variant == VariantKnightmate)
14920             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14921       default:
14922         defaultlabel:
14923         if (gameMode == IcsExamining) {
14924             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14925             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14926                      PieceToChar(selection), AAA + x, ONE + y);
14927             SendToICS(buf);
14928         } else {
14929             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14930                 int n;
14931                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14932                     n = PieceToNumber(selection - BlackPawn);
14933                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14934                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14935                     boards[0][BOARD_HEIGHT-1-n][1]++;
14936                 } else
14937                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14938                     n = PieceToNumber(selection);
14939                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14940                     boards[0][n][BOARD_WIDTH-1] = selection;
14941                     boards[0][n][BOARD_WIDTH-2]++;
14942                 }
14943             } else
14944             boards[0][y][x] = selection;
14945             DrawPosition(TRUE, boards[0]);
14946             ClearHighlights();
14947             fromX = fromY = -1;
14948         }
14949         break;
14950     }
14951 }
14952
14953
14954 void
14955 DropMenuEvent (ChessSquare selection, int x, int y)
14956 {
14957     ChessMove moveType;
14958
14959     switch (gameMode) {
14960       case IcsPlayingWhite:
14961       case MachinePlaysBlack:
14962         if (!WhiteOnMove(currentMove)) {
14963             DisplayMoveError(_("It is Black's turn"));
14964             return;
14965         }
14966         moveType = WhiteDrop;
14967         break;
14968       case IcsPlayingBlack:
14969       case MachinePlaysWhite:
14970         if (WhiteOnMove(currentMove)) {
14971             DisplayMoveError(_("It is White's turn"));
14972             return;
14973         }
14974         moveType = BlackDrop;
14975         break;
14976       case EditGame:
14977         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14978         break;
14979       default:
14980         return;
14981     }
14982
14983     if (moveType == BlackDrop && selection < BlackPawn) {
14984       selection = (ChessSquare) ((int) selection
14985                                  + (int) BlackPawn - (int) WhitePawn);
14986     }
14987     if (boards[currentMove][y][x] != EmptySquare) {
14988         DisplayMoveError(_("That square is occupied"));
14989         return;
14990     }
14991
14992     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14993 }
14994
14995 void
14996 AcceptEvent ()
14997 {
14998     /* Accept a pending offer of any kind from opponent */
14999
15000     if (appData.icsActive) {
15001         SendToICS(ics_prefix);
15002         SendToICS("accept\n");
15003     } else if (cmailMsgLoaded) {
15004         if (currentMove == cmailOldMove &&
15005             commentList[cmailOldMove] != NULL &&
15006             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15007                    "Black offers a draw" : "White offers a draw")) {
15008             TruncateGame();
15009             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15010             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15011         } else {
15012             DisplayError(_("There is no pending offer on this move"), 0);
15013             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15014         }
15015     } else {
15016         /* Not used for offers from chess program */
15017     }
15018 }
15019
15020 void
15021 DeclineEvent ()
15022 {
15023     /* Decline a pending offer of any kind from opponent */
15024
15025     if (appData.icsActive) {
15026         SendToICS(ics_prefix);
15027         SendToICS("decline\n");
15028     } else if (cmailMsgLoaded) {
15029         if (currentMove == cmailOldMove &&
15030             commentList[cmailOldMove] != NULL &&
15031             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15032                    "Black offers a draw" : "White offers a draw")) {
15033 #ifdef NOTDEF
15034             AppendComment(cmailOldMove, "Draw declined", TRUE);
15035             DisplayComment(cmailOldMove - 1, "Draw declined");
15036 #endif /*NOTDEF*/
15037         } else {
15038             DisplayError(_("There is no pending offer on this move"), 0);
15039         }
15040     } else {
15041         /* Not used for offers from chess program */
15042     }
15043 }
15044
15045 void
15046 RematchEvent ()
15047 {
15048     /* Issue ICS rematch command */
15049     if (appData.icsActive) {
15050         SendToICS(ics_prefix);
15051         SendToICS("rematch\n");
15052     }
15053 }
15054
15055 void
15056 CallFlagEvent ()
15057 {
15058     /* Call your opponent's flag (claim a win on time) */
15059     if (appData.icsActive) {
15060         SendToICS(ics_prefix);
15061         SendToICS("flag\n");
15062     } else {
15063         switch (gameMode) {
15064           default:
15065             return;
15066           case MachinePlaysWhite:
15067             if (whiteFlag) {
15068                 if (blackFlag)
15069                   GameEnds(GameIsDrawn, "Both players ran out of time",
15070                            GE_PLAYER);
15071                 else
15072                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15073             } else {
15074                 DisplayError(_("Your opponent is not out of time"), 0);
15075             }
15076             break;
15077           case MachinePlaysBlack:
15078             if (blackFlag) {
15079                 if (whiteFlag)
15080                   GameEnds(GameIsDrawn, "Both players ran out of time",
15081                            GE_PLAYER);
15082                 else
15083                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15084             } else {
15085                 DisplayError(_("Your opponent is not out of time"), 0);
15086             }
15087             break;
15088         }
15089     }
15090 }
15091
15092 void
15093 ClockClick (int which)
15094 {       // [HGM] code moved to back-end from winboard.c
15095         if(which) { // black clock
15096           if (gameMode == EditPosition || gameMode == IcsExamining) {
15097             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15098             SetBlackToPlayEvent();
15099           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15100           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15101           } else if (shiftKey) {
15102             AdjustClock(which, -1);
15103           } else if (gameMode == IcsPlayingWhite ||
15104                      gameMode == MachinePlaysBlack) {
15105             CallFlagEvent();
15106           }
15107         } else { // white clock
15108           if (gameMode == EditPosition || gameMode == IcsExamining) {
15109             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15110             SetWhiteToPlayEvent();
15111           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15112           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15113           } else if (shiftKey) {
15114             AdjustClock(which, -1);
15115           } else if (gameMode == IcsPlayingBlack ||
15116                    gameMode == MachinePlaysWhite) {
15117             CallFlagEvent();
15118           }
15119         }
15120 }
15121
15122 void
15123 DrawEvent ()
15124 {
15125     /* Offer draw or accept pending draw offer from opponent */
15126
15127     if (appData.icsActive) {
15128         /* Note: tournament rules require draw offers to be
15129            made after you make your move but before you punch
15130            your clock.  Currently ICS doesn't let you do that;
15131            instead, you immediately punch your clock after making
15132            a move, but you can offer a draw at any time. */
15133
15134         SendToICS(ics_prefix);
15135         SendToICS("draw\n");
15136         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15137     } else if (cmailMsgLoaded) {
15138         if (currentMove == cmailOldMove &&
15139             commentList[cmailOldMove] != NULL &&
15140             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15141                    "Black offers a draw" : "White offers a draw")) {
15142             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15143             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15144         } else if (currentMove == cmailOldMove + 1) {
15145             char *offer = WhiteOnMove(cmailOldMove) ?
15146               "White offers a draw" : "Black offers a draw";
15147             AppendComment(currentMove, offer, TRUE);
15148             DisplayComment(currentMove - 1, offer);
15149             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15150         } else {
15151             DisplayError(_("You must make your move before offering a draw"), 0);
15152             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15153         }
15154     } else if (first.offeredDraw) {
15155         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15156     } else {
15157         if (first.sendDrawOffers) {
15158             SendToProgram("draw\n", &first);
15159             userOfferedDraw = TRUE;
15160         }
15161     }
15162 }
15163
15164 void
15165 AdjournEvent ()
15166 {
15167     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15168
15169     if (appData.icsActive) {
15170         SendToICS(ics_prefix);
15171         SendToICS("adjourn\n");
15172     } else {
15173         /* Currently GNU Chess doesn't offer or accept Adjourns */
15174     }
15175 }
15176
15177
15178 void
15179 AbortEvent ()
15180 {
15181     /* Offer Abort or accept pending Abort offer from opponent */
15182
15183     if (appData.icsActive) {
15184         SendToICS(ics_prefix);
15185         SendToICS("abort\n");
15186     } else {
15187         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15188     }
15189 }
15190
15191 void
15192 ResignEvent ()
15193 {
15194     /* Resign.  You can do this even if it's not your turn. */
15195
15196     if (appData.icsActive) {
15197         SendToICS(ics_prefix);
15198         SendToICS("resign\n");
15199     } else {
15200         switch (gameMode) {
15201           case MachinePlaysWhite:
15202             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15203             break;
15204           case MachinePlaysBlack:
15205             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15206             break;
15207           case EditGame:
15208             if (cmailMsgLoaded) {
15209                 TruncateGame();
15210                 if (WhiteOnMove(cmailOldMove)) {
15211                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15212                 } else {
15213                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15214                 }
15215                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15216             }
15217             break;
15218           default:
15219             break;
15220         }
15221     }
15222 }
15223
15224
15225 void
15226 StopObservingEvent ()
15227 {
15228     /* Stop observing current games */
15229     SendToICS(ics_prefix);
15230     SendToICS("unobserve\n");
15231 }
15232
15233 void
15234 StopExaminingEvent ()
15235 {
15236     /* Stop observing current game */
15237     SendToICS(ics_prefix);
15238     SendToICS("unexamine\n");
15239 }
15240
15241 void
15242 ForwardInner (int target)
15243 {
15244     int limit; int oldSeekGraphUp = seekGraphUp;
15245
15246     if (appData.debugMode)
15247         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15248                 target, currentMove, forwardMostMove);
15249
15250     if (gameMode == EditPosition)
15251       return;
15252
15253     seekGraphUp = FALSE;
15254     MarkTargetSquares(1);
15255
15256     if (gameMode == PlayFromGameFile && !pausing)
15257       PauseEvent();
15258
15259     if (gameMode == IcsExamining && pausing)
15260       limit = pauseExamForwardMostMove;
15261     else
15262       limit = forwardMostMove;
15263
15264     if (target > limit) target = limit;
15265
15266     if (target > 0 && moveList[target - 1][0]) {
15267         int fromX, fromY, toX, toY;
15268         toX = moveList[target - 1][2] - AAA;
15269         toY = moveList[target - 1][3] - ONE;
15270         if (moveList[target - 1][1] == '@') {
15271             if (appData.highlightLastMove) {
15272                 SetHighlights(-1, -1, toX, toY);
15273             }
15274         } else {
15275             fromX = moveList[target - 1][0] - AAA;
15276             fromY = moveList[target - 1][1] - ONE;
15277             if (target == currentMove + 1) {
15278                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15279             }
15280             if (appData.highlightLastMove) {
15281                 SetHighlights(fromX, fromY, toX, toY);
15282             }
15283         }
15284     }
15285     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15286         gameMode == Training || gameMode == PlayFromGameFile ||
15287         gameMode == AnalyzeFile) {
15288         while (currentMove < target) {
15289             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15290             SendMoveToProgram(currentMove++, &first);
15291         }
15292     } else {
15293         currentMove = target;
15294     }
15295
15296     if (gameMode == EditGame || gameMode == EndOfGame) {
15297         whiteTimeRemaining = timeRemaining[0][currentMove];
15298         blackTimeRemaining = timeRemaining[1][currentMove];
15299     }
15300     DisplayBothClocks();
15301     DisplayMove(currentMove - 1);
15302     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15303     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15304     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15305         DisplayComment(currentMove - 1, commentList[currentMove]);
15306     }
15307     ClearMap(); // [HGM] exclude: invalidate map
15308 }
15309
15310
15311 void
15312 ForwardEvent ()
15313 {
15314     if (gameMode == IcsExamining && !pausing) {
15315         SendToICS(ics_prefix);
15316         SendToICS("forward\n");
15317     } else {
15318         ForwardInner(currentMove + 1);
15319     }
15320 }
15321
15322 void
15323 ToEndEvent ()
15324 {
15325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15326         /* to optimze, we temporarily turn off analysis mode while we feed
15327          * the remaining moves to the engine. Otherwise we get analysis output
15328          * after each move.
15329          */
15330         if (first.analysisSupport) {
15331           SendToProgram("exit\nforce\n", &first);
15332           first.analyzing = FALSE;
15333         }
15334     }
15335
15336     if (gameMode == IcsExamining && !pausing) {
15337         SendToICS(ics_prefix);
15338         SendToICS("forward 999999\n");
15339     } else {
15340         ForwardInner(forwardMostMove);
15341     }
15342
15343     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15344         /* we have fed all the moves, so reactivate analysis mode */
15345         SendToProgram("analyze\n", &first);
15346         first.analyzing = TRUE;
15347         /*first.maybeThinking = TRUE;*/
15348         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15349     }
15350 }
15351
15352 void
15353 BackwardInner (int target)
15354 {
15355     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15356
15357     if (appData.debugMode)
15358         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15359                 target, currentMove, forwardMostMove);
15360
15361     if (gameMode == EditPosition) return;
15362     seekGraphUp = FALSE;
15363     MarkTargetSquares(1);
15364     if (currentMove <= backwardMostMove) {
15365         ClearHighlights();
15366         DrawPosition(full_redraw, boards[currentMove]);
15367         return;
15368     }
15369     if (gameMode == PlayFromGameFile && !pausing)
15370       PauseEvent();
15371
15372     if (moveList[target][0]) {
15373         int fromX, fromY, toX, toY;
15374         toX = moveList[target][2] - AAA;
15375         toY = moveList[target][3] - ONE;
15376         if (moveList[target][1] == '@') {
15377             if (appData.highlightLastMove) {
15378                 SetHighlights(-1, -1, toX, toY);
15379             }
15380         } else {
15381             fromX = moveList[target][0] - AAA;
15382             fromY = moveList[target][1] - ONE;
15383             if (target == currentMove - 1) {
15384                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15385             }
15386             if (appData.highlightLastMove) {
15387                 SetHighlights(fromX, fromY, toX, toY);
15388             }
15389         }
15390     }
15391     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15392         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15393         while (currentMove > target) {
15394             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15395                 // null move cannot be undone. Reload program with move history before it.
15396                 int i;
15397                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15398                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15399                 }
15400                 SendBoard(&first, i);
15401               if(second.analyzing) SendBoard(&second, i);
15402                 for(currentMove=i; currentMove<target; currentMove++) {
15403                     SendMoveToProgram(currentMove, &first);
15404                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15405                 }
15406                 break;
15407             }
15408             SendToBoth("undo\n");
15409             currentMove--;
15410         }
15411     } else {
15412         currentMove = target;
15413     }
15414
15415     if (gameMode == EditGame || gameMode == EndOfGame) {
15416         whiteTimeRemaining = timeRemaining[0][currentMove];
15417         blackTimeRemaining = timeRemaining[1][currentMove];
15418     }
15419     DisplayBothClocks();
15420     DisplayMove(currentMove - 1);
15421     DrawPosition(full_redraw, boards[currentMove]);
15422     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15423     // [HGM] PV info: routine tests if comment empty
15424     DisplayComment(currentMove - 1, commentList[currentMove]);
15425     ClearMap(); // [HGM] exclude: invalidate map
15426 }
15427
15428 void
15429 BackwardEvent ()
15430 {
15431     if (gameMode == IcsExamining && !pausing) {
15432         SendToICS(ics_prefix);
15433         SendToICS("backward\n");
15434     } else {
15435         BackwardInner(currentMove - 1);
15436     }
15437 }
15438
15439 void
15440 ToStartEvent ()
15441 {
15442     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15443         /* to optimize, we temporarily turn off analysis mode while we undo
15444          * all the moves. Otherwise we get analysis output after each undo.
15445          */
15446         if (first.analysisSupport) {
15447           SendToProgram("exit\nforce\n", &first);
15448           first.analyzing = FALSE;
15449         }
15450     }
15451
15452     if (gameMode == IcsExamining && !pausing) {
15453         SendToICS(ics_prefix);
15454         SendToICS("backward 999999\n");
15455     } else {
15456         BackwardInner(backwardMostMove);
15457     }
15458
15459     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15460         /* we have fed all the moves, so reactivate analysis mode */
15461         SendToProgram("analyze\n", &first);
15462         first.analyzing = TRUE;
15463         /*first.maybeThinking = TRUE;*/
15464         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15465     }
15466 }
15467
15468 void
15469 ToNrEvent (int to)
15470 {
15471   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15472   if (to >= forwardMostMove) to = forwardMostMove;
15473   if (to <= backwardMostMove) to = backwardMostMove;
15474   if (to < currentMove) {
15475     BackwardInner(to);
15476   } else {
15477     ForwardInner(to);
15478   }
15479 }
15480
15481 void
15482 RevertEvent (Boolean annotate)
15483 {
15484     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15485         return;
15486     }
15487     if (gameMode != IcsExamining) {
15488         DisplayError(_("You are not examining a game"), 0);
15489         return;
15490     }
15491     if (pausing) {
15492         DisplayError(_("You can't revert while pausing"), 0);
15493         return;
15494     }
15495     SendToICS(ics_prefix);
15496     SendToICS("revert\n");
15497 }
15498
15499 void
15500 RetractMoveEvent ()
15501 {
15502     switch (gameMode) {
15503       case MachinePlaysWhite:
15504       case MachinePlaysBlack:
15505         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15506             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15507             return;
15508         }
15509         if (forwardMostMove < 2) return;
15510         currentMove = forwardMostMove = forwardMostMove - 2;
15511         whiteTimeRemaining = timeRemaining[0][currentMove];
15512         blackTimeRemaining = timeRemaining[1][currentMove];
15513         DisplayBothClocks();
15514         DisplayMove(currentMove - 1);
15515         ClearHighlights();/*!! could figure this out*/
15516         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15517         SendToProgram("remove\n", &first);
15518         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15519         break;
15520
15521       case BeginningOfGame:
15522       default:
15523         break;
15524
15525       case IcsPlayingWhite:
15526       case IcsPlayingBlack:
15527         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15528             SendToICS(ics_prefix);
15529             SendToICS("takeback 2\n");
15530         } else {
15531             SendToICS(ics_prefix);
15532             SendToICS("takeback 1\n");
15533         }
15534         break;
15535     }
15536 }
15537
15538 void
15539 MoveNowEvent ()
15540 {
15541     ChessProgramState *cps;
15542
15543     switch (gameMode) {
15544       case MachinePlaysWhite:
15545         if (!WhiteOnMove(forwardMostMove)) {
15546             DisplayError(_("It is your turn"), 0);
15547             return;
15548         }
15549         cps = &first;
15550         break;
15551       case MachinePlaysBlack:
15552         if (WhiteOnMove(forwardMostMove)) {
15553             DisplayError(_("It is your turn"), 0);
15554             return;
15555         }
15556         cps = &first;
15557         break;
15558       case TwoMachinesPlay:
15559         if (WhiteOnMove(forwardMostMove) ==
15560             (first.twoMachinesColor[0] == 'w')) {
15561             cps = &first;
15562         } else {
15563             cps = &second;
15564         }
15565         break;
15566       case BeginningOfGame:
15567       default:
15568         return;
15569     }
15570     SendToProgram("?\n", cps);
15571 }
15572
15573 void
15574 TruncateGameEvent ()
15575 {
15576     EditGameEvent();
15577     if (gameMode != EditGame) return;
15578     TruncateGame();
15579 }
15580
15581 void
15582 TruncateGame ()
15583 {
15584     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15585     if (forwardMostMove > currentMove) {
15586         if (gameInfo.resultDetails != NULL) {
15587             free(gameInfo.resultDetails);
15588             gameInfo.resultDetails = NULL;
15589             gameInfo.result = GameUnfinished;
15590         }
15591         forwardMostMove = currentMove;
15592         HistorySet(parseList, backwardMostMove, forwardMostMove,
15593                    currentMove-1);
15594     }
15595 }
15596
15597 void
15598 HintEvent ()
15599 {
15600     if (appData.noChessProgram) return;
15601     switch (gameMode) {
15602       case MachinePlaysWhite:
15603         if (WhiteOnMove(forwardMostMove)) {
15604             DisplayError(_("Wait until your turn."), 0);
15605             return;
15606         }
15607         break;
15608       case BeginningOfGame:
15609       case MachinePlaysBlack:
15610         if (!WhiteOnMove(forwardMostMove)) {
15611             DisplayError(_("Wait until your turn."), 0);
15612             return;
15613         }
15614         break;
15615       default:
15616         DisplayError(_("No hint available"), 0);
15617         return;
15618     }
15619     SendToProgram("hint\n", &first);
15620     hintRequested = TRUE;
15621 }
15622
15623 void
15624 CreateBookEvent ()
15625 {
15626     ListGame * lg = (ListGame *) gameList.head;
15627     FILE *f, *g;
15628     int nItem;
15629     static int secondTime = FALSE;
15630
15631     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15632         DisplayError(_("Game list not loaded or empty"), 0);
15633         return;
15634     }
15635
15636     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15637         fclose(g);
15638         secondTime++;
15639         DisplayNote(_("Book file exists! Try again for overwrite."));
15640         return;
15641     }
15642
15643     creatingBook = TRUE;
15644     secondTime = FALSE;
15645
15646     /* Get list size */
15647     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15648         LoadGame(f, nItem, "", TRUE);
15649         AddGameToBook(TRUE);
15650         lg = (ListGame *) lg->node.succ;
15651     }
15652
15653     creatingBook = FALSE;
15654     FlushBook();
15655 }
15656
15657 void
15658 BookEvent ()
15659 {
15660     if (appData.noChessProgram) return;
15661     switch (gameMode) {
15662       case MachinePlaysWhite:
15663         if (WhiteOnMove(forwardMostMove)) {
15664             DisplayError(_("Wait until your turn."), 0);
15665             return;
15666         }
15667         break;
15668       case BeginningOfGame:
15669       case MachinePlaysBlack:
15670         if (!WhiteOnMove(forwardMostMove)) {
15671             DisplayError(_("Wait until your turn."), 0);
15672             return;
15673         }
15674         break;
15675       case EditPosition:
15676         EditPositionDone(TRUE);
15677         break;
15678       case TwoMachinesPlay:
15679         return;
15680       default:
15681         break;
15682     }
15683     SendToProgram("bk\n", &first);
15684     bookOutput[0] = NULLCHAR;
15685     bookRequested = TRUE;
15686 }
15687
15688 void
15689 AboutGameEvent ()
15690 {
15691     char *tags = PGNTags(&gameInfo);
15692     TagsPopUp(tags, CmailMsg());
15693     free(tags);
15694 }
15695
15696 /* end button procedures */
15697
15698 void
15699 PrintPosition (FILE *fp, int move)
15700 {
15701     int i, j;
15702
15703     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15704         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15705             char c = PieceToChar(boards[move][i][j]);
15706             fputc(c == 'x' ? '.' : c, fp);
15707             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15708         }
15709     }
15710     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15711       fprintf(fp, "white to play\n");
15712     else
15713       fprintf(fp, "black to play\n");
15714 }
15715
15716 void
15717 PrintOpponents (FILE *fp)
15718 {
15719     if (gameInfo.white != NULL) {
15720         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15721     } else {
15722         fprintf(fp, "\n");
15723     }
15724 }
15725
15726 /* Find last component of program's own name, using some heuristics */
15727 void
15728 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15729 {
15730     char *p, *q, c;
15731     int local = (strcmp(host, "localhost") == 0);
15732     while (!local && (p = strchr(prog, ';')) != NULL) {
15733         p++;
15734         while (*p == ' ') p++;
15735         prog = p;
15736     }
15737     if (*prog == '"' || *prog == '\'') {
15738         q = strchr(prog + 1, *prog);
15739     } else {
15740         q = strchr(prog, ' ');
15741     }
15742     if (q == NULL) q = prog + strlen(prog);
15743     p = q;
15744     while (p >= prog && *p != '/' && *p != '\\') p--;
15745     p++;
15746     if(p == prog && *p == '"') p++;
15747     c = *q; *q = 0;
15748     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15749     memcpy(buf, p, q - p);
15750     buf[q - p] = NULLCHAR;
15751     if (!local) {
15752         strcat(buf, "@");
15753         strcat(buf, host);
15754     }
15755 }
15756
15757 char *
15758 TimeControlTagValue ()
15759 {
15760     char buf[MSG_SIZ];
15761     if (!appData.clockMode) {
15762       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15763     } else if (movesPerSession > 0) {
15764       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15765     } else if (timeIncrement == 0) {
15766       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15767     } else {
15768       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15769     }
15770     return StrSave(buf);
15771 }
15772
15773 void
15774 SetGameInfo ()
15775 {
15776     /* This routine is used only for certain modes */
15777     VariantClass v = gameInfo.variant;
15778     ChessMove r = GameUnfinished;
15779     char *p = NULL;
15780
15781     if(keepInfo) return;
15782
15783     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15784         r = gameInfo.result;
15785         p = gameInfo.resultDetails;
15786         gameInfo.resultDetails = NULL;
15787     }
15788     ClearGameInfo(&gameInfo);
15789     gameInfo.variant = v;
15790
15791     switch (gameMode) {
15792       case MachinePlaysWhite:
15793         gameInfo.event = StrSave( appData.pgnEventHeader );
15794         gameInfo.site = StrSave(HostName());
15795         gameInfo.date = PGNDate();
15796         gameInfo.round = StrSave("-");
15797         gameInfo.white = StrSave(first.tidy);
15798         gameInfo.black = StrSave(UserName());
15799         gameInfo.timeControl = TimeControlTagValue();
15800         break;
15801
15802       case MachinePlaysBlack:
15803         gameInfo.event = StrSave( appData.pgnEventHeader );
15804         gameInfo.site = StrSave(HostName());
15805         gameInfo.date = PGNDate();
15806         gameInfo.round = StrSave("-");
15807         gameInfo.white = StrSave(UserName());
15808         gameInfo.black = StrSave(first.tidy);
15809         gameInfo.timeControl = TimeControlTagValue();
15810         break;
15811
15812       case TwoMachinesPlay:
15813         gameInfo.event = StrSave( appData.pgnEventHeader );
15814         gameInfo.site = StrSave(HostName());
15815         gameInfo.date = PGNDate();
15816         if (roundNr > 0) {
15817             char buf[MSG_SIZ];
15818             snprintf(buf, MSG_SIZ, "%d", roundNr);
15819             gameInfo.round = StrSave(buf);
15820         } else {
15821             gameInfo.round = StrSave("-");
15822         }
15823         if (first.twoMachinesColor[0] == 'w') {
15824             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15825             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15826         } else {
15827             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15828             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15829         }
15830         gameInfo.timeControl = TimeControlTagValue();
15831         break;
15832
15833       case EditGame:
15834         gameInfo.event = StrSave("Edited game");
15835         gameInfo.site = StrSave(HostName());
15836         gameInfo.date = PGNDate();
15837         gameInfo.round = StrSave("-");
15838         gameInfo.white = StrSave("-");
15839         gameInfo.black = StrSave("-");
15840         gameInfo.result = r;
15841         gameInfo.resultDetails = p;
15842         break;
15843
15844       case EditPosition:
15845         gameInfo.event = StrSave("Edited position");
15846         gameInfo.site = StrSave(HostName());
15847         gameInfo.date = PGNDate();
15848         gameInfo.round = StrSave("-");
15849         gameInfo.white = StrSave("-");
15850         gameInfo.black = StrSave("-");
15851         break;
15852
15853       case IcsPlayingWhite:
15854       case IcsPlayingBlack:
15855       case IcsObserving:
15856       case IcsExamining:
15857         break;
15858
15859       case PlayFromGameFile:
15860         gameInfo.event = StrSave("Game from non-PGN file");
15861         gameInfo.site = StrSave(HostName());
15862         gameInfo.date = PGNDate();
15863         gameInfo.round = StrSave("-");
15864         gameInfo.white = StrSave("?");
15865         gameInfo.black = StrSave("?");
15866         break;
15867
15868       default:
15869         break;
15870     }
15871 }
15872
15873 void
15874 ReplaceComment (int index, char *text)
15875 {
15876     int len;
15877     char *p;
15878     float score;
15879
15880     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15881        pvInfoList[index-1].depth == len &&
15882        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15883        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15884     while (*text == '\n') text++;
15885     len = strlen(text);
15886     while (len > 0 && text[len - 1] == '\n') len--;
15887
15888     if (commentList[index] != NULL)
15889       free(commentList[index]);
15890
15891     if (len == 0) {
15892         commentList[index] = NULL;
15893         return;
15894     }
15895   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15896       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15897       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15898     commentList[index] = (char *) malloc(len + 2);
15899     strncpy(commentList[index], text, len);
15900     commentList[index][len] = '\n';
15901     commentList[index][len + 1] = NULLCHAR;
15902   } else {
15903     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15904     char *p;
15905     commentList[index] = (char *) malloc(len + 7);
15906     safeStrCpy(commentList[index], "{\n", 3);
15907     safeStrCpy(commentList[index]+2, text, len+1);
15908     commentList[index][len+2] = NULLCHAR;
15909     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15910     strcat(commentList[index], "\n}\n");
15911   }
15912 }
15913
15914 void
15915 CrushCRs (char *text)
15916 {
15917   char *p = text;
15918   char *q = text;
15919   char ch;
15920
15921   do {
15922     ch = *p++;
15923     if (ch == '\r') continue;
15924     *q++ = ch;
15925   } while (ch != '\0');
15926 }
15927
15928 void
15929 AppendComment (int index, char *text, Boolean addBraces)
15930 /* addBraces  tells if we should add {} */
15931 {
15932     int oldlen, len;
15933     char *old;
15934
15935 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15936     if(addBraces == 3) addBraces = 0; else // force appending literally
15937     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15938
15939     CrushCRs(text);
15940     while (*text == '\n') text++;
15941     len = strlen(text);
15942     while (len > 0 && text[len - 1] == '\n') len--;
15943     text[len] = NULLCHAR;
15944
15945     if (len == 0) return;
15946
15947     if (commentList[index] != NULL) {
15948       Boolean addClosingBrace = addBraces;
15949         old = commentList[index];
15950         oldlen = strlen(old);
15951         while(commentList[index][oldlen-1] ==  '\n')
15952           commentList[index][--oldlen] = NULLCHAR;
15953         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15954         safeStrCpy(commentList[index], old, oldlen + len + 6);
15955         free(old);
15956         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15957         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15958           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15959           while (*text == '\n') { text++; len--; }
15960           commentList[index][--oldlen] = NULLCHAR;
15961       }
15962         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15963         else          strcat(commentList[index], "\n");
15964         strcat(commentList[index], text);
15965         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15966         else          strcat(commentList[index], "\n");
15967     } else {
15968         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15969         if(addBraces)
15970           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15971         else commentList[index][0] = NULLCHAR;
15972         strcat(commentList[index], text);
15973         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15974         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15975     }
15976 }
15977
15978 static char *
15979 FindStr (char * text, char * sub_text)
15980 {
15981     char * result = strstr( text, sub_text );
15982
15983     if( result != NULL ) {
15984         result += strlen( sub_text );
15985     }
15986
15987     return result;
15988 }
15989
15990 /* [AS] Try to extract PV info from PGN comment */
15991 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15992 char *
15993 GetInfoFromComment (int index, char * text)
15994 {
15995     char * sep = text, *p;
15996
15997     if( text != NULL && index > 0 ) {
15998         int score = 0;
15999         int depth = 0;
16000         int time = -1, sec = 0, deci;
16001         char * s_eval = FindStr( text, "[%eval " );
16002         char * s_emt = FindStr( text, "[%emt " );
16003 #if 0
16004         if( s_eval != NULL || s_emt != NULL ) {
16005 #else
16006         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16007 #endif
16008             /* New style */
16009             char delim;
16010
16011             if( s_eval != NULL ) {
16012                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16013                     return text;
16014                 }
16015
16016                 if( delim != ']' ) {
16017                     return text;
16018                 }
16019             }
16020
16021             if( s_emt != NULL ) {
16022             }
16023                 return text;
16024         }
16025         else {
16026             /* We expect something like: [+|-]nnn.nn/dd */
16027             int score_lo = 0;
16028
16029             if(*text != '{') return text; // [HGM] braces: must be normal comment
16030
16031             sep = strchr( text, '/' );
16032             if( sep == NULL || sep < (text+4) ) {
16033                 return text;
16034             }
16035
16036             p = text;
16037             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16038             if(p[1] == '(') { // comment starts with PV
16039                p = strchr(p, ')'); // locate end of PV
16040                if(p == NULL || sep < p+5) return text;
16041                // at this point we have something like "{(.*) +0.23/6 ..."
16042                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16043                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16044                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16045             }
16046             time = -1; sec = -1; deci = -1;
16047             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16048                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16049                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16050                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16051                 return text;
16052             }
16053
16054             if( score_lo < 0 || score_lo >= 100 ) {
16055                 return text;
16056             }
16057
16058             if(sec >= 0) time = 600*time + 10*sec; else
16059             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16060
16061             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16062
16063             /* [HGM] PV time: now locate end of PV info */
16064             while( *++sep >= '0' && *sep <= '9'); // strip depth
16065             if(time >= 0)
16066             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16067             if(sec >= 0)
16068             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16069             if(deci >= 0)
16070             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16071             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16072         }
16073
16074         if( depth <= 0 ) {
16075             return text;
16076         }
16077
16078         if( time < 0 ) {
16079             time = -1;
16080         }
16081
16082         pvInfoList[index-1].depth = depth;
16083         pvInfoList[index-1].score = score;
16084         pvInfoList[index-1].time  = 10*time; // centi-sec
16085         if(*sep == '}') *sep = 0; else *--sep = '{';
16086         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16087     }
16088     return sep;
16089 }
16090
16091 void
16092 SendToProgram (char *message, ChessProgramState *cps)
16093 {
16094     int count, outCount, error;
16095     char buf[MSG_SIZ];
16096
16097     if (cps->pr == NoProc) return;
16098     Attention(cps);
16099
16100     if (appData.debugMode) {
16101         TimeMark now;
16102         GetTimeMark(&now);
16103         fprintf(debugFP, "%ld >%-6s: %s",
16104                 SubtractTimeMarks(&now, &programStartTime),
16105                 cps->which, message);
16106         if(serverFP)
16107             fprintf(serverFP, "%ld >%-6s: %s",
16108                 SubtractTimeMarks(&now, &programStartTime),
16109                 cps->which, message), fflush(serverFP);
16110     }
16111
16112     count = strlen(message);
16113     outCount = OutputToProcess(cps->pr, message, count, &error);
16114     if (outCount < count && !exiting
16115                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16116       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16117       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16118         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16119             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16120                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16121                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16122                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16123             } else {
16124                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16125                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16126                 gameInfo.result = res;
16127             }
16128             gameInfo.resultDetails = StrSave(buf);
16129         }
16130         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16131         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16132     }
16133 }
16134
16135 void
16136 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16137 {
16138     char *end_str;
16139     char buf[MSG_SIZ];
16140     ChessProgramState *cps = (ChessProgramState *)closure;
16141
16142     if (isr != cps->isr) return; /* Killed intentionally */
16143     if (count <= 0) {
16144         if (count == 0) {
16145             RemoveInputSource(cps->isr);
16146             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16147                     _(cps->which), cps->program);
16148             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16149             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16150                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16151                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16152                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16153                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16154                 } else {
16155                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16156                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16157                     gameInfo.result = res;
16158                 }
16159                 gameInfo.resultDetails = StrSave(buf);
16160             }
16161             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16162             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16163         } else {
16164             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16165                     _(cps->which), cps->program);
16166             RemoveInputSource(cps->isr);
16167
16168             /* [AS] Program is misbehaving badly... kill it */
16169             if( count == -2 ) {
16170                 DestroyChildProcess( cps->pr, 9 );
16171                 cps->pr = NoProc;
16172             }
16173
16174             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16175         }
16176         return;
16177     }
16178
16179     if ((end_str = strchr(message, '\r')) != NULL)
16180       *end_str = NULLCHAR;
16181     if ((end_str = strchr(message, '\n')) != NULL)
16182       *end_str = NULLCHAR;
16183
16184     if (appData.debugMode) {
16185         TimeMark now; int print = 1;
16186         char *quote = ""; char c; int i;
16187
16188         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16189                 char start = message[0];
16190                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16191                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16192                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16193                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16194                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16195                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16196                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16197                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16198                    sscanf(message, "hint: %c", &c)!=1 &&
16199                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16200                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16201                     print = (appData.engineComments >= 2);
16202                 }
16203                 message[0] = start; // restore original message
16204         }
16205         if(print) {
16206                 GetTimeMark(&now);
16207                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16208                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16209                         quote,
16210                         message);
16211                 if(serverFP)
16212                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16213                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16214                         quote,
16215                         message), fflush(serverFP);
16216         }
16217     }
16218
16219     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16220     if (appData.icsEngineAnalyze) {
16221         if (strstr(message, "whisper") != NULL ||
16222              strstr(message, "kibitz") != NULL ||
16223             strstr(message, "tellics") != NULL) return;
16224     }
16225
16226     HandleMachineMove(message, cps);
16227 }
16228
16229
16230 void
16231 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16232 {
16233     char buf[MSG_SIZ];
16234     int seconds;
16235
16236     if( timeControl_2 > 0 ) {
16237         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16238             tc = timeControl_2;
16239         }
16240     }
16241     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16242     inc /= cps->timeOdds;
16243     st  /= cps->timeOdds;
16244
16245     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16246
16247     if (st > 0) {
16248       /* Set exact time per move, normally using st command */
16249       if (cps->stKludge) {
16250         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16251         seconds = st % 60;
16252         if (seconds == 0) {
16253           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16254         } else {
16255           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16256         }
16257       } else {
16258         snprintf(buf, MSG_SIZ, "st %d\n", st);
16259       }
16260     } else {
16261       /* Set conventional or incremental time control, using level command */
16262       if (seconds == 0) {
16263         /* Note old gnuchess bug -- minutes:seconds used to not work.
16264            Fixed in later versions, but still avoid :seconds
16265            when seconds is 0. */
16266         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16267       } else {
16268         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16269                  seconds, inc/1000.);
16270       }
16271     }
16272     SendToProgram(buf, cps);
16273
16274     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16275     /* Orthogonally, limit search to given depth */
16276     if (sd > 0) {
16277       if (cps->sdKludge) {
16278         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16279       } else {
16280         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16281       }
16282       SendToProgram(buf, cps);
16283     }
16284
16285     if(cps->nps >= 0) { /* [HGM] nps */
16286         if(cps->supportsNPS == FALSE)
16287           cps->nps = -1; // don't use if engine explicitly says not supported!
16288         else {
16289           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16290           SendToProgram(buf, cps);
16291         }
16292     }
16293 }
16294
16295 ChessProgramState *
16296 WhitePlayer ()
16297 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16298 {
16299     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16300        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16301         return &second;
16302     return &first;
16303 }
16304
16305 void
16306 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16307 {
16308     char message[MSG_SIZ];
16309     long time, otime;
16310
16311     /* Note: this routine must be called when the clocks are stopped
16312        or when they have *just* been set or switched; otherwise
16313        it will be off by the time since the current tick started.
16314     */
16315     if (machineWhite) {
16316         time = whiteTimeRemaining / 10;
16317         otime = blackTimeRemaining / 10;
16318     } else {
16319         time = blackTimeRemaining / 10;
16320         otime = whiteTimeRemaining / 10;
16321     }
16322     /* [HGM] translate opponent's time by time-odds factor */
16323     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16324
16325     if (time <= 0) time = 1;
16326     if (otime <= 0) otime = 1;
16327
16328     snprintf(message, MSG_SIZ, "time %ld\n", time);
16329     SendToProgram(message, cps);
16330
16331     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16332     SendToProgram(message, cps);
16333 }
16334
16335 char *
16336 EngineDefinedVariant (ChessProgramState *cps, int n)
16337 {   // return name of n-th unknown variant that engine supports
16338     static char buf[MSG_SIZ];
16339     char *p, *s = cps->variants;
16340     if(!s) return NULL;
16341     do { // parse string from variants feature
16342       VariantClass v;
16343         p = strchr(s, ',');
16344         if(p) *p = NULLCHAR;
16345       v = StringToVariant(s);
16346       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16347         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16348             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16349         }
16350         if(p) *p++ = ',';
16351         if(n < 0) return buf;
16352     } while(s = p);
16353     return NULL;
16354 }
16355
16356 int
16357 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16358 {
16359   char buf[MSG_SIZ];
16360   int len = strlen(name);
16361   int val;
16362
16363   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16364     (*p) += len + 1;
16365     sscanf(*p, "%d", &val);
16366     *loc = (val != 0);
16367     while (**p && **p != ' ')
16368       (*p)++;
16369     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16370     SendToProgram(buf, cps);
16371     return TRUE;
16372   }
16373   return FALSE;
16374 }
16375
16376 int
16377 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16378 {
16379   char buf[MSG_SIZ];
16380   int len = strlen(name);
16381   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16382     (*p) += len + 1;
16383     sscanf(*p, "%d", loc);
16384     while (**p && **p != ' ') (*p)++;
16385     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16386     SendToProgram(buf, cps);
16387     return TRUE;
16388   }
16389   return FALSE;
16390 }
16391
16392 int
16393 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16394 {
16395   char buf[MSG_SIZ];
16396   int len = strlen(name);
16397   if (strncmp((*p), name, len) == 0
16398       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16399     (*p) += len + 2;
16400     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16401     sscanf(*p, "%[^\"]", *loc);
16402     while (**p && **p != '\"') (*p)++;
16403     if (**p == '\"') (*p)++;
16404     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16405     SendToProgram(buf, cps);
16406     return TRUE;
16407   }
16408   return FALSE;
16409 }
16410
16411 int
16412 ParseOption (Option *opt, ChessProgramState *cps)
16413 // [HGM] options: process the string that defines an engine option, and determine
16414 // name, type, default value, and allowed value range
16415 {
16416         char *p, *q, buf[MSG_SIZ];
16417         int n, min = (-1)<<31, max = 1<<31, def;
16418
16419         if(p = strstr(opt->name, " -spin ")) {
16420             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16421             if(max < min) max = min; // enforce consistency
16422             if(def < min) def = min;
16423             if(def > max) def = max;
16424             opt->value = def;
16425             opt->min = min;
16426             opt->max = max;
16427             opt->type = Spin;
16428         } else if((p = strstr(opt->name, " -slider "))) {
16429             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16430             if((n = sscanf(p, " -slider %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; // Slider;
16438         } else if((p = strstr(opt->name, " -string "))) {
16439             opt->textValue = p+9;
16440             opt->type = TextBox;
16441         } else if((p = strstr(opt->name, " -file "))) {
16442             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16443             opt->textValue = p+7;
16444             opt->type = FileName; // FileName;
16445         } else if((p = strstr(opt->name, " -path "))) {
16446             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16447             opt->textValue = p+7;
16448             opt->type = PathName; // PathName;
16449         } else if(p = strstr(opt->name, " -check ")) {
16450             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16451             opt->value = (def != 0);
16452             opt->type = CheckBox;
16453         } else if(p = strstr(opt->name, " -combo ")) {
16454             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16455             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16456             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16457             opt->value = n = 0;
16458             while(q = StrStr(q, " /// ")) {
16459                 n++; *q = 0;    // count choices, and null-terminate each of them
16460                 q += 5;
16461                 if(*q == '*') { // remember default, which is marked with * prefix
16462                     q++;
16463                     opt->value = n;
16464                 }
16465                 cps->comboList[cps->comboCnt++] = q;
16466             }
16467             cps->comboList[cps->comboCnt++] = NULL;
16468             opt->max = n + 1;
16469             opt->type = ComboBox;
16470         } else if(p = strstr(opt->name, " -button")) {
16471             opt->type = Button;
16472         } else if(p = strstr(opt->name, " -save")) {
16473             opt->type = SaveButton;
16474         } else return FALSE;
16475         *p = 0; // terminate option name
16476         // now look if the command-line options define a setting for this engine option.
16477         if(cps->optionSettings && cps->optionSettings[0])
16478             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16479         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16480           snprintf(buf, MSG_SIZ, "option %s", p);
16481                 if(p = strstr(buf, ",")) *p = 0;
16482                 if(q = strchr(buf, '=')) switch(opt->type) {
16483                     case ComboBox:
16484                         for(n=0; n<opt->max; n++)
16485                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16486                         break;
16487                     case TextBox:
16488                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16489                         break;
16490                     case Spin:
16491                     case CheckBox:
16492                         opt->value = atoi(q+1);
16493                     default:
16494                         break;
16495                 }
16496                 strcat(buf, "\n");
16497                 SendToProgram(buf, cps);
16498         }
16499         return TRUE;
16500 }
16501
16502 void
16503 FeatureDone (ChessProgramState *cps, int val)
16504 {
16505   DelayedEventCallback cb = GetDelayedEvent();
16506   if ((cb == InitBackEnd3 && cps == &first) ||
16507       (cb == SettingsMenuIfReady && cps == &second) ||
16508       (cb == LoadEngine) ||
16509       (cb == TwoMachinesEventIfReady)) {
16510     CancelDelayedEvent();
16511     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16512   }
16513   cps->initDone = val;
16514   if(val) cps->reload = FALSE;
16515 }
16516
16517 /* Parse feature command from engine */
16518 void
16519 ParseFeatures (char *args, ChessProgramState *cps)
16520 {
16521   char *p = args;
16522   char *q = NULL;
16523   int val;
16524   char buf[MSG_SIZ];
16525
16526   for (;;) {
16527     while (*p == ' ') p++;
16528     if (*p == NULLCHAR) return;
16529
16530     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16531     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16532     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16533     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16534     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16535     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16536     if (BoolFeature(&p, "reuse", &val, cps)) {
16537       /* Engine can disable reuse, but can't enable it if user said no */
16538       if (!val) cps->reuse = FALSE;
16539       continue;
16540     }
16541     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16542     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16543       if (gameMode == TwoMachinesPlay) {
16544         DisplayTwoMachinesTitle();
16545       } else {
16546         DisplayTitle("");
16547       }
16548       continue;
16549     }
16550     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16551     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16552     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16553     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16554     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16555     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16556     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16557     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16558     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16559     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16560     if (IntFeature(&p, "done", &val, cps)) {
16561       FeatureDone(cps, val);
16562       continue;
16563     }
16564     /* Added by Tord: */
16565     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16566     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16567     /* End of additions by Tord */
16568
16569     /* [HGM] added features: */
16570     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16571     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16572     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16573     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16574     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16575     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16576     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16577     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16578         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16579         FREE(cps->option[cps->nrOptions].name);
16580         cps->option[cps->nrOptions].name = q; q = NULL;
16581         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16582           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16583             SendToProgram(buf, cps);
16584             continue;
16585         }
16586         if(cps->nrOptions >= MAX_OPTIONS) {
16587             cps->nrOptions--;
16588             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16589             DisplayError(buf, 0);
16590         }
16591         continue;
16592     }
16593     /* End of additions by HGM */
16594
16595     /* unknown feature: complain and skip */
16596     q = p;
16597     while (*q && *q != '=') q++;
16598     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16599     SendToProgram(buf, cps);
16600     p = q;
16601     if (*p == '=') {
16602       p++;
16603       if (*p == '\"') {
16604         p++;
16605         while (*p && *p != '\"') p++;
16606         if (*p == '\"') p++;
16607       } else {
16608         while (*p && *p != ' ') p++;
16609       }
16610     }
16611   }
16612
16613 }
16614
16615 void
16616 PeriodicUpdatesEvent (int newState)
16617 {
16618     if (newState == appData.periodicUpdates)
16619       return;
16620
16621     appData.periodicUpdates=newState;
16622
16623     /* Display type changes, so update it now */
16624 //    DisplayAnalysis();
16625
16626     /* Get the ball rolling again... */
16627     if (newState) {
16628         AnalysisPeriodicEvent(1);
16629         StartAnalysisClock();
16630     }
16631 }
16632
16633 void
16634 PonderNextMoveEvent (int newState)
16635 {
16636     if (newState == appData.ponderNextMove) return;
16637     if (gameMode == EditPosition) EditPositionDone(TRUE);
16638     if (newState) {
16639         SendToProgram("hard\n", &first);
16640         if (gameMode == TwoMachinesPlay) {
16641             SendToProgram("hard\n", &second);
16642         }
16643     } else {
16644         SendToProgram("easy\n", &first);
16645         thinkOutput[0] = NULLCHAR;
16646         if (gameMode == TwoMachinesPlay) {
16647             SendToProgram("easy\n", &second);
16648         }
16649     }
16650     appData.ponderNextMove = newState;
16651 }
16652
16653 void
16654 NewSettingEvent (int option, int *feature, char *command, int value)
16655 {
16656     char buf[MSG_SIZ];
16657
16658     if (gameMode == EditPosition) EditPositionDone(TRUE);
16659     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16660     if(feature == NULL || *feature) SendToProgram(buf, &first);
16661     if (gameMode == TwoMachinesPlay) {
16662         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16663     }
16664 }
16665
16666 void
16667 ShowThinkingEvent ()
16668 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16669 {
16670     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16671     int newState = appData.showThinking
16672         // [HGM] thinking: other features now need thinking output as well
16673         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16674
16675     if (oldState == newState) return;
16676     oldState = newState;
16677     if (gameMode == EditPosition) EditPositionDone(TRUE);
16678     if (oldState) {
16679         SendToProgram("post\n", &first);
16680         if (gameMode == TwoMachinesPlay) {
16681             SendToProgram("post\n", &second);
16682         }
16683     } else {
16684         SendToProgram("nopost\n", &first);
16685         thinkOutput[0] = NULLCHAR;
16686         if (gameMode == TwoMachinesPlay) {
16687             SendToProgram("nopost\n", &second);
16688         }
16689     }
16690 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16691 }
16692
16693 void
16694 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16695 {
16696   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16697   if (pr == NoProc) return;
16698   AskQuestion(title, question, replyPrefix, pr);
16699 }
16700
16701 void
16702 TypeInEvent (char firstChar)
16703 {
16704     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16705         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16706         gameMode == AnalyzeMode || gameMode == EditGame ||
16707         gameMode == EditPosition || gameMode == IcsExamining ||
16708         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16709         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16710                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16711                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16712         gameMode == Training) PopUpMoveDialog(firstChar);
16713 }
16714
16715 void
16716 TypeInDoneEvent (char *move)
16717 {
16718         Board board;
16719         int n, fromX, fromY, toX, toY;
16720         char promoChar;
16721         ChessMove moveType;
16722
16723         // [HGM] FENedit
16724         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16725                 EditPositionPasteFEN(move);
16726                 return;
16727         }
16728         // [HGM] movenum: allow move number to be typed in any mode
16729         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16730           ToNrEvent(2*n-1);
16731           return;
16732         }
16733         // undocumented kludge: allow command-line option to be typed in!
16734         // (potentially fatal, and does not implement the effect of the option.)
16735         // should only be used for options that are values on which future decisions will be made,
16736         // and definitely not on options that would be used during initialization.
16737         if(strstr(move, "!!! -") == move) {
16738             ParseArgsFromString(move+4);
16739             return;
16740         }
16741
16742       if (gameMode != EditGame && currentMove != forwardMostMove &&
16743         gameMode != Training) {
16744         DisplayMoveError(_("Displayed move is not current"));
16745       } else {
16746         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16747           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16748         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16749         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16750           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16751           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16752         } else {
16753           DisplayMoveError(_("Could not parse move"));
16754         }
16755       }
16756 }
16757
16758 void
16759 DisplayMove (int moveNumber)
16760 {
16761     char message[MSG_SIZ];
16762     char res[MSG_SIZ];
16763     char cpThinkOutput[MSG_SIZ];
16764
16765     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16766
16767     if (moveNumber == forwardMostMove - 1 ||
16768         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16769
16770         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16771
16772         if (strchr(cpThinkOutput, '\n')) {
16773             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16774         }
16775     } else {
16776         *cpThinkOutput = NULLCHAR;
16777     }
16778
16779     /* [AS] Hide thinking from human user */
16780     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16781         *cpThinkOutput = NULLCHAR;
16782         if( thinkOutput[0] != NULLCHAR ) {
16783             int i;
16784
16785             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16786                 cpThinkOutput[i] = '.';
16787             }
16788             cpThinkOutput[i] = NULLCHAR;
16789             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16790         }
16791     }
16792
16793     if (moveNumber == forwardMostMove - 1 &&
16794         gameInfo.resultDetails != NULL) {
16795         if (gameInfo.resultDetails[0] == NULLCHAR) {
16796           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16797         } else {
16798           snprintf(res, MSG_SIZ, " {%s} %s",
16799                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16800         }
16801     } else {
16802         res[0] = NULLCHAR;
16803     }
16804
16805     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16806         DisplayMessage(res, cpThinkOutput);
16807     } else {
16808       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16809                 WhiteOnMove(moveNumber) ? " " : ".. ",
16810                 parseList[moveNumber], res);
16811         DisplayMessage(message, cpThinkOutput);
16812     }
16813 }
16814
16815 void
16816 DisplayComment (int moveNumber, char *text)
16817 {
16818     char title[MSG_SIZ];
16819
16820     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16821       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16822     } else {
16823       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16824               WhiteOnMove(moveNumber) ? " " : ".. ",
16825               parseList[moveNumber]);
16826     }
16827     if (text != NULL && (appData.autoDisplayComment || commentUp))
16828         CommentPopUp(title, text);
16829 }
16830
16831 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16832  * might be busy thinking or pondering.  It can be omitted if your
16833  * gnuchess is configured to stop thinking immediately on any user
16834  * input.  However, that gnuchess feature depends on the FIONREAD
16835  * ioctl, which does not work properly on some flavors of Unix.
16836  */
16837 void
16838 Attention (ChessProgramState *cps)
16839 {
16840 #if ATTENTION
16841     if (!cps->useSigint) return;
16842     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16843     switch (gameMode) {
16844       case MachinePlaysWhite:
16845       case MachinePlaysBlack:
16846       case TwoMachinesPlay:
16847       case IcsPlayingWhite:
16848       case IcsPlayingBlack:
16849       case AnalyzeMode:
16850       case AnalyzeFile:
16851         /* Skip if we know it isn't thinking */
16852         if (!cps->maybeThinking) return;
16853         if (appData.debugMode)
16854           fprintf(debugFP, "Interrupting %s\n", cps->which);
16855         InterruptChildProcess(cps->pr);
16856         cps->maybeThinking = FALSE;
16857         break;
16858       default:
16859         break;
16860     }
16861 #endif /*ATTENTION*/
16862 }
16863
16864 int
16865 CheckFlags ()
16866 {
16867     if (whiteTimeRemaining <= 0) {
16868         if (!whiteFlag) {
16869             whiteFlag = TRUE;
16870             if (appData.icsActive) {
16871                 if (appData.autoCallFlag &&
16872                     gameMode == IcsPlayingBlack && !blackFlag) {
16873                   SendToICS(ics_prefix);
16874                   SendToICS("flag\n");
16875                 }
16876             } else {
16877                 if (blackFlag) {
16878                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16879                 } else {
16880                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16881                     if (appData.autoCallFlag) {
16882                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16883                         return TRUE;
16884                     }
16885                 }
16886             }
16887         }
16888     }
16889     if (blackTimeRemaining <= 0) {
16890         if (!blackFlag) {
16891             blackFlag = TRUE;
16892             if (appData.icsActive) {
16893                 if (appData.autoCallFlag &&
16894                     gameMode == IcsPlayingWhite && !whiteFlag) {
16895                   SendToICS(ics_prefix);
16896                   SendToICS("flag\n");
16897                 }
16898             } else {
16899                 if (whiteFlag) {
16900                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16901                 } else {
16902                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16903                     if (appData.autoCallFlag) {
16904                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16905                         return TRUE;
16906                     }
16907                 }
16908             }
16909         }
16910     }
16911     return FALSE;
16912 }
16913
16914 void
16915 CheckTimeControl ()
16916 {
16917     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16918         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16919
16920     /*
16921      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16922      */
16923     if ( !WhiteOnMove(forwardMostMove) ) {
16924         /* White made time control */
16925         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16926         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16927         /* [HGM] time odds: correct new time quota for time odds! */
16928                                             / WhitePlayer()->timeOdds;
16929         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16930     } else {
16931         lastBlack -= blackTimeRemaining;
16932         /* Black made time control */
16933         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16934                                             / WhitePlayer()->other->timeOdds;
16935         lastWhite = whiteTimeRemaining;
16936     }
16937 }
16938
16939 void
16940 DisplayBothClocks ()
16941 {
16942     int wom = gameMode == EditPosition ?
16943       !blackPlaysFirst : WhiteOnMove(currentMove);
16944     DisplayWhiteClock(whiteTimeRemaining, wom);
16945     DisplayBlackClock(blackTimeRemaining, !wom);
16946 }
16947
16948
16949 /* Timekeeping seems to be a portability nightmare.  I think everyone
16950    has ftime(), but I'm really not sure, so I'm including some ifdefs
16951    to use other calls if you don't.  Clocks will be less accurate if
16952    you have neither ftime nor gettimeofday.
16953 */
16954
16955 /* VS 2008 requires the #include outside of the function */
16956 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16957 #include <sys/timeb.h>
16958 #endif
16959
16960 /* Get the current time as a TimeMark */
16961 void
16962 GetTimeMark (TimeMark *tm)
16963 {
16964 #if HAVE_GETTIMEOFDAY
16965
16966     struct timeval timeVal;
16967     struct timezone timeZone;
16968
16969     gettimeofday(&timeVal, &timeZone);
16970     tm->sec = (long) timeVal.tv_sec;
16971     tm->ms = (int) (timeVal.tv_usec / 1000L);
16972
16973 #else /*!HAVE_GETTIMEOFDAY*/
16974 #if HAVE_FTIME
16975
16976 // include <sys/timeb.h> / moved to just above start of function
16977     struct timeb timeB;
16978
16979     ftime(&timeB);
16980     tm->sec = (long) timeB.time;
16981     tm->ms = (int) timeB.millitm;
16982
16983 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16984     tm->sec = (long) time(NULL);
16985     tm->ms = 0;
16986 #endif
16987 #endif
16988 }
16989
16990 /* Return the difference in milliseconds between two
16991    time marks.  We assume the difference will fit in a long!
16992 */
16993 long
16994 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16995 {
16996     return 1000L*(tm2->sec - tm1->sec) +
16997            (long) (tm2->ms - tm1->ms);
16998 }
16999
17000
17001 /*
17002  * Code to manage the game clocks.
17003  *
17004  * In tournament play, black starts the clock and then white makes a move.
17005  * We give the human user a slight advantage if he is playing white---the
17006  * clocks don't run until he makes his first move, so it takes zero time.
17007  * Also, we don't account for network lag, so we could get out of sync
17008  * with GNU Chess's clock -- but then, referees are always right.
17009  */
17010
17011 static TimeMark tickStartTM;
17012 static long intendedTickLength;
17013
17014 long
17015 NextTickLength (long timeRemaining)
17016 {
17017     long nominalTickLength, nextTickLength;
17018
17019     if (timeRemaining > 0L && timeRemaining <= 10000L)
17020       nominalTickLength = 100L;
17021     else
17022       nominalTickLength = 1000L;
17023     nextTickLength = timeRemaining % nominalTickLength;
17024     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17025
17026     return nextTickLength;
17027 }
17028
17029 /* Adjust clock one minute up or down */
17030 void
17031 AdjustClock (Boolean which, int dir)
17032 {
17033     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17034     if(which) blackTimeRemaining += 60000*dir;
17035     else      whiteTimeRemaining += 60000*dir;
17036     DisplayBothClocks();
17037     adjustedClock = TRUE;
17038 }
17039
17040 /* Stop clocks and reset to a fresh time control */
17041 void
17042 ResetClocks ()
17043 {
17044     (void) StopClockTimer();
17045     if (appData.icsActive) {
17046         whiteTimeRemaining = blackTimeRemaining = 0;
17047     } else if (searchTime) {
17048         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17049         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17050     } else { /* [HGM] correct new time quote for time odds */
17051         whiteTC = blackTC = fullTimeControlString;
17052         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17053         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17054     }
17055     if (whiteFlag || blackFlag) {
17056         DisplayTitle("");
17057         whiteFlag = blackFlag = FALSE;
17058     }
17059     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17060     DisplayBothClocks();
17061     adjustedClock = FALSE;
17062 }
17063
17064 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17065
17066 /* Decrement running clock by amount of time that has passed */
17067 void
17068 DecrementClocks ()
17069 {
17070     long timeRemaining;
17071     long lastTickLength, fudge;
17072     TimeMark now;
17073
17074     if (!appData.clockMode) return;
17075     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17076
17077     GetTimeMark(&now);
17078
17079     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17080
17081     /* Fudge if we woke up a little too soon */
17082     fudge = intendedTickLength - lastTickLength;
17083     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17084
17085     if (WhiteOnMove(forwardMostMove)) {
17086         if(whiteNPS >= 0) lastTickLength = 0;
17087         timeRemaining = whiteTimeRemaining -= lastTickLength;
17088         if(timeRemaining < 0 && !appData.icsActive) {
17089             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17090             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17091                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17092                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17093             }
17094         }
17095         DisplayWhiteClock(whiteTimeRemaining - fudge,
17096                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17097     } else {
17098         if(blackNPS >= 0) lastTickLength = 0;
17099         timeRemaining = blackTimeRemaining -= lastTickLength;
17100         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17101             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17102             if(suddenDeath) {
17103                 blackStartMove = forwardMostMove;
17104                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17105             }
17106         }
17107         DisplayBlackClock(blackTimeRemaining - fudge,
17108                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17109     }
17110     if (CheckFlags()) return;
17111
17112     if(twoBoards) { // count down secondary board's clocks as well
17113         activePartnerTime -= lastTickLength;
17114         partnerUp = 1;
17115         if(activePartner == 'W')
17116             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17117         else
17118             DisplayBlackClock(activePartnerTime, TRUE);
17119         partnerUp = 0;
17120     }
17121
17122     tickStartTM = now;
17123     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17124     StartClockTimer(intendedTickLength);
17125
17126     /* if the time remaining has fallen below the alarm threshold, sound the
17127      * alarm. if the alarm has sounded and (due to a takeback or time control
17128      * with increment) the time remaining has increased to a level above the
17129      * threshold, reset the alarm so it can sound again.
17130      */
17131
17132     if (appData.icsActive && appData.icsAlarm) {
17133
17134         /* make sure we are dealing with the user's clock */
17135         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17136                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17137            )) return;
17138
17139         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17140             alarmSounded = FALSE;
17141         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17142             PlayAlarmSound();
17143             alarmSounded = TRUE;
17144         }
17145     }
17146 }
17147
17148
17149 /* A player has just moved, so stop the previously running
17150    clock and (if in clock mode) start the other one.
17151    We redisplay both clocks in case we're in ICS mode, because
17152    ICS gives us an update to both clocks after every move.
17153    Note that this routine is called *after* forwardMostMove
17154    is updated, so the last fractional tick must be subtracted
17155    from the color that is *not* on move now.
17156 */
17157 void
17158 SwitchClocks (int newMoveNr)
17159 {
17160     long lastTickLength;
17161     TimeMark now;
17162     int flagged = FALSE;
17163
17164     GetTimeMark(&now);
17165
17166     if (StopClockTimer() && appData.clockMode) {
17167         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17168         if (!WhiteOnMove(forwardMostMove)) {
17169             if(blackNPS >= 0) lastTickLength = 0;
17170             blackTimeRemaining -= lastTickLength;
17171            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17172 //         if(pvInfoList[forwardMostMove].time == -1)
17173                  pvInfoList[forwardMostMove].time =               // use GUI time
17174                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17175         } else {
17176            if(whiteNPS >= 0) lastTickLength = 0;
17177            whiteTimeRemaining -= lastTickLength;
17178            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17179 //         if(pvInfoList[forwardMostMove].time == -1)
17180                  pvInfoList[forwardMostMove].time =
17181                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17182         }
17183         flagged = CheckFlags();
17184     }
17185     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17186     CheckTimeControl();
17187
17188     if (flagged || !appData.clockMode) return;
17189
17190     switch (gameMode) {
17191       case MachinePlaysBlack:
17192       case MachinePlaysWhite:
17193       case BeginningOfGame:
17194         if (pausing) return;
17195         break;
17196
17197       case EditGame:
17198       case PlayFromGameFile:
17199       case IcsExamining:
17200         return;
17201
17202       default:
17203         break;
17204     }
17205
17206     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17207         if(WhiteOnMove(forwardMostMove))
17208              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17209         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17210     }
17211
17212     tickStartTM = now;
17213     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17214       whiteTimeRemaining : blackTimeRemaining);
17215     StartClockTimer(intendedTickLength);
17216 }
17217
17218
17219 /* Stop both clocks */
17220 void
17221 StopClocks ()
17222 {
17223     long lastTickLength;
17224     TimeMark now;
17225
17226     if (!StopClockTimer()) return;
17227     if (!appData.clockMode) return;
17228
17229     GetTimeMark(&now);
17230
17231     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17232     if (WhiteOnMove(forwardMostMove)) {
17233         if(whiteNPS >= 0) lastTickLength = 0;
17234         whiteTimeRemaining -= lastTickLength;
17235         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17236     } else {
17237         if(blackNPS >= 0) lastTickLength = 0;
17238         blackTimeRemaining -= lastTickLength;
17239         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17240     }
17241     CheckFlags();
17242 }
17243
17244 /* Start clock of player on move.  Time may have been reset, so
17245    if clock is already running, stop and restart it. */
17246 void
17247 StartClocks ()
17248 {
17249     (void) StopClockTimer(); /* in case it was running already */
17250     DisplayBothClocks();
17251     if (CheckFlags()) return;
17252
17253     if (!appData.clockMode) return;
17254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17255
17256     GetTimeMark(&tickStartTM);
17257     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17258       whiteTimeRemaining : blackTimeRemaining);
17259
17260    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17261     whiteNPS = blackNPS = -1;
17262     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17263        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17264         whiteNPS = first.nps;
17265     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17266        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17267         blackNPS = first.nps;
17268     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17269         whiteNPS = second.nps;
17270     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17271         blackNPS = second.nps;
17272     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17273
17274     StartClockTimer(intendedTickLength);
17275 }
17276
17277 char *
17278 TimeString (long ms)
17279 {
17280     long second, minute, hour, day;
17281     char *sign = "";
17282     static char buf[32];
17283
17284     if (ms > 0 && ms <= 9900) {
17285       /* convert milliseconds to tenths, rounding up */
17286       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17287
17288       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17289       return buf;
17290     }
17291
17292     /* convert milliseconds to seconds, rounding up */
17293     /* use floating point to avoid strangeness of integer division
17294        with negative dividends on many machines */
17295     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17296
17297     if (second < 0) {
17298         sign = "-";
17299         second = -second;
17300     }
17301
17302     day = second / (60 * 60 * 24);
17303     second = second % (60 * 60 * 24);
17304     hour = second / (60 * 60);
17305     second = second % (60 * 60);
17306     minute = second / 60;
17307     second = second % 60;
17308
17309     if (day > 0)
17310       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17311               sign, day, hour, minute, second);
17312     else if (hour > 0)
17313       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17314     else
17315       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17316
17317     return buf;
17318 }
17319
17320
17321 /*
17322  * This is necessary because some C libraries aren't ANSI C compliant yet.
17323  */
17324 char *
17325 StrStr (char *string, char *match)
17326 {
17327     int i, length;
17328
17329     length = strlen(match);
17330
17331     for (i = strlen(string) - length; i >= 0; i--, string++)
17332       if (!strncmp(match, string, length))
17333         return string;
17334
17335     return NULL;
17336 }
17337
17338 char *
17339 StrCaseStr (char *string, char *match)
17340 {
17341     int i, j, length;
17342
17343     length = strlen(match);
17344
17345     for (i = strlen(string) - length; i >= 0; i--, string++) {
17346         for (j = 0; j < length; j++) {
17347             if (ToLower(match[j]) != ToLower(string[j]))
17348               break;
17349         }
17350         if (j == length) return string;
17351     }
17352
17353     return NULL;
17354 }
17355
17356 #ifndef _amigados
17357 int
17358 StrCaseCmp (char *s1, char *s2)
17359 {
17360     char c1, c2;
17361
17362     for (;;) {
17363         c1 = ToLower(*s1++);
17364         c2 = ToLower(*s2++);
17365         if (c1 > c2) return 1;
17366         if (c1 < c2) return -1;
17367         if (c1 == NULLCHAR) return 0;
17368     }
17369 }
17370
17371
17372 int
17373 ToLower (int c)
17374 {
17375     return isupper(c) ? tolower(c) : c;
17376 }
17377
17378
17379 int
17380 ToUpper (int c)
17381 {
17382     return islower(c) ? toupper(c) : c;
17383 }
17384 #endif /* !_amigados    */
17385
17386 char *
17387 StrSave (char *s)
17388 {
17389   char *ret;
17390
17391   if ((ret = (char *) malloc(strlen(s) + 1)))
17392     {
17393       safeStrCpy(ret, s, strlen(s)+1);
17394     }
17395   return ret;
17396 }
17397
17398 char *
17399 StrSavePtr (char *s, char **savePtr)
17400 {
17401     if (*savePtr) {
17402         free(*savePtr);
17403     }
17404     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17405       safeStrCpy(*savePtr, s, strlen(s)+1);
17406     }
17407     return(*savePtr);
17408 }
17409
17410 char *
17411 PGNDate ()
17412 {
17413     time_t clock;
17414     struct tm *tm;
17415     char buf[MSG_SIZ];
17416
17417     clock = time((time_t *)NULL);
17418     tm = localtime(&clock);
17419     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17420             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17421     return StrSave(buf);
17422 }
17423
17424
17425 char *
17426 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17427 {
17428     int i, j, fromX, fromY, toX, toY;
17429     int whiteToPlay;
17430     char buf[MSG_SIZ];
17431     char *p, *q;
17432     int emptycount;
17433     ChessSquare piece;
17434
17435     whiteToPlay = (gameMode == EditPosition) ?
17436       !blackPlaysFirst : (move % 2 == 0);
17437     p = buf;
17438
17439     /* Piece placement data */
17440     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17441         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17442         emptycount = 0;
17443         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17444             if (boards[move][i][j] == EmptySquare) {
17445                 emptycount++;
17446             } else { ChessSquare piece = boards[move][i][j];
17447                 if (emptycount > 0) {
17448                     if(emptycount<10) /* [HGM] can be >= 10 */
17449                         *p++ = '0' + emptycount;
17450                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17451                     emptycount = 0;
17452                 }
17453                 if(PieceToChar(piece) == '+') {
17454                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17455                     *p++ = '+';
17456                     piece = (ChessSquare)(DEMOTED piece);
17457                 }
17458                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17459                 if(p[-1] == '~') {
17460                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17461                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17462                     *p++ = '~';
17463                 }
17464             }
17465         }
17466         if (emptycount > 0) {
17467             if(emptycount<10) /* [HGM] can be >= 10 */
17468                 *p++ = '0' + emptycount;
17469             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17470             emptycount = 0;
17471         }
17472         *p++ = '/';
17473     }
17474     *(p - 1) = ' ';
17475
17476     /* [HGM] print Crazyhouse or Shogi holdings */
17477     if( gameInfo.holdingsWidth ) {
17478         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17479         q = p;
17480         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17481             piece = boards[move][i][BOARD_WIDTH-1];
17482             if( piece != EmptySquare )
17483               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17484                   *p++ = PieceToChar(piece);
17485         }
17486         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17487             piece = boards[move][BOARD_HEIGHT-i-1][0];
17488             if( piece != EmptySquare )
17489               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17490                   *p++ = PieceToChar(piece);
17491         }
17492
17493         if( q == p ) *p++ = '-';
17494         *p++ = ']';
17495         *p++ = ' ';
17496     }
17497
17498     /* Active color */
17499     *p++ = whiteToPlay ? 'w' : 'b';
17500     *p++ = ' ';
17501
17502   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17503     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17504   } else {
17505   if(nrCastlingRights) {
17506      q = p;
17507      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17508        /* [HGM] write directly from rights */
17509            if(boards[move][CASTLING][2] != NoRights &&
17510               boards[move][CASTLING][0] != NoRights   )
17511                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17512            if(boards[move][CASTLING][2] != NoRights &&
17513               boards[move][CASTLING][1] != NoRights   )
17514                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17515            if(boards[move][CASTLING][5] != NoRights &&
17516               boards[move][CASTLING][3] != NoRights   )
17517                 *p++ = boards[move][CASTLING][3] + AAA;
17518            if(boards[move][CASTLING][5] != NoRights &&
17519               boards[move][CASTLING][4] != NoRights   )
17520                 *p++ = boards[move][CASTLING][4] + AAA;
17521      } else {
17522
17523         /* [HGM] write true castling rights */
17524         if( nrCastlingRights == 6 ) {
17525             int q, k=0;
17526             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17527                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17528             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17529                  boards[move][CASTLING][2] != NoRights  );
17530             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17531                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17532                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17533                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17534                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17535             }
17536             if(q) *p++ = 'Q';
17537             k = 0;
17538             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17539                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17540             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17541                  boards[move][CASTLING][5] != NoRights  );
17542             if(gameInfo.variant == VariantSChess) {
17543                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17544                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17545                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17546                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17547             }
17548             if(q) *p++ = 'q';
17549         }
17550      }
17551      if (q == p) *p++ = '-'; /* No castling rights */
17552      *p++ = ' ';
17553   }
17554
17555   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17556      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17557      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17558     /* En passant target square */
17559     if (move > backwardMostMove) {
17560         fromX = moveList[move - 1][0] - AAA;
17561         fromY = moveList[move - 1][1] - ONE;
17562         toX = moveList[move - 1][2] - AAA;
17563         toY = moveList[move - 1][3] - ONE;
17564         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17565             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17566             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17567             fromX == toX) {
17568             /* 2-square pawn move just happened */
17569             *p++ = toX + AAA;
17570             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17571         } else {
17572             *p++ = '-';
17573         }
17574     } else if(move == backwardMostMove) {
17575         // [HGM] perhaps we should always do it like this, and forget the above?
17576         if((signed char)boards[move][EP_STATUS] >= 0) {
17577             *p++ = boards[move][EP_STATUS] + AAA;
17578             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17579         } else {
17580             *p++ = '-';
17581         }
17582     } else {
17583         *p++ = '-';
17584     }
17585     *p++ = ' ';
17586   }
17587   }
17588
17589     if(moveCounts)
17590     {   int i = 0, j=move;
17591
17592         /* [HGM] find reversible plies */
17593         if (appData.debugMode) { int k;
17594             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17595             for(k=backwardMostMove; k<=forwardMostMove; k++)
17596                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17597
17598         }
17599
17600         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17601         if( j == backwardMostMove ) i += initialRulePlies;
17602         sprintf(p, "%d ", i);
17603         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17604
17605         /* Fullmove number */
17606         sprintf(p, "%d", (move / 2) + 1);
17607     } else *--p = NULLCHAR;
17608
17609     return StrSave(buf);
17610 }
17611
17612 Boolean
17613 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17614 {
17615     int i, j, k, w=0;
17616     char *p, c;
17617     int emptycount, virgin[BOARD_FILES];
17618     ChessSquare piece;
17619
17620     p = fen;
17621
17622     /* Piece placement data */
17623     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17624         j = 0;
17625         for (;;) {
17626             if (*p == '/' || *p == ' ' || *p == '[' ) {
17627                 if(j > w) w = j;
17628                 emptycount = gameInfo.boardWidth - j;
17629                 while (emptycount--)
17630                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17631                 if (*p == '/') p++;
17632                 else if(autoSize) { // we stumbled unexpectedly into end of board
17633                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17634                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17635                     }
17636                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17637                 }
17638                 break;
17639 #if(BOARD_FILES >= 10)
17640             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17641                 p++; emptycount=10;
17642                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17643                 while (emptycount--)
17644                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17645 #endif
17646             } else if (*p == '*') {
17647                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17648             } else if (isdigit(*p)) {
17649                 emptycount = *p++ - '0';
17650                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17651                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17652                 while (emptycount--)
17653                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17654             } else if (*p == '+' || isalpha(*p)) {
17655                 if (j >= gameInfo.boardWidth) return FALSE;
17656                 if(*p=='+') {
17657                     piece = CharToPiece(*++p);
17658                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17659                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17660                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17661                 } else piece = CharToPiece(*p++);
17662
17663                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17664                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17665                     piece = (ChessSquare) (PROMOTED piece);
17666                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17667                     p++;
17668                 }
17669                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17670             } else {
17671                 return FALSE;
17672             }
17673         }
17674     }
17675     while (*p == '/' || *p == ' ') p++;
17676
17677     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17678
17679     /* [HGM] by default clear Crazyhouse holdings, if present */
17680     if(gameInfo.holdingsWidth) {
17681        for(i=0; i<BOARD_HEIGHT; i++) {
17682            board[i][0]             = EmptySquare; /* black holdings */
17683            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17684            board[i][1]             = (ChessSquare) 0; /* black counts */
17685            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17686        }
17687     }
17688
17689     /* [HGM] look for Crazyhouse holdings here */
17690     while(*p==' ') p++;
17691     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17692         if(*p == '[') p++;
17693         if(*p == '-' ) p++; /* empty holdings */ else {
17694             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17695             /* if we would allow FEN reading to set board size, we would   */
17696             /* have to add holdings and shift the board read so far here   */
17697             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17698                 p++;
17699                 if((int) piece >= (int) BlackPawn ) {
17700                     i = (int)piece - (int)BlackPawn;
17701                     i = PieceToNumber((ChessSquare)i);
17702                     if( i >= gameInfo.holdingsSize ) return FALSE;
17703                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17704                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17705                 } else {
17706                     i = (int)piece - (int)WhitePawn;
17707                     i = PieceToNumber((ChessSquare)i);
17708                     if( i >= gameInfo.holdingsSize ) return FALSE;
17709                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17710                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17711                 }
17712             }
17713         }
17714         if(*p == ']') p++;
17715     }
17716
17717     while(*p == ' ') p++;
17718
17719     /* Active color */
17720     c = *p++;
17721     if(appData.colorNickNames) {
17722       if( c == appData.colorNickNames[0] ) c = 'w'; else
17723       if( c == appData.colorNickNames[1] ) c = 'b';
17724     }
17725     switch (c) {
17726       case 'w':
17727         *blackPlaysFirst = FALSE;
17728         break;
17729       case 'b':
17730         *blackPlaysFirst = TRUE;
17731         break;
17732       default:
17733         return FALSE;
17734     }
17735
17736     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17737     /* return the extra info in global variiables             */
17738
17739     /* set defaults in case FEN is incomplete */
17740     board[EP_STATUS] = EP_UNKNOWN;
17741     for(i=0; i<nrCastlingRights; i++ ) {
17742         board[CASTLING][i] =
17743             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17744     }   /* assume possible unless obviously impossible */
17745     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17746     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17747     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17748                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17749     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17750     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17751     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17752                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17753     FENrulePlies = 0;
17754
17755     while(*p==' ') p++;
17756     if(nrCastlingRights) {
17757       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17758       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17759           /* castling indicator present, so default becomes no castlings */
17760           for(i=0; i<nrCastlingRights; i++ ) {
17761                  board[CASTLING][i] = NoRights;
17762           }
17763       }
17764       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17765              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17766              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17767              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17768         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17769
17770         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17771             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17772             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17773         }
17774         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17775             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17776         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17777                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17778         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17779                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17780         switch(c) {
17781           case'K':
17782               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17783               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17784               board[CASTLING][2] = whiteKingFile;
17785               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17786               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17787               break;
17788           case'Q':
17789               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17790               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17791               board[CASTLING][2] = whiteKingFile;
17792               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17793               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17794               break;
17795           case'k':
17796               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17797               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17798               board[CASTLING][5] = blackKingFile;
17799               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17800               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17801               break;
17802           case'q':
17803               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17804               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17805               board[CASTLING][5] = blackKingFile;
17806               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17807               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17808           case '-':
17809               break;
17810           default: /* FRC castlings */
17811               if(c >= 'a') { /* black rights */
17812                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17813                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17814                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17815                   if(i == BOARD_RGHT) break;
17816                   board[CASTLING][5] = i;
17817                   c -= AAA;
17818                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17819                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17820                   if(c > i)
17821                       board[CASTLING][3] = c;
17822                   else
17823                       board[CASTLING][4] = c;
17824               } else { /* white rights */
17825                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17826                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17827                     if(board[0][i] == WhiteKing) break;
17828                   if(i == BOARD_RGHT) break;
17829                   board[CASTLING][2] = i;
17830                   c -= AAA - 'a' + 'A';
17831                   if(board[0][c] >= WhiteKing) break;
17832                   if(c > i)
17833                       board[CASTLING][0] = c;
17834                   else
17835                       board[CASTLING][1] = c;
17836               }
17837         }
17838       }
17839       for(i=0; i<nrCastlingRights; i++)
17840         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17841       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17842     if (appData.debugMode) {
17843         fprintf(debugFP, "FEN castling rights:");
17844         for(i=0; i<nrCastlingRights; i++)
17845         fprintf(debugFP, " %d", board[CASTLING][i]);
17846         fprintf(debugFP, "\n");
17847     }
17848
17849       while(*p==' ') p++;
17850     }
17851
17852     /* read e.p. field in games that know e.p. capture */
17853     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17854        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17855        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17856       if(*p=='-') {
17857         p++; board[EP_STATUS] = EP_NONE;
17858       } else {
17859          char c = *p++ - AAA;
17860
17861          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17862          if(*p >= '0' && *p <='9') p++;
17863          board[EP_STATUS] = c;
17864       }
17865     }
17866
17867
17868     if(sscanf(p, "%d", &i) == 1) {
17869         FENrulePlies = i; /* 50-move ply counter */
17870         /* (The move number is still ignored)    */
17871     }
17872
17873     return TRUE;
17874 }
17875
17876 void
17877 EditPositionPasteFEN (char *fen)
17878 {
17879   if (fen != NULL) {
17880     Board initial_position;
17881
17882     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17883       DisplayError(_("Bad FEN position in clipboard"), 0);
17884       return ;
17885     } else {
17886       int savedBlackPlaysFirst = blackPlaysFirst;
17887       EditPositionEvent();
17888       blackPlaysFirst = savedBlackPlaysFirst;
17889       CopyBoard(boards[0], initial_position);
17890       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17891       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17892       DisplayBothClocks();
17893       DrawPosition(FALSE, boards[currentMove]);
17894     }
17895   }
17896 }
17897
17898 static char cseq[12] = "\\   ";
17899
17900 Boolean
17901 set_cont_sequence (char *new_seq)
17902 {
17903     int len;
17904     Boolean ret;
17905
17906     // handle bad attempts to set the sequence
17907         if (!new_seq)
17908                 return 0; // acceptable error - no debug
17909
17910     len = strlen(new_seq);
17911     ret = (len > 0) && (len < sizeof(cseq));
17912     if (ret)
17913       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17914     else if (appData.debugMode)
17915       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17916     return ret;
17917 }
17918
17919 /*
17920     reformat a source message so words don't cross the width boundary.  internal
17921     newlines are not removed.  returns the wrapped size (no null character unless
17922     included in source message).  If dest is NULL, only calculate the size required
17923     for the dest buffer.  lp argument indicats line position upon entry, and it's
17924     passed back upon exit.
17925 */
17926 int
17927 wrap (char *dest, char *src, int count, int width, int *lp)
17928 {
17929     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17930
17931     cseq_len = strlen(cseq);
17932     old_line = line = *lp;
17933     ansi = len = clen = 0;
17934
17935     for (i=0; i < count; i++)
17936     {
17937         if (src[i] == '\033')
17938             ansi = 1;
17939
17940         // if we hit the width, back up
17941         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17942         {
17943             // store i & len in case the word is too long
17944             old_i = i, old_len = len;
17945
17946             // find the end of the last word
17947             while (i && src[i] != ' ' && src[i] != '\n')
17948             {
17949                 i--;
17950                 len--;
17951             }
17952
17953             // word too long?  restore i & len before splitting it
17954             if ((old_i-i+clen) >= width)
17955             {
17956                 i = old_i;
17957                 len = old_len;
17958             }
17959
17960             // extra space?
17961             if (i && src[i-1] == ' ')
17962                 len--;
17963
17964             if (src[i] != ' ' && src[i] != '\n')
17965             {
17966                 i--;
17967                 if (len)
17968                     len--;
17969             }
17970
17971             // now append the newline and continuation sequence
17972             if (dest)
17973                 dest[len] = '\n';
17974             len++;
17975             if (dest)
17976                 strncpy(dest+len, cseq, cseq_len);
17977             len += cseq_len;
17978             line = cseq_len;
17979             clen = cseq_len;
17980             continue;
17981         }
17982
17983         if (dest)
17984             dest[len] = src[i];
17985         len++;
17986         if (!ansi)
17987             line++;
17988         if (src[i] == '\n')
17989             line = 0;
17990         if (src[i] == 'm')
17991             ansi = 0;
17992     }
17993     if (dest && appData.debugMode)
17994     {
17995         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17996             count, width, line, len, *lp);
17997         show_bytes(debugFP, src, count);
17998         fprintf(debugFP, "\ndest: ");
17999         show_bytes(debugFP, dest, len);
18000         fprintf(debugFP, "\n");
18001     }
18002     *lp = dest ? line : old_line;
18003
18004     return len;
18005 }
18006
18007 // [HGM] vari: routines for shelving variations
18008 Boolean modeRestore = FALSE;
18009
18010 void
18011 PushInner (int firstMove, int lastMove)
18012 {
18013         int i, j, nrMoves = lastMove - firstMove;
18014
18015         // push current tail of game on stack
18016         savedResult[storedGames] = gameInfo.result;
18017         savedDetails[storedGames] = gameInfo.resultDetails;
18018         gameInfo.resultDetails = NULL;
18019         savedFirst[storedGames] = firstMove;
18020         savedLast [storedGames] = lastMove;
18021         savedFramePtr[storedGames] = framePtr;
18022         framePtr -= nrMoves; // reserve space for the boards
18023         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18024             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18025             for(j=0; j<MOVE_LEN; j++)
18026                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18027             for(j=0; j<2*MOVE_LEN; j++)
18028                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18029             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18030             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18031             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18032             pvInfoList[firstMove+i-1].depth = 0;
18033             commentList[framePtr+i] = commentList[firstMove+i];
18034             commentList[firstMove+i] = NULL;
18035         }
18036
18037         storedGames++;
18038         forwardMostMove = firstMove; // truncate game so we can start variation
18039 }
18040
18041 void
18042 PushTail (int firstMove, int lastMove)
18043 {
18044         if(appData.icsActive) { // only in local mode
18045                 forwardMostMove = currentMove; // mimic old ICS behavior
18046                 return;
18047         }
18048         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18049
18050         PushInner(firstMove, lastMove);
18051         if(storedGames == 1) GreyRevert(FALSE);
18052         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18053 }
18054
18055 void
18056 PopInner (Boolean annotate)
18057 {
18058         int i, j, nrMoves;
18059         char buf[8000], moveBuf[20];
18060
18061         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18062         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18063         nrMoves = savedLast[storedGames] - currentMove;
18064         if(annotate) {
18065                 int cnt = 10;
18066                 if(!WhiteOnMove(currentMove))
18067                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18068                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18069                 for(i=currentMove; i<forwardMostMove; i++) {
18070                         if(WhiteOnMove(i))
18071                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18072                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18073                         strcat(buf, moveBuf);
18074                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18075                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18076                 }
18077                 strcat(buf, ")");
18078         }
18079         for(i=1; i<=nrMoves; i++) { // copy last variation back
18080             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18081             for(j=0; j<MOVE_LEN; j++)
18082                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18083             for(j=0; j<2*MOVE_LEN; j++)
18084                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18085             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18086             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18087             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18088             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18089             commentList[currentMove+i] = commentList[framePtr+i];
18090             commentList[framePtr+i] = NULL;
18091         }
18092         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18093         framePtr = savedFramePtr[storedGames];
18094         gameInfo.result = savedResult[storedGames];
18095         if(gameInfo.resultDetails != NULL) {
18096             free(gameInfo.resultDetails);
18097       }
18098         gameInfo.resultDetails = savedDetails[storedGames];
18099         forwardMostMove = currentMove + nrMoves;
18100 }
18101
18102 Boolean
18103 PopTail (Boolean annotate)
18104 {
18105         if(appData.icsActive) return FALSE; // only in local mode
18106         if(!storedGames) return FALSE; // sanity
18107         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18108
18109         PopInner(annotate);
18110         if(currentMove < forwardMostMove) ForwardEvent(); else
18111         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18112
18113         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18114         return TRUE;
18115 }
18116
18117 void
18118 CleanupTail ()
18119 {       // remove all shelved variations
18120         int i;
18121         for(i=0; i<storedGames; i++) {
18122             if(savedDetails[i])
18123                 free(savedDetails[i]);
18124             savedDetails[i] = NULL;
18125         }
18126         for(i=framePtr; i<MAX_MOVES; i++) {
18127                 if(commentList[i]) free(commentList[i]);
18128                 commentList[i] = NULL;
18129         }
18130         framePtr = MAX_MOVES-1;
18131         storedGames = 0;
18132 }
18133
18134 void
18135 LoadVariation (int index, char *text)
18136 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18137         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18138         int level = 0, move;
18139
18140         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18141         // first find outermost bracketing variation
18142         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18143             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18144                 if(*p == '{') wait = '}'; else
18145                 if(*p == '[') wait = ']'; else
18146                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18147                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18148             }
18149             if(*p == wait) wait = NULLCHAR; // closing ]} found
18150             p++;
18151         }
18152         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18153         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18154         end[1] = NULLCHAR; // clip off comment beyond variation
18155         ToNrEvent(currentMove-1);
18156         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18157         // kludge: use ParsePV() to append variation to game
18158         move = currentMove;
18159         ParsePV(start, TRUE, TRUE);
18160         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18161         ClearPremoveHighlights();
18162         CommentPopDown();
18163         ToNrEvent(currentMove+1);
18164 }
18165
18166 void
18167 LoadTheme ()
18168 {
18169     char *p, *q, buf[MSG_SIZ];
18170     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18171         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18172         ParseArgsFromString(buf);
18173         ActivateTheme(TRUE); // also redo colors
18174         return;
18175     }
18176     p = nickName;
18177     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18178     {
18179         int len;
18180         q = appData.themeNames;
18181         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18182       if(appData.useBitmaps) {
18183         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18184                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18185                 appData.liteBackTextureMode,
18186                 appData.darkBackTextureMode );
18187       } else {
18188         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18189                 Col2Text(2),   // lightSquareColor
18190                 Col2Text(3) ); // darkSquareColor
18191       }
18192       if(appData.useBorder) {
18193         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18194                 appData.border);
18195       } else {
18196         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18197       }
18198       if(appData.useFont) {
18199         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18200                 appData.renderPiecesWithFont,
18201                 appData.fontToPieceTable,
18202                 Col2Text(9),    // appData.fontBackColorWhite
18203                 Col2Text(10) ); // appData.fontForeColorBlack
18204       } else {
18205         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18206                 appData.pieceDirectory);
18207         if(!appData.pieceDirectory[0])
18208           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18209                 Col2Text(0),   // whitePieceColor
18210                 Col2Text(1) ); // blackPieceColor
18211       }
18212       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18213                 Col2Text(4),   // highlightSquareColor
18214                 Col2Text(5) ); // premoveHighlightColor
18215         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18216         if(insert != q) insert[-1] = NULLCHAR;
18217         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18218         if(q)   free(q);
18219     }
18220     ActivateTheme(FALSE);
18221 }