Allow piece promotion by pieceToChar in all variants
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 static int initPing = -1;
279
280 /* States for ics_getting_history */
281 #define H_FALSE 0
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
287
288 /* whosays values for GameEnds */
289 #define GE_ICS 0
290 #define GE_ENGINE 1
291 #define GE_PLAYER 2
292 #define GE_FILE 3
293 #define GE_XBOARD 4
294 #define GE_ENGINE1 5
295 #define GE_ENGINE2 6
296
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
299
300 /* Different types of move when calling RegisterMove */
301 #define CMAIL_MOVE   0
302 #define CMAIL_RESIGN 1
303 #define CMAIL_DRAW   2
304 #define CMAIL_ACCEPT 3
305
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
310
311 /* Telnet protocol constants */
312 #define TN_WILL 0373
313 #define TN_WONT 0374
314 #define TN_DO   0375
315 #define TN_DONT 0376
316 #define TN_IAC  0377
317 #define TN_ECHO 0001
318 #define TN_SGA  0003
319 #define TN_PORT 23
320
321 char*
322 safeStrCpy (char *dst, const char *src, size_t count)
323 { // [HGM] made safe
324   int i;
325   assert( dst != NULL );
326   assert( src != NULL );
327   assert( count > 0 );
328
329   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330   if(  i == count && dst[count-1] != NULLCHAR)
331     {
332       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333       if(appData.debugMode)
334         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335     }
336
337   return dst;
338 }
339
340 /* Some compiler can't cast u64 to double
341  * This function do the job for us:
342
343  * We use the highest bit for cast, this only
344  * works if the highest bit is not
345  * in use (This should not happen)
346  *
347  * We used this for all compiler
348  */
349 double
350 u64ToDouble (u64 value)
351 {
352   double r;
353   u64 tmp = value & u64Const(0x7fffffffffffffff);
354   r = (double)(s64)tmp;
355   if (value & u64Const(0x8000000000000000))
356        r +=  9.2233720368547758080e18; /* 2^63 */
357  return r;
358 }
359
360 /* Fake up flags for now, as we aren't keeping track of castling
361    availability yet. [HGM] Change of logic: the flag now only
362    indicates the type of castlings allowed by the rule of the game.
363    The actual rights themselves are maintained in the array
364    castlingRights, as part of the game history, and are not probed
365    by this function.
366  */
367 int
368 PosFlags (index)
369 {
370   int flags = F_ALL_CASTLE_OK;
371   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372   switch (gameInfo.variant) {
373   case VariantSuicide:
374     flags &= ~F_ALL_CASTLE_OK;
375   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376     flags |= F_IGNORE_CHECK;
377   case VariantLosers:
378     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379     break;
380   case VariantAtomic:
381     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382     break;
383   case VariantKriegspiel:
384     flags |= F_KRIEGSPIEL_CAPTURE;
385     break;
386   case VariantCapaRandom:
387   case VariantFischeRandom:
388     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389   case VariantNoCastle:
390   case VariantShatranj:
391   case VariantCourier:
392   case VariantMakruk:
393   case VariantASEAN:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second, pairing;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackMan, BlackFerz,
567         BlackKing, BlackMan, BlackKnight, BlackRook }
568 };
569
570 ChessSquare  lionArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackLion, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackKnight, BlackRook }
575 };
576
577
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
584 };
585
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
591 };
592
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
598 };
599
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
605 };
606
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
612 };
613
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
619 };
620
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
626 };
627
628 #ifdef GOTHIC
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
634 };
635 #else // !GOTHIC
636 #define GothicArray CapablancaArray
637 #endif // !GOTHIC
638
639 #ifdef FALCON
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
645 };
646 #else // !FALCON
647 #define FalconArray CapablancaArray
648 #endif // !FALCON
649
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
656
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
663 };
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
677 };
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
682
683
684 Board initialPosition;
685
686
687 /* Convert str to a rating. Checks for special cases of "----",
688
689    "++++", etc. Also strips ()'s */
690 int
691 string_to_rating (char *str)
692 {
693   while(*str && !isdigit(*str)) ++str;
694   if (!*str)
695     return 0;   /* One of the special "no rating" cases */
696   else
697     return atoi(str);
698 }
699
700 void
701 ClearProgramStats ()
702 {
703     /* Init programStats */
704     programStats.movelist[0] = 0;
705     programStats.depth = 0;
706     programStats.nr_moves = 0;
707     programStats.moves_left = 0;
708     programStats.nodes = 0;
709     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
710     programStats.score = 0;
711     programStats.got_only_move = 0;
712     programStats.got_fail = 0;
713     programStats.line_is_book = 0;
714 }
715
716 void
717 CommonEngineInit ()
718 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719     if (appData.firstPlaysBlack) {
720         first.twoMachinesColor = "black\n";
721         second.twoMachinesColor = "white\n";
722     } else {
723         first.twoMachinesColor = "white\n";
724         second.twoMachinesColor = "black\n";
725     }
726
727     first.other = &second;
728     second.other = &first;
729
730     { float norm = 1;
731         if(appData.timeOddsMode) {
732             norm = appData.timeOdds[0];
733             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
734         }
735         first.timeOdds  = appData.timeOdds[0]/norm;
736         second.timeOdds = appData.timeOdds[1]/norm;
737     }
738
739     if(programVersion) free(programVersion);
740     if (appData.noChessProgram) {
741         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742         sprintf(programVersion, "%s", PACKAGE_STRING);
743     } else {
744       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
747     }
748 }
749
750 void
751 UnloadEngine (ChessProgramState *cps)
752 {
753         /* Kill off first chess program */
754         if (cps->isr != NULL)
755           RemoveInputSource(cps->isr);
756         cps->isr = NULL;
757
758         if (cps->pr != NoProc) {
759             ExitAnalyzeMode();
760             DoSleep( appData.delayBeforeQuit );
761             SendToProgram("quit\n", cps);
762             DoSleep( appData.delayAfterQuit );
763             DestroyChildProcess(cps->pr, cps->useSigterm);
764         }
765         cps->pr = NoProc;
766         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
767 }
768
769 void
770 ClearOptions (ChessProgramState *cps)
771 {
772     int i;
773     cps->nrOptions = cps->comboCnt = 0;
774     for(i=0; i<MAX_OPTIONS; i++) {
775         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776         cps->option[i].textValue = 0;
777     }
778 }
779
780 char *engineNames[] = {
781   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 N_("first"),
784   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
786 N_("second")
787 };
788
789 void
790 InitEngine (ChessProgramState *cps, int n)
791 {   // [HGM] all engine initialiation put in a function that does one engine
792
793     ClearOptions(cps);
794
795     cps->which = engineNames[n];
796     cps->maybeThinking = FALSE;
797     cps->pr = NoProc;
798     cps->isr = NULL;
799     cps->sendTime = 2;
800     cps->sendDrawOffers = 1;
801
802     cps->program = appData.chessProgram[n];
803     cps->host = appData.host[n];
804     cps->dir = appData.directory[n];
805     cps->initString = appData.engInitString[n];
806     cps->computerString = appData.computerString[n];
807     cps->useSigint  = TRUE;
808     cps->useSigterm = TRUE;
809     cps->reuse = appData.reuse[n];
810     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
811     cps->useSetboard = FALSE;
812     cps->useSAN = FALSE;
813     cps->usePing = FALSE;
814     cps->lastPing = 0;
815     cps->lastPong = 0;
816     cps->usePlayother = FALSE;
817     cps->useColors = TRUE;
818     cps->useUsermove = FALSE;
819     cps->sendICS = FALSE;
820     cps->sendName = appData.icsActive;
821     cps->sdKludge = FALSE;
822     cps->stKludge = FALSE;
823     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824     TidyProgramName(cps->program, cps->host, cps->tidy);
825     cps->matchWins = 0;
826     ASSIGN(cps->variants, appData.variant);
827     cps->analysisSupport = 2; /* detect */
828     cps->analyzing = FALSE;
829     cps->initDone = FALSE;
830     cps->reload = FALSE;
831
832     /* New features added by Tord: */
833     cps->useFEN960 = FALSE;
834     cps->useOOCastle = TRUE;
835     /* End of new features added by Tord. */
836     cps->fenOverride  = appData.fenOverride[n];
837
838     /* [HGM] time odds: set factor for each machine */
839     cps->timeOdds  = appData.timeOdds[n];
840
841     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842     cps->accumulateTC = appData.accumulateTC[n];
843     cps->maxNrOfSessions = 1;
844
845     /* [HGM] debug */
846     cps->debug = FALSE;
847
848     cps->supportsNPS = UNKNOWN;
849     cps->memSize = FALSE;
850     cps->maxCores = FALSE;
851     ASSIGN(cps->egtFormats, "");
852
853     /* [HGM] options */
854     cps->optionSettings  = appData.engOptions[n];
855
856     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857     cps->isUCI = appData.isUCI[n]; /* [AS] */
858     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
859     cps->highlight = 0;
860
861     if (appData.protocolVersion[n] > PROTOVER
862         || appData.protocolVersion[n] < 1)
863       {
864         char buf[MSG_SIZ];
865         int len;
866
867         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868                        appData.protocolVersion[n]);
869         if( (len >= MSG_SIZ) && appData.debugMode )
870           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
871
872         DisplayFatalError(buf, 0, 2);
873       }
874     else
875       {
876         cps->protocolVersion = appData.protocolVersion[n];
877       }
878
879     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
880     ParseFeatures(appData.featureDefaults, cps);
881 }
882
883 ChessProgramState *savCps;
884
885 GameMode oldMode;
886
887 void
888 LoadEngine ()
889 {
890     int i;
891     if(WaitForEngine(savCps, LoadEngine)) return;
892     CommonEngineInit(); // recalculate time odds
893     if(gameInfo.variant != StringToVariant(appData.variant)) {
894         // we changed variant when loading the engine; this forces us to reset
895         Reset(TRUE, savCps != &first);
896         oldMode = BeginningOfGame; // to prevent restoring old mode
897     }
898     InitChessProgram(savCps, FALSE);
899     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900     DisplayMessage("", "");
901     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
903     ThawUI();
904     SetGNUMode();
905     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
906 }
907
908 void
909 ReplaceEngine (ChessProgramState *cps, int n)
910 {
911     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
912     keepInfo = 1;
913     if(oldMode != BeginningOfGame) EditGameEvent();
914     keepInfo = 0;
915     UnloadEngine(cps);
916     appData.noChessProgram = FALSE;
917     appData.clockMode = TRUE;
918     InitEngine(cps, n);
919     UpdateLogos(TRUE);
920     if(n) return; // only startup first engine immediately; second can wait
921     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
922     LoadEngine();
923 }
924
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
927
928 static char resetOptions[] =
929         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
933
934 void
935 FloatToFront(char **list, char *engineLine)
936 {
937     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
938     int i=0;
939     if(appData.recentEngines <= 0) return;
940     TidyProgramName(engineLine, "localhost", tidy+1);
941     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942     strncpy(buf+1, *list, MSG_SIZ-50);
943     if(p = strstr(buf, tidy)) { // tidy name appears in list
944         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945         while(*p++ = *++q); // squeeze out
946     }
947     strcat(tidy, buf+1); // put list behind tidy name
948     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950     ASSIGN(*list, tidy+1);
951 }
952
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
954
955 void
956 Load (ChessProgramState *cps, int i)
957 {
958     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964         appData.firstProtocolVersion = PROTOVER;
965         ParseArgsFromString(buf);
966         SwapEngines(i);
967         ReplaceEngine(cps, i);
968         FloatToFront(&appData.recentEngineList, engineLine);
969         return;
970     }
971     p = engineName;
972     while(q = strchr(p, SLASH)) p = q+1;
973     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974     if(engineDir[0] != NULLCHAR) {
975         ASSIGN(appData.directory[i], engineDir); p = engineName;
976     } else if(p != engineName) { // derive directory from engine path, when not given
977         p[-1] = 0;
978         ASSIGN(appData.directory[i], engineName);
979         p[-1] = SLASH;
980         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981     } else { ASSIGN(appData.directory[i], "."); }
982     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
983     if(params[0]) {
984         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985         snprintf(command, MSG_SIZ, "%s %s", p, params);
986         p = command;
987     }
988     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989     ASSIGN(appData.chessProgram[i], p);
990     appData.isUCI[i] = isUCI;
991     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992     appData.hasOwnBookUCI[i] = hasBook;
993     if(!nickName[0]) useNick = FALSE;
994     if(useNick) ASSIGN(appData.pgnName[i], nickName);
995     if(addToList) {
996         int len;
997         char quote;
998         q = firstChessProgramNames;
999         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002                         quote, p, quote, appData.directory[i],
1003                         useNick ? " -fn \"" : "",
1004                         useNick ? nickName : "",
1005                         useNick ? "\"" : "",
1006                         v1 ? " -firstProtocolVersion 1" : "",
1007                         hasBook ? "" : " -fNoOwnBookUCI",
1008                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009                         storeVariant ? " -variant " : "",
1010                         storeVariant ? VariantName(gameInfo.variant) : "");
1011         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013         if(insert != q) insert[-1] = NULLCHAR;
1014         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1015         if(q)   free(q);
1016         FloatToFront(&appData.recentEngineList, buf);
1017     }
1018     ReplaceEngine(cps, i);
1019 }
1020
1021 void
1022 InitTimeControls ()
1023 {
1024     int matched, min, sec;
1025     /*
1026      * Parse timeControl resource
1027      */
1028     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029                           appData.movesPerSession)) {
1030         char buf[MSG_SIZ];
1031         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032         DisplayFatalError(buf, 0, 2);
1033     }
1034
1035     /*
1036      * Parse searchTime resource
1037      */
1038     if (*appData.searchTime != NULLCHAR) {
1039         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1040         if (matched == 1) {
1041             searchTime = min * 60;
1042         } else if (matched == 2) {
1043             searchTime = min * 60 + sec;
1044         } else {
1045             char buf[MSG_SIZ];
1046             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047             DisplayFatalError(buf, 0, 2);
1048         }
1049     }
1050 }
1051
1052 void
1053 InitBackEnd1 ()
1054 {
1055
1056     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1058
1059     GetTimeMark(&programStartTime);
1060     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061     appData.seedBase = random() + (random()<<15);
1062     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1063
1064     ClearProgramStats();
1065     programStats.ok_to_send = 1;
1066     programStats.seen_stat = 0;
1067
1068     /*
1069      * Initialize game list
1070      */
1071     ListNew(&gameList);
1072
1073
1074     /*
1075      * Internet chess server status
1076      */
1077     if (appData.icsActive) {
1078         appData.matchMode = FALSE;
1079         appData.matchGames = 0;
1080 #if ZIPPY
1081         appData.noChessProgram = !appData.zippyPlay;
1082 #else
1083         appData.zippyPlay = FALSE;
1084         appData.zippyTalk = FALSE;
1085         appData.noChessProgram = TRUE;
1086 #endif
1087         if (*appData.icsHelper != NULLCHAR) {
1088             appData.useTelnet = TRUE;
1089             appData.telnetProgram = appData.icsHelper;
1090         }
1091     } else {
1092         appData.zippyTalk = appData.zippyPlay = FALSE;
1093     }
1094
1095     /* [AS] Initialize pv info list [HGM] and game state */
1096     {
1097         int i, j;
1098
1099         for( i=0; i<=framePtr; i++ ) {
1100             pvInfoList[i].depth = -1;
1101             boards[i][EP_STATUS] = EP_NONE;
1102             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1103         }
1104     }
1105
1106     InitTimeControls();
1107
1108     /* [AS] Adjudication threshold */
1109     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1110
1111     InitEngine(&first, 0);
1112     InitEngine(&second, 1);
1113     CommonEngineInit();
1114
1115     pairing.which = "pairing"; // pairing engine
1116     pairing.pr = NoProc;
1117     pairing.isr = NULL;
1118     pairing.program = appData.pairingEngine;
1119     pairing.host = "localhost";
1120     pairing.dir = ".";
1121
1122     if (appData.icsActive) {
1123         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1124     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125         appData.clockMode = FALSE;
1126         first.sendTime = second.sendTime = 0;
1127     }
1128
1129 #if ZIPPY
1130     /* Override some settings from environment variables, for backward
1131        compatibility.  Unfortunately it's not feasible to have the env
1132        vars just set defaults, at least in xboard.  Ugh.
1133     */
1134     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1135       ZippyInit();
1136     }
1137 #endif
1138
1139     if (!appData.icsActive) {
1140       char buf[MSG_SIZ];
1141       int len;
1142
1143       /* Check for variants that are supported only in ICS mode,
1144          or not at all.  Some that are accepted here nevertheless
1145          have bugs; see comments below.
1146       */
1147       VariantClass variant = StringToVariant(appData.variant);
1148       switch (variant) {
1149       case VariantBughouse:     /* need four players and two boards */
1150       case VariantKriegspiel:   /* need to hide pieces and move details */
1151         /* case VariantFischeRandom: (Fabien: moved below) */
1152         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153         if( (len >= MSG_SIZ) && appData.debugMode )
1154           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1155
1156         DisplayFatalError(buf, 0, 2);
1157         return;
1158
1159       case VariantUnknown:
1160       case VariantLoadable:
1161       case Variant29:
1162       case Variant30:
1163       case Variant31:
1164       case Variant32:
1165       case Variant33:
1166       case Variant34:
1167       case Variant35:
1168       case Variant36:
1169       default:
1170         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171         if( (len >= MSG_SIZ) && appData.debugMode )
1172           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1173
1174         DisplayFatalError(buf, 0, 2);
1175         return;
1176
1177       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1178       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1179       case VariantGothic:     /* [HGM] should work */
1180       case VariantCapablanca: /* [HGM] should work */
1181       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1182       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1183       case VariantChu:        /* [HGM] experimental */
1184       case VariantKnightmate: /* [HGM] should work */
1185       case VariantCylinder:   /* [HGM] untested */
1186       case VariantFalcon:     /* [HGM] untested */
1187       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188                                  offboard interposition not understood */
1189       case VariantNormal:     /* definitely works! */
1190       case VariantWildCastle: /* pieces not automatically shuffled */
1191       case VariantNoCastle:   /* pieces not automatically shuffled */
1192       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193       case VariantLosers:     /* should work except for win condition,
1194                                  and doesn't know captures are mandatory */
1195       case VariantSuicide:    /* should work except for win condition,
1196                                  and doesn't know captures are mandatory */
1197       case VariantGiveaway:   /* should work except for win condition,
1198                                  and doesn't know captures are mandatory */
1199       case VariantTwoKings:   /* should work */
1200       case VariantAtomic:     /* should work except for win condition */
1201       case Variant3Check:     /* should work except for win condition */
1202       case VariantShatranj:   /* should work except for all win conditions */
1203       case VariantMakruk:     /* should work except for draw countdown */
1204       case VariantASEAN :     /* should work except for draw countdown */
1205       case VariantBerolina:   /* might work if TestLegality is off */
1206       case VariantCapaRandom: /* should work */
1207       case VariantJanus:      /* should work */
1208       case VariantSuper:      /* experimental */
1209       case VariantGreat:      /* experimental, requires legality testing to be off */
1210       case VariantSChess:     /* S-Chess, should work */
1211       case VariantGrand:      /* should work */
1212       case VariantSpartan:    /* should work */
1213       case VariantLion:       /* should work */
1214       case VariantChuChess:   /* should work */
1215         break;
1216       }
1217     }
1218
1219 }
1220
1221 int
1222 NextIntegerFromString (char ** str, long * value)
1223 {
1224     int result = -1;
1225     char * s = *str;
1226
1227     while( *s == ' ' || *s == '\t' ) {
1228         s++;
1229     }
1230
1231     *value = 0;
1232
1233     if( *s >= '0' && *s <= '9' ) {
1234         while( *s >= '0' && *s <= '9' ) {
1235             *value = *value * 10 + (*s - '0');
1236             s++;
1237         }
1238
1239         result = 0;
1240     }
1241
1242     *str = s;
1243
1244     return result;
1245 }
1246
1247 int
1248 NextTimeControlFromString (char ** str, long * value)
1249 {
1250     long temp;
1251     int result = NextIntegerFromString( str, &temp );
1252
1253     if( result == 0 ) {
1254         *value = temp * 60; /* Minutes */
1255         if( **str == ':' ) {
1256             (*str)++;
1257             result = NextIntegerFromString( str, &temp );
1258             *value += temp; /* Seconds */
1259         }
1260     }
1261
1262     return result;
1263 }
1264
1265 int
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268     int result = -1, type = 0; long temp, temp2;
1269
1270     if(**str != ':') return -1; // old params remain in force!
1271     (*str)++;
1272     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273     if( NextIntegerFromString( str, &temp ) ) return -1;
1274     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1275
1276     if(**str != '/') {
1277         /* time only: incremental or sudden-death time control */
1278         if(**str == '+') { /* increment follows; read it */
1279             (*str)++;
1280             if(**str == '!') type = *(*str)++; // Bronstein TC
1281             if(result = NextIntegerFromString( str, &temp2)) return -1;
1282             *inc = temp2 * 1000;
1283             if(**str == '.') { // read fraction of increment
1284                 char *start = ++(*str);
1285                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1286                 temp2 *= 1000;
1287                 while(start++ < *str) temp2 /= 10;
1288                 *inc += temp2;
1289             }
1290         } else *inc = 0;
1291         *moves = 0; *tc = temp * 1000; *incType = type;
1292         return 0;
1293     }
1294
1295     (*str)++; /* classical time control */
1296     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1297
1298     if(result == 0) {
1299         *moves = temp;
1300         *tc    = temp2 * 1000;
1301         *inc   = 0;
1302         *incType = type;
1303     }
1304     return result;
1305 }
1306
1307 int
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 {   /* [HGM] get time to add from the multi-session time-control string */
1310     int incType, moves=1; /* kludge to force reading of first session */
1311     long time, increment;
1312     char *s = tcString;
1313
1314     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1315     do {
1316         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318         if(movenr == -1) return time;    /* last move before new session     */
1319         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321         if(!moves) return increment;     /* current session is incremental   */
1322         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323     } while(movenr >= -1);               /* try again for next session       */
1324
1325     return 0; // no new time quota on this move
1326 }
1327
1328 int
1329 ParseTimeControl (char *tc, float ti, int mps)
1330 {
1331   long tc1;
1332   long tc2;
1333   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1334   int min, sec=0;
1335
1336   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1339   if(ti > 0) {
1340
1341     if(mps)
1342       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1343     else
1344       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1345   } else {
1346     if(mps)
1347       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1348     else
1349       snprintf(buf, MSG_SIZ, ":%s", mytc);
1350   }
1351   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1352
1353   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1354     return FALSE;
1355   }
1356
1357   if( *tc == '/' ) {
1358     /* Parse second time control */
1359     tc++;
1360
1361     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1362       return FALSE;
1363     }
1364
1365     if( tc2 == 0 ) {
1366       return FALSE;
1367     }
1368
1369     timeControl_2 = tc2 * 1000;
1370   }
1371   else {
1372     timeControl_2 = 0;
1373   }
1374
1375   if( tc1 == 0 ) {
1376     return FALSE;
1377   }
1378
1379   timeControl = tc1 * 1000;
1380
1381   if (ti >= 0) {
1382     timeIncrement = ti * 1000;  /* convert to ms */
1383     movesPerSession = 0;
1384   } else {
1385     timeIncrement = 0;
1386     movesPerSession = mps;
1387   }
1388   return TRUE;
1389 }
1390
1391 void
1392 InitBackEnd2 ()
1393 {
1394     if (appData.debugMode) {
1395 #    ifdef __GIT_VERSION
1396       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1397 #    else
1398       fprintf(debugFP, "Version: %s\n", programVersion);
1399 #    endif
1400     }
1401     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1402
1403     set_cont_sequence(appData.wrapContSeq);
1404     if (appData.matchGames > 0) {
1405         appData.matchMode = TRUE;
1406     } else if (appData.matchMode) {
1407         appData.matchGames = 1;
1408     }
1409     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410         appData.matchGames = appData.sameColorGames;
1411     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1414     }
1415     Reset(TRUE, FALSE);
1416     if (appData.noChessProgram || first.protocolVersion == 1) {
1417       InitBackEnd3();
1418     } else {
1419       /* kludge: allow timeout for initial "feature" commands */
1420       FreezeUI();
1421       DisplayMessage("", _("Starting chess program"));
1422       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1423     }
1424 }
1425
1426 int
1427 CalculateIndex (int index, int gameNr)
1428 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1429     int res;
1430     if(index > 0) return index; // fixed nmber
1431     if(index == 0) return 1;
1432     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1434     return res;
1435 }
1436
1437 int
1438 LoadGameOrPosition (int gameNr)
1439 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440     if (*appData.loadGameFile != NULLCHAR) {
1441         if (!LoadGameFromFile(appData.loadGameFile,
1442                 CalculateIndex(appData.loadGameIndex, gameNr),
1443                               appData.loadGameFile, FALSE)) {
1444             DisplayFatalError(_("Bad game file"), 0, 1);
1445             return 0;
1446         }
1447     } else if (*appData.loadPositionFile != NULLCHAR) {
1448         if (!LoadPositionFromFile(appData.loadPositionFile,
1449                 CalculateIndex(appData.loadPositionIndex, gameNr),
1450                                   appData.loadPositionFile)) {
1451             DisplayFatalError(_("Bad position file"), 0, 1);
1452             return 0;
1453         }
1454     }
1455     return 1;
1456 }
1457
1458 void
1459 ReserveGame (int gameNr, char resChar)
1460 {
1461     FILE *tf = fopen(appData.tourneyFile, "r+");
1462     char *p, *q, c, buf[MSG_SIZ];
1463     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464     safeStrCpy(buf, lastMsg, MSG_SIZ);
1465     DisplayMessage(_("Pick new game"), "");
1466     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467     ParseArgsFromFile(tf);
1468     p = q = appData.results;
1469     if(appData.debugMode) {
1470       char *r = appData.participants;
1471       fprintf(debugFP, "results = '%s'\n", p);
1472       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473       fprintf(debugFP, "\n");
1474     }
1475     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1476     nextGame = q - p;
1477     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478     safeStrCpy(q, p, strlen(p) + 2);
1479     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1483         q[nextGame] = '*';
1484     }
1485     fseek(tf, -(strlen(p)+4), SEEK_END);
1486     c = fgetc(tf);
1487     if(c != '"') // depending on DOS or Unix line endings we can be one off
1488          fseek(tf, -(strlen(p)+2), SEEK_END);
1489     else fseek(tf, -(strlen(p)+3), SEEK_END);
1490     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491     DisplayMessage(buf, "");
1492     free(p); appData.results = q;
1493     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495       int round = appData.defaultMatchGames * appData.tourneyType;
1496       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1497          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498         UnloadEngine(&first);  // next game belongs to other pairing;
1499         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1500     }
1501     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1502 }
1503
1504 void
1505 MatchEvent (int mode)
1506 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1507         int dummy;
1508         if(matchMode) { // already in match mode: switch it off
1509             abortMatch = TRUE;
1510             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1511             return;
1512         }
1513 //      if(gameMode != BeginningOfGame) {
1514 //          DisplayError(_("You can only start a match from the initial position."), 0);
1515 //          return;
1516 //      }
1517         abortMatch = FALSE;
1518         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519         /* Set up machine vs. machine match */
1520         nextGame = 0;
1521         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522         if(appData.tourneyFile[0]) {
1523             ReserveGame(-1, 0);
1524             if(nextGame > appData.matchGames) {
1525                 char buf[MSG_SIZ];
1526                 if(strchr(appData.results, '*') == NULL) {
1527                     FILE *f;
1528                     appData.tourneyCycles++;
1529                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1530                         fclose(f);
1531                         NextTourneyGame(-1, &dummy);
1532                         ReserveGame(-1, 0);
1533                         if(nextGame <= appData.matchGames) {
1534                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1535                             matchMode = mode;
1536                             ScheduleDelayedEvent(NextMatchGame, 10000);
1537                             return;
1538                         }
1539                     }
1540                 }
1541                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542                 DisplayError(buf, 0);
1543                 appData.tourneyFile[0] = 0;
1544                 return;
1545             }
1546         } else
1547         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1548             DisplayFatalError(_("Can't have a match with no chess programs"),
1549                               0, 2);
1550             return;
1551         }
1552         matchMode = mode;
1553         matchGame = roundNr = 1;
1554         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1555         NextMatchGame();
1556 }
1557
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1559
1560 void
1561 InitBackEnd3 P((void))
1562 {
1563     GameMode initialMode;
1564     char buf[MSG_SIZ];
1565     int err, len;
1566
1567     InitChessProgram(&first, startedFromSetupPosition);
1568
1569     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1570         free(programVersion);
1571         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1572         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1573         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1574     }
1575
1576     if (appData.icsActive) {
1577 #ifdef WIN32
1578         /* [DM] Make a console window if needed [HGM] merged ifs */
1579         ConsoleCreate();
1580 #endif
1581         err = establish();
1582         if (err != 0)
1583           {
1584             if (*appData.icsCommPort != NULLCHAR)
1585               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1586                              appData.icsCommPort);
1587             else
1588               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1589                         appData.icsHost, appData.icsPort);
1590
1591             if( (len >= MSG_SIZ) && appData.debugMode )
1592               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1593
1594             DisplayFatalError(buf, err, 1);
1595             return;
1596         }
1597         SetICSMode();
1598         telnetISR =
1599           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1600         fromUserISR =
1601           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1602         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1603             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1604     } else if (appData.noChessProgram) {
1605         SetNCPMode();
1606     } else {
1607         SetGNUMode();
1608     }
1609
1610     if (*appData.cmailGameName != NULLCHAR) {
1611         SetCmailMode();
1612         OpenLoopback(&cmailPR);
1613         cmailISR =
1614           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1615     }
1616
1617     ThawUI();
1618     DisplayMessage("", "");
1619     if (StrCaseCmp(appData.initialMode, "") == 0) {
1620       initialMode = BeginningOfGame;
1621       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1622         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1623         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1624         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1625         ModeHighlight();
1626       }
1627     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1628       initialMode = TwoMachinesPlay;
1629     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1630       initialMode = AnalyzeFile;
1631     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1632       initialMode = AnalyzeMode;
1633     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1634       initialMode = MachinePlaysWhite;
1635     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1636       initialMode = MachinePlaysBlack;
1637     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1638       initialMode = EditGame;
1639     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1640       initialMode = EditPosition;
1641     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1642       initialMode = Training;
1643     } else {
1644       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1645       if( (len >= MSG_SIZ) && appData.debugMode )
1646         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648       DisplayFatalError(buf, 0, 2);
1649       return;
1650     }
1651
1652     if (appData.matchMode) {
1653         if(appData.tourneyFile[0]) { // start tourney from command line
1654             FILE *f;
1655             if(f = fopen(appData.tourneyFile, "r")) {
1656                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1657                 fclose(f);
1658                 appData.clockMode = TRUE;
1659                 SetGNUMode();
1660             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1661         }
1662         MatchEvent(TRUE);
1663     } else if (*appData.cmailGameName != NULLCHAR) {
1664         /* Set up cmail mode */
1665         ReloadCmailMsgEvent(TRUE);
1666     } else {
1667         /* Set up other modes */
1668         if (initialMode == AnalyzeFile) {
1669           if (*appData.loadGameFile == NULLCHAR) {
1670             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1671             return;
1672           }
1673         }
1674         if (*appData.loadGameFile != NULLCHAR) {
1675             (void) LoadGameFromFile(appData.loadGameFile,
1676                                     appData.loadGameIndex,
1677                                     appData.loadGameFile, TRUE);
1678         } else if (*appData.loadPositionFile != NULLCHAR) {
1679             (void) LoadPositionFromFile(appData.loadPositionFile,
1680                                         appData.loadPositionIndex,
1681                                         appData.loadPositionFile);
1682             /* [HGM] try to make self-starting even after FEN load */
1683             /* to allow automatic setup of fairy variants with wtm */
1684             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1685                 gameMode = BeginningOfGame;
1686                 setboardSpoiledMachineBlack = 1;
1687             }
1688             /* [HGM] loadPos: make that every new game uses the setup */
1689             /* from file as long as we do not switch variant          */
1690             if(!blackPlaysFirst) {
1691                 startedFromPositionFile = TRUE;
1692                 CopyBoard(filePosition, boards[0]);
1693             }
1694         }
1695         if (initialMode == AnalyzeMode) {
1696           if (appData.noChessProgram) {
1697             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1698             return;
1699           }
1700           if (appData.icsActive) {
1701             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1702             return;
1703           }
1704           AnalyzeModeEvent();
1705         } else if (initialMode == AnalyzeFile) {
1706           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1707           ShowThinkingEvent();
1708           AnalyzeFileEvent();
1709           AnalysisPeriodicEvent(1);
1710         } else if (initialMode == MachinePlaysWhite) {
1711           if (appData.noChessProgram) {
1712             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1713                               0, 2);
1714             return;
1715           }
1716           if (appData.icsActive) {
1717             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1718                               0, 2);
1719             return;
1720           }
1721           MachineWhiteEvent();
1722         } else if (initialMode == MachinePlaysBlack) {
1723           if (appData.noChessProgram) {
1724             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1725                               0, 2);
1726             return;
1727           }
1728           if (appData.icsActive) {
1729             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1730                               0, 2);
1731             return;
1732           }
1733           MachineBlackEvent();
1734         } else if (initialMode == TwoMachinesPlay) {
1735           if (appData.noChessProgram) {
1736             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1737                               0, 2);
1738             return;
1739           }
1740           if (appData.icsActive) {
1741             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1742                               0, 2);
1743             return;
1744           }
1745           TwoMachinesEvent();
1746         } else if (initialMode == EditGame) {
1747           EditGameEvent();
1748         } else if (initialMode == EditPosition) {
1749           EditPositionEvent();
1750         } else if (initialMode == Training) {
1751           if (*appData.loadGameFile == NULLCHAR) {
1752             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1753             return;
1754           }
1755           TrainingEvent();
1756         }
1757     }
1758 }
1759
1760 void
1761 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1762 {
1763     DisplayBook(current+1);
1764
1765     MoveHistorySet( movelist, first, last, current, pvInfoList );
1766
1767     EvalGraphSet( first, last, current, pvInfoList );
1768
1769     MakeEngineOutputTitle();
1770 }
1771
1772 /*
1773  * Establish will establish a contact to a remote host.port.
1774  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1775  *  used to talk to the host.
1776  * Returns 0 if okay, error code if not.
1777  */
1778 int
1779 establish ()
1780 {
1781     char buf[MSG_SIZ];
1782
1783     if (*appData.icsCommPort != NULLCHAR) {
1784         /* Talk to the host through a serial comm port */
1785         return OpenCommPort(appData.icsCommPort, &icsPR);
1786
1787     } else if (*appData.gateway != NULLCHAR) {
1788         if (*appData.remoteShell == NULLCHAR) {
1789             /* Use the rcmd protocol to run telnet program on a gateway host */
1790             snprintf(buf, sizeof(buf), "%s %s %s",
1791                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1792             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1793
1794         } else {
1795             /* Use the rsh program to run telnet program on a gateway host */
1796             if (*appData.remoteUser == NULLCHAR) {
1797                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1798                         appData.gateway, appData.telnetProgram,
1799                         appData.icsHost, appData.icsPort);
1800             } else {
1801                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1802                         appData.remoteShell, appData.gateway,
1803                         appData.remoteUser, appData.telnetProgram,
1804                         appData.icsHost, appData.icsPort);
1805             }
1806             return StartChildProcess(buf, "", &icsPR);
1807
1808         }
1809     } else if (appData.useTelnet) {
1810         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1811
1812     } else {
1813         /* TCP socket interface differs somewhat between
1814            Unix and NT; handle details in the front end.
1815            */
1816         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1817     }
1818 }
1819
1820 void
1821 EscapeExpand (char *p, char *q)
1822 {       // [HGM] initstring: routine to shape up string arguments
1823         while(*p++ = *q++) if(p[-1] == '\\')
1824             switch(*q++) {
1825                 case 'n': p[-1] = '\n'; break;
1826                 case 'r': p[-1] = '\r'; break;
1827                 case 't': p[-1] = '\t'; break;
1828                 case '\\': p[-1] = '\\'; break;
1829                 case 0: *p = 0; return;
1830                 default: p[-1] = q[-1]; break;
1831             }
1832 }
1833
1834 void
1835 show_bytes (FILE *fp, char *buf, int count)
1836 {
1837     while (count--) {
1838         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1839             fprintf(fp, "\\%03o", *buf & 0xff);
1840         } else {
1841             putc(*buf, fp);
1842         }
1843         buf++;
1844     }
1845     fflush(fp);
1846 }
1847
1848 /* Returns an errno value */
1849 int
1850 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1851 {
1852     char buf[8192], *p, *q, *buflim;
1853     int left, newcount, outcount;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1856         *appData.gateway != NULLCHAR) {
1857         if (appData.debugMode) {
1858             fprintf(debugFP, ">ICS: ");
1859             show_bytes(debugFP, message, count);
1860             fprintf(debugFP, "\n");
1861         }
1862         return OutputToProcess(pr, message, count, outError);
1863     }
1864
1865     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1866     p = message;
1867     q = buf;
1868     left = count;
1869     newcount = 0;
1870     while (left) {
1871         if (q >= buflim) {
1872             if (appData.debugMode) {
1873                 fprintf(debugFP, ">ICS: ");
1874                 show_bytes(debugFP, buf, newcount);
1875                 fprintf(debugFP, "\n");
1876             }
1877             outcount = OutputToProcess(pr, buf, newcount, outError);
1878             if (outcount < newcount) return -1; /* to be sure */
1879             q = buf;
1880             newcount = 0;
1881         }
1882         if (*p == '\n') {
1883             *q++ = '\r';
1884             newcount++;
1885         } else if (((unsigned char) *p) == TN_IAC) {
1886             *q++ = (char) TN_IAC;
1887             newcount ++;
1888         }
1889         *q++ = *p++;
1890         newcount++;
1891         left--;
1892     }
1893     if (appData.debugMode) {
1894         fprintf(debugFP, ">ICS: ");
1895         show_bytes(debugFP, buf, newcount);
1896         fprintf(debugFP, "\n");
1897     }
1898     outcount = OutputToProcess(pr, buf, newcount, outError);
1899     if (outcount < newcount) return -1; /* to be sure */
1900     return count;
1901 }
1902
1903 void
1904 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1905 {
1906     int outError, outCount;
1907     static int gotEof = 0;
1908     static FILE *ini;
1909
1910     /* Pass data read from player on to ICS */
1911     if (count > 0) {
1912         gotEof = 0;
1913         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1914         if (outCount < count) {
1915             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916         }
1917         if(have_sent_ICS_logon == 2) {
1918           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1919             fprintf(ini, "%s", message);
1920             have_sent_ICS_logon = 3;
1921           } else
1922             have_sent_ICS_logon = 1;
1923         } else if(have_sent_ICS_logon == 3) {
1924             fprintf(ini, "%s", message);
1925             fclose(ini);
1926           have_sent_ICS_logon = 1;
1927         }
1928     } else if (count < 0) {
1929         RemoveInputSource(isr);
1930         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1931     } else if (gotEof++ > 0) {
1932         RemoveInputSource(isr);
1933         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1934     }
1935 }
1936
1937 void
1938 KeepAlive ()
1939 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1940     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1941     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1942     SendToICS("date\n");
1943     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1944 }
1945
1946 /* added routine for printf style output to ics */
1947 void
1948 ics_printf (char *format, ...)
1949 {
1950     char buffer[MSG_SIZ];
1951     va_list args;
1952
1953     va_start(args, format);
1954     vsnprintf(buffer, sizeof(buffer), format, args);
1955     buffer[sizeof(buffer)-1] = '\0';
1956     SendToICS(buffer);
1957     va_end(args);
1958 }
1959
1960 void
1961 SendToICS (char *s)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1969     if (outCount < count) {
1970         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1971     }
1972 }
1973
1974 /* This is used for sending logon scripts to the ICS. Sending
1975    without a delay causes problems when using timestamp on ICC
1976    (at least on my machine). */
1977 void
1978 SendToICSDelayed (char *s, long msdelay)
1979 {
1980     int count, outCount, outError;
1981
1982     if (icsPR == NoProc) return;
1983
1984     count = strlen(s);
1985     if (appData.debugMode) {
1986         fprintf(debugFP, ">ICS: ");
1987         show_bytes(debugFP, s, count);
1988         fprintf(debugFP, "\n");
1989     }
1990     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1991                                       msdelay);
1992     if (outCount < count) {
1993         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1994     }
1995 }
1996
1997
1998 /* Remove all highlighting escape sequences in s
1999    Also deletes any suffix starting with '('
2000    */
2001 char *
2002 StripHighlightAndTitle (char *s)
2003 {
2004     static char retbuf[MSG_SIZ];
2005     char *p = retbuf;
2006
2007     while (*s != NULLCHAR) {
2008         while (*s == '\033') {
2009             while (*s != NULLCHAR && !isalpha(*s)) s++;
2010             if (*s != NULLCHAR) s++;
2011         }
2012         while (*s != NULLCHAR && *s != '\033') {
2013             if (*s == '(' || *s == '[') {
2014                 *p = NULLCHAR;
2015                 return retbuf;
2016             }
2017             *p++ = *s++;
2018         }
2019     }
2020     *p = NULLCHAR;
2021     return retbuf;
2022 }
2023
2024 /* Remove all highlighting escape sequences in s */
2025 char *
2026 StripHighlight (char *s)
2027 {
2028     static char retbuf[MSG_SIZ];
2029     char *p = retbuf;
2030
2031     while (*s != NULLCHAR) {
2032         while (*s == '\033') {
2033             while (*s != NULLCHAR && !isalpha(*s)) s++;
2034             if (*s != NULLCHAR) s++;
2035         }
2036         while (*s != NULLCHAR && *s != '\033') {
2037             *p++ = *s++;
2038         }
2039     }
2040     *p = NULLCHAR;
2041     return retbuf;
2042 }
2043
2044 char engineVariant[MSG_SIZ];
2045 char *variantNames[] = VARIANT_NAMES;
2046 char *
2047 VariantName (VariantClass v)
2048 {
2049     if(v == VariantUnknown || *engineVariant) return engineVariant;
2050     return variantNames[v];
2051 }
2052
2053
2054 /* Identify a variant from the strings the chess servers use or the
2055    PGN Variant tag names we use. */
2056 VariantClass
2057 StringToVariant (char *e)
2058 {
2059     char *p;
2060     int wnum = -1;
2061     VariantClass v = VariantNormal;
2062     int i, found = FALSE;
2063     char buf[MSG_SIZ];
2064     int len;
2065
2066     if (!e) return v;
2067
2068     /* [HGM] skip over optional board-size prefixes */
2069     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2070         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2071         while( *e++ != '_');
2072     }
2073
2074     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2075         v = VariantNormal;
2076         found = TRUE;
2077     } else
2078     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2079       if (p = StrCaseStr(e, variantNames[i])) {
2080         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2081         v = (VariantClass) i;
2082         found = TRUE;
2083         break;
2084       }
2085     }
2086
2087     if (!found) {
2088       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2089           || StrCaseStr(e, "wild/fr")
2090           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2091         v = VariantFischeRandom;
2092       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2093                  (i = 1, p = StrCaseStr(e, "w"))) {
2094         p += i;
2095         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2096         if (isdigit(*p)) {
2097           wnum = atoi(p);
2098         } else {
2099           wnum = -1;
2100         }
2101         switch (wnum) {
2102         case 0: /* FICS only, actually */
2103         case 1:
2104           /* Castling legal even if K starts on d-file */
2105           v = VariantWildCastle;
2106           break;
2107         case 2:
2108         case 3:
2109         case 4:
2110           /* Castling illegal even if K & R happen to start in
2111              normal positions. */
2112           v = VariantNoCastle;
2113           break;
2114         case 5:
2115         case 7:
2116         case 8:
2117         case 10:
2118         case 11:
2119         case 12:
2120         case 13:
2121         case 14:
2122         case 15:
2123         case 18:
2124         case 19:
2125           /* Castling legal iff K & R start in normal positions */
2126           v = VariantNormal;
2127           break;
2128         case 6:
2129         case 20:
2130         case 21:
2131           /* Special wilds for position setup; unclear what to do here */
2132           v = VariantLoadable;
2133           break;
2134         case 9:
2135           /* Bizarre ICC game */
2136           v = VariantTwoKings;
2137           break;
2138         case 16:
2139           v = VariantKriegspiel;
2140           break;
2141         case 17:
2142           v = VariantLosers;
2143           break;
2144         case 22:
2145           v = VariantFischeRandom;
2146           break;
2147         case 23:
2148           v = VariantCrazyhouse;
2149           break;
2150         case 24:
2151           v = VariantBughouse;
2152           break;
2153         case 25:
2154           v = Variant3Check;
2155           break;
2156         case 26:
2157           /* Not quite the same as FICS suicide! */
2158           v = VariantGiveaway;
2159           break;
2160         case 27:
2161           v = VariantAtomic;
2162           break;
2163         case 28:
2164           v = VariantShatranj;
2165           break;
2166
2167         /* Temporary names for future ICC types.  The name *will* change in
2168            the next xboard/WinBoard release after ICC defines it. */
2169         case 29:
2170           v = Variant29;
2171           break;
2172         case 30:
2173           v = Variant30;
2174           break;
2175         case 31:
2176           v = Variant31;
2177           break;
2178         case 32:
2179           v = Variant32;
2180           break;
2181         case 33:
2182           v = Variant33;
2183           break;
2184         case 34:
2185           v = Variant34;
2186           break;
2187         case 35:
2188           v = Variant35;
2189           break;
2190         case 36:
2191           v = Variant36;
2192           break;
2193         case 37:
2194           v = VariantShogi;
2195           break;
2196         case 38:
2197           v = VariantXiangqi;
2198           break;
2199         case 39:
2200           v = VariantCourier;
2201           break;
2202         case 40:
2203           v = VariantGothic;
2204           break;
2205         case 41:
2206           v = VariantCapablanca;
2207           break;
2208         case 42:
2209           v = VariantKnightmate;
2210           break;
2211         case 43:
2212           v = VariantFairy;
2213           break;
2214         case 44:
2215           v = VariantCylinder;
2216           break;
2217         case 45:
2218           v = VariantFalcon;
2219           break;
2220         case 46:
2221           v = VariantCapaRandom;
2222           break;
2223         case 47:
2224           v = VariantBerolina;
2225           break;
2226         case 48:
2227           v = VariantJanus;
2228           break;
2229         case 49:
2230           v = VariantSuper;
2231           break;
2232         case 50:
2233           v = VariantGreat;
2234           break;
2235         case -1:
2236           /* Found "wild" or "w" in the string but no number;
2237              must assume it's normal chess. */
2238           v = VariantNormal;
2239           break;
2240         default:
2241           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2242           if( (len >= MSG_SIZ) && appData.debugMode )
2243             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2244
2245           DisplayError(buf, 0);
2246           v = VariantUnknown;
2247           break;
2248         }
2249       }
2250     }
2251     if (appData.debugMode) {
2252       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2253               e, wnum, VariantName(v));
2254     }
2255     return v;
2256 }
2257
2258 static int leftover_start = 0, leftover_len = 0;
2259 char star_match[STAR_MATCH_N][MSG_SIZ];
2260
2261 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2262    advance *index beyond it, and set leftover_start to the new value of
2263    *index; else return FALSE.  If pattern contains the character '*', it
2264    matches any sequence of characters not containing '\r', '\n', or the
2265    character following the '*' (if any), and the matched sequence(s) are
2266    copied into star_match.
2267    */
2268 int
2269 looking_at ( char *buf, int *index, char *pattern)
2270 {
2271     char *bufp = &buf[*index], *patternp = pattern;
2272     int star_count = 0;
2273     char *matchp = star_match[0];
2274
2275     for (;;) {
2276         if (*patternp == NULLCHAR) {
2277             *index = leftover_start = bufp - buf;
2278             *matchp = NULLCHAR;
2279             return TRUE;
2280         }
2281         if (*bufp == NULLCHAR) return FALSE;
2282         if (*patternp == '*') {
2283             if (*bufp == *(patternp + 1)) {
2284                 *matchp = NULLCHAR;
2285                 matchp = star_match[++star_count];
2286                 patternp += 2;
2287                 bufp++;
2288                 continue;
2289             } else if (*bufp == '\n' || *bufp == '\r') {
2290                 patternp++;
2291                 if (*patternp == NULLCHAR)
2292                   continue;
2293                 else
2294                   return FALSE;
2295             } else {
2296                 *matchp++ = *bufp++;
2297                 continue;
2298             }
2299         }
2300         if (*patternp != *bufp) return FALSE;
2301         patternp++;
2302         bufp++;
2303     }
2304 }
2305
2306 void
2307 SendToPlayer (char *data, int length)
2308 {
2309     int error, outCount;
2310     outCount = OutputToProcess(NoProc, data, length, &error);
2311     if (outCount < length) {
2312         DisplayFatalError(_("Error writing to display"), error, 1);
2313     }
2314 }
2315
2316 void
2317 PackHolding (char packed[], char *holding)
2318 {
2319     char *p = holding;
2320     char *q = packed;
2321     int runlength = 0;
2322     int curr = 9999;
2323     do {
2324         if (*p == curr) {
2325             runlength++;
2326         } else {
2327             switch (runlength) {
2328               case 0:
2329                 break;
2330               case 1:
2331                 *q++ = curr;
2332                 break;
2333               case 2:
2334                 *q++ = curr;
2335                 *q++ = curr;
2336                 break;
2337               default:
2338                 sprintf(q, "%d", runlength);
2339                 while (*q) q++;
2340                 *q++ = curr;
2341                 break;
2342             }
2343             runlength = 1;
2344             curr = *p;
2345         }
2346     } while (*p++);
2347     *q = NULLCHAR;
2348 }
2349
2350 /* Telnet protocol requests from the front end */
2351 void
2352 TelnetRequest (unsigned char ddww, unsigned char option)
2353 {
2354     unsigned char msg[3];
2355     int outCount, outError;
2356
2357     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2358
2359     if (appData.debugMode) {
2360         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2361         switch (ddww) {
2362           case TN_DO:
2363             ddwwStr = "DO";
2364             break;
2365           case TN_DONT:
2366             ddwwStr = "DONT";
2367             break;
2368           case TN_WILL:
2369             ddwwStr = "WILL";
2370             break;
2371           case TN_WONT:
2372             ddwwStr = "WONT";
2373             break;
2374           default:
2375             ddwwStr = buf1;
2376             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2377             break;
2378         }
2379         switch (option) {
2380           case TN_ECHO:
2381             optionStr = "ECHO";
2382             break;
2383           default:
2384             optionStr = buf2;
2385             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2386             break;
2387         }
2388         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2389     }
2390     msg[0] = TN_IAC;
2391     msg[1] = ddww;
2392     msg[2] = option;
2393     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2394     if (outCount < 3) {
2395         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2396     }
2397 }
2398
2399 void
2400 DoEcho ()
2401 {
2402     if (!appData.icsActive) return;
2403     TelnetRequest(TN_DO, TN_ECHO);
2404 }
2405
2406 void
2407 DontEcho ()
2408 {
2409     if (!appData.icsActive) return;
2410     TelnetRequest(TN_DONT, TN_ECHO);
2411 }
2412
2413 void
2414 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2415 {
2416     /* put the holdings sent to us by the server on the board holdings area */
2417     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2418     char p;
2419     ChessSquare piece;
2420
2421     if(gameInfo.holdingsWidth < 2)  return;
2422     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2423         return; // prevent overwriting by pre-board holdings
2424
2425     if( (int)lowestPiece >= BlackPawn ) {
2426         holdingsColumn = 0;
2427         countsColumn = 1;
2428         holdingsStartRow = BOARD_HEIGHT-1;
2429         direction = -1;
2430     } else {
2431         holdingsColumn = BOARD_WIDTH-1;
2432         countsColumn = BOARD_WIDTH-2;
2433         holdingsStartRow = 0;
2434         direction = 1;
2435     }
2436
2437     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2438         board[i][holdingsColumn] = EmptySquare;
2439         board[i][countsColumn]   = (ChessSquare) 0;
2440     }
2441     while( (p=*holdings++) != NULLCHAR ) {
2442         piece = CharToPiece( ToUpper(p) );
2443         if(piece == EmptySquare) continue;
2444         /*j = (int) piece - (int) WhitePawn;*/
2445         j = PieceToNumber(piece);
2446         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2447         if(j < 0) continue;               /* should not happen */
2448         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2449         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2450         board[holdingsStartRow+j*direction][countsColumn]++;
2451     }
2452 }
2453
2454
2455 void
2456 VariantSwitch (Board board, VariantClass newVariant)
2457 {
2458    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2459    static Board oldBoard;
2460
2461    startedFromPositionFile = FALSE;
2462    if(gameInfo.variant == newVariant) return;
2463
2464    /* [HGM] This routine is called each time an assignment is made to
2465     * gameInfo.variant during a game, to make sure the board sizes
2466     * are set to match the new variant. If that means adding or deleting
2467     * holdings, we shift the playing board accordingly
2468     * This kludge is needed because in ICS observe mode, we get boards
2469     * of an ongoing game without knowing the variant, and learn about the
2470     * latter only later. This can be because of the move list we requested,
2471     * in which case the game history is refilled from the beginning anyway,
2472     * but also when receiving holdings of a crazyhouse game. In the latter
2473     * case we want to add those holdings to the already received position.
2474     */
2475
2476
2477    if (appData.debugMode) {
2478      fprintf(debugFP, "Switch board from %s to %s\n",
2479              VariantName(gameInfo.variant), VariantName(newVariant));
2480      setbuf(debugFP, NULL);
2481    }
2482    shuffleOpenings = 0;       /* [HGM] shuffle */
2483    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2484    switch(newVariant)
2485      {
2486      case VariantShogi:
2487        newWidth = 9;  newHeight = 9;
2488        gameInfo.holdingsSize = 7;
2489      case VariantBughouse:
2490      case VariantCrazyhouse:
2491        newHoldingsWidth = 2; break;
2492      case VariantGreat:
2493        newWidth = 10;
2494      case VariantSuper:
2495        newHoldingsWidth = 2;
2496        gameInfo.holdingsSize = 8;
2497        break;
2498      case VariantGothic:
2499      case VariantCapablanca:
2500      case VariantCapaRandom:
2501        newWidth = 10;
2502      default:
2503        newHoldingsWidth = gameInfo.holdingsSize = 0;
2504      };
2505
2506    if(newWidth  != gameInfo.boardWidth  ||
2507       newHeight != gameInfo.boardHeight ||
2508       newHoldingsWidth != gameInfo.holdingsWidth ) {
2509
2510      /* shift position to new playing area, if needed */
2511      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2512        for(i=0; i<BOARD_HEIGHT; i++)
2513          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2514            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2515              board[i][j];
2516        for(i=0; i<newHeight; i++) {
2517          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2518          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2519        }
2520      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2521        for(i=0; i<BOARD_HEIGHT; i++)
2522          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2523            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2524              board[i][j];
2525      }
2526      board[HOLDINGS_SET] = 0;
2527      gameInfo.boardWidth  = newWidth;
2528      gameInfo.boardHeight = newHeight;
2529      gameInfo.holdingsWidth = newHoldingsWidth;
2530      gameInfo.variant = newVariant;
2531      InitDrawingSizes(-2, 0);
2532    } else gameInfo.variant = newVariant;
2533    CopyBoard(oldBoard, board);   // remember correctly formatted board
2534      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2535    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2536 }
2537
2538 static int loggedOn = FALSE;
2539
2540 /*-- Game start info cache: --*/
2541 int gs_gamenum;
2542 char gs_kind[MSG_SIZ];
2543 static char player1Name[128] = "";
2544 static char player2Name[128] = "";
2545 static char cont_seq[] = "\n\\   ";
2546 static int player1Rating = -1;
2547 static int player2Rating = -1;
2548 /*----------------------------*/
2549
2550 ColorClass curColor = ColorNormal;
2551 int suppressKibitz = 0;
2552
2553 // [HGM] seekgraph
2554 Boolean soughtPending = FALSE;
2555 Boolean seekGraphUp;
2556 #define MAX_SEEK_ADS 200
2557 #define SQUARE 0x80
2558 char *seekAdList[MAX_SEEK_ADS];
2559 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2560 float tcList[MAX_SEEK_ADS];
2561 char colorList[MAX_SEEK_ADS];
2562 int nrOfSeekAds = 0;
2563 int minRating = 1010, maxRating = 2800;
2564 int hMargin = 10, vMargin = 20, h, w;
2565 extern int squareSize, lineGap;
2566
2567 void
2568 PlotSeekAd (int i)
2569 {
2570         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2571         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2572         if(r < minRating+100 && r >=0 ) r = minRating+100;
2573         if(r > maxRating) r = maxRating;
2574         if(tc < 1.f) tc = 1.f;
2575         if(tc > 95.f) tc = 95.f;
2576         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2577         y = ((double)r - minRating)/(maxRating - minRating)
2578             * (h-vMargin-squareSize/8-1) + vMargin;
2579         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2580         if(strstr(seekAdList[i], " u ")) color = 1;
2581         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2582            !strstr(seekAdList[i], "bullet") &&
2583            !strstr(seekAdList[i], "blitz") &&
2584            !strstr(seekAdList[i], "standard") ) color = 2;
2585         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2586         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2587 }
2588
2589 void
2590 PlotSingleSeekAd (int i)
2591 {
2592         PlotSeekAd(i);
2593 }
2594
2595 void
2596 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2597 {
2598         char buf[MSG_SIZ], *ext = "";
2599         VariantClass v = StringToVariant(type);
2600         if(strstr(type, "wild")) {
2601             ext = type + 4; // append wild number
2602             if(v == VariantFischeRandom) type = "chess960"; else
2603             if(v == VariantLoadable) type = "setup"; else
2604             type = VariantName(v);
2605         }
2606         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2607         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2608             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2609             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2610             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2611             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2612             seekNrList[nrOfSeekAds] = nr;
2613             zList[nrOfSeekAds] = 0;
2614             seekAdList[nrOfSeekAds++] = StrSave(buf);
2615             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2616         }
2617 }
2618
2619 void
2620 EraseSeekDot (int i)
2621 {
2622     int x = xList[i], y = yList[i], d=squareSize/4, k;
2623     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2624     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2625     // now replot every dot that overlapped
2626     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2627         int xx = xList[k], yy = yList[k];
2628         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2629             DrawSeekDot(xx, yy, colorList[k]);
2630     }
2631 }
2632
2633 void
2634 RemoveSeekAd (int nr)
2635 {
2636         int i;
2637         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2638             EraseSeekDot(i);
2639             if(seekAdList[i]) free(seekAdList[i]);
2640             seekAdList[i] = seekAdList[--nrOfSeekAds];
2641             seekNrList[i] = seekNrList[nrOfSeekAds];
2642             ratingList[i] = ratingList[nrOfSeekAds];
2643             colorList[i]  = colorList[nrOfSeekAds];
2644             tcList[i] = tcList[nrOfSeekAds];
2645             xList[i]  = xList[nrOfSeekAds];
2646             yList[i]  = yList[nrOfSeekAds];
2647             zList[i]  = zList[nrOfSeekAds];
2648             seekAdList[nrOfSeekAds] = NULL;
2649             break;
2650         }
2651 }
2652
2653 Boolean
2654 MatchSoughtLine (char *line)
2655 {
2656     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2657     int nr, base, inc, u=0; char dummy;
2658
2659     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2660        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2661        (u=1) &&
2662        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2663         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2664         // match: compact and save the line
2665         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2666         return TRUE;
2667     }
2668     return FALSE;
2669 }
2670
2671 int
2672 DrawSeekGraph ()
2673 {
2674     int i;
2675     if(!seekGraphUp) return FALSE;
2676     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2677     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2678
2679     DrawSeekBackground(0, 0, w, h);
2680     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2681     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2682     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2683         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2684         yy = h-1-yy;
2685         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2686         if(i%500 == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2690         }
2691     }
2692     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2693     for(i=1; i<100; i+=(i<10?1:5)) {
2694         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2695         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2696         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2697             char buf[MSG_SIZ];
2698             snprintf(buf, MSG_SIZ, "%d", i);
2699             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2700         }
2701     }
2702     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2703     return TRUE;
2704 }
2705
2706 int
2707 SeekGraphClick (ClickType click, int x, int y, int moving)
2708 {
2709     static int lastDown = 0, displayed = 0, lastSecond;
2710     if(y < 0) return FALSE;
2711     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2712         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2713         if(!seekGraphUp) return FALSE;
2714         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2715         DrawPosition(TRUE, NULL);
2716         return TRUE;
2717     }
2718     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2719         if(click == Release || moving) return FALSE;
2720         nrOfSeekAds = 0;
2721         soughtPending = TRUE;
2722         SendToICS(ics_prefix);
2723         SendToICS("sought\n"); // should this be "sought all"?
2724     } else { // issue challenge based on clicked ad
2725         int dist = 10000; int i, closest = 0, second = 0;
2726         for(i=0; i<nrOfSeekAds; i++) {
2727             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2728             if(d < dist) { dist = d; closest = i; }
2729             second += (d - zList[i] < 120); // count in-range ads
2730             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2731         }
2732         if(dist < 120) {
2733             char buf[MSG_SIZ];
2734             second = (second > 1);
2735             if(displayed != closest || second != lastSecond) {
2736                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2737                 lastSecond = second; displayed = closest;
2738             }
2739             if(click == Press) {
2740                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2741                 lastDown = closest;
2742                 return TRUE;
2743             } // on press 'hit', only show info
2744             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2745             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2746             SendToICS(ics_prefix);
2747             SendToICS(buf);
2748             return TRUE; // let incoming board of started game pop down the graph
2749         } else if(click == Release) { // release 'miss' is ignored
2750             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2751             if(moving == 2) { // right up-click
2752                 nrOfSeekAds = 0; // refresh graph
2753                 soughtPending = TRUE;
2754                 SendToICS(ics_prefix);
2755                 SendToICS("sought\n"); // should this be "sought all"?
2756             }
2757             return TRUE;
2758         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2759         // press miss or release hit 'pop down' seek graph
2760         seekGraphUp = FALSE;
2761         DrawPosition(TRUE, NULL);
2762     }
2763     return TRUE;
2764 }
2765
2766 void
2767 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2768 {
2769 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2770 #define STARTED_NONE 0
2771 #define STARTED_MOVES 1
2772 #define STARTED_BOARD 2
2773 #define STARTED_OBSERVE 3
2774 #define STARTED_HOLDINGS 4
2775 #define STARTED_CHATTER 5
2776 #define STARTED_COMMENT 6
2777 #define STARTED_MOVES_NOHIDE 7
2778
2779     static int started = STARTED_NONE;
2780     static char parse[20000];
2781     static int parse_pos = 0;
2782     static char buf[BUF_SIZE + 1];
2783     static int firstTime = TRUE, intfSet = FALSE;
2784     static ColorClass prevColor = ColorNormal;
2785     static int savingComment = FALSE;
2786     static int cmatch = 0; // continuation sequence match
2787     char *bp;
2788     char str[MSG_SIZ];
2789     int i, oldi;
2790     int buf_len;
2791     int next_out;
2792     int tkind;
2793     int backup;    /* [DM] For zippy color lines */
2794     char *p;
2795     char talker[MSG_SIZ]; // [HGM] chat
2796     int channel;
2797
2798     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2799
2800     if (appData.debugMode) {
2801       if (!error) {
2802         fprintf(debugFP, "<ICS: ");
2803         show_bytes(debugFP, data, count);
2804         fprintf(debugFP, "\n");
2805       }
2806     }
2807
2808     if (appData.debugMode) { int f = forwardMostMove;
2809         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2810                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2811                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2812     }
2813     if (count > 0) {
2814         /* If last read ended with a partial line that we couldn't parse,
2815            prepend it to the new read and try again. */
2816         if (leftover_len > 0) {
2817             for (i=0; i<leftover_len; i++)
2818               buf[i] = buf[leftover_start + i];
2819         }
2820
2821     /* copy new characters into the buffer */
2822     bp = buf + leftover_len;
2823     buf_len=leftover_len;
2824     for (i=0; i<count; i++)
2825     {
2826         // ignore these
2827         if (data[i] == '\r')
2828             continue;
2829
2830         // join lines split by ICS?
2831         if (!appData.noJoin)
2832         {
2833             /*
2834                 Joining just consists of finding matches against the
2835                 continuation sequence, and discarding that sequence
2836                 if found instead of copying it.  So, until a match
2837                 fails, there's nothing to do since it might be the
2838                 complete sequence, and thus, something we don't want
2839                 copied.
2840             */
2841             if (data[i] == cont_seq[cmatch])
2842             {
2843                 cmatch++;
2844                 if (cmatch == strlen(cont_seq))
2845                 {
2846                     cmatch = 0; // complete match.  just reset the counter
2847
2848                     /*
2849                         it's possible for the ICS to not include the space
2850                         at the end of the last word, making our [correct]
2851                         join operation fuse two separate words.  the server
2852                         does this when the space occurs at the width setting.
2853                     */
2854                     if (!buf_len || buf[buf_len-1] != ' ')
2855                     {
2856                         *bp++ = ' ';
2857                         buf_len++;
2858                     }
2859                 }
2860                 continue;
2861             }
2862             else if (cmatch)
2863             {
2864                 /*
2865                     match failed, so we have to copy what matched before
2866                     falling through and copying this character.  In reality,
2867                     this will only ever be just the newline character, but
2868                     it doesn't hurt to be precise.
2869                 */
2870                 strncpy(bp, cont_seq, cmatch);
2871                 bp += cmatch;
2872                 buf_len += cmatch;
2873                 cmatch = 0;
2874             }
2875         }
2876
2877         // copy this char
2878         *bp++ = data[i];
2879         buf_len++;
2880     }
2881
2882         buf[buf_len] = NULLCHAR;
2883 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2884         next_out = 0;
2885         leftover_start = 0;
2886
2887         i = 0;
2888         while (i < buf_len) {
2889             /* Deal with part of the TELNET option negotiation
2890                protocol.  We refuse to do anything beyond the
2891                defaults, except that we allow the WILL ECHO option,
2892                which ICS uses to turn off password echoing when we are
2893                directly connected to it.  We reject this option
2894                if localLineEditing mode is on (always on in xboard)
2895                and we are talking to port 23, which might be a real
2896                telnet server that will try to keep WILL ECHO on permanently.
2897              */
2898             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2899                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2900                 unsigned char option;
2901                 oldi = i;
2902                 switch ((unsigned char) buf[++i]) {
2903                   case TN_WILL:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<WILL ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       case TN_ECHO:
2908                         if (appData.debugMode)
2909                           fprintf(debugFP, "ECHO ");
2910                         /* Reply only if this is a change, according
2911                            to the protocol rules. */
2912                         if (remoteEchoOption) break;
2913                         if (appData.localLineEditing &&
2914                             atoi(appData.icsPort) == TN_PORT) {
2915                             TelnetRequest(TN_DONT, TN_ECHO);
2916                         } else {
2917                             EchoOff();
2918                             TelnetRequest(TN_DO, TN_ECHO);
2919                             remoteEchoOption = TRUE;
2920                         }
2921                         break;
2922                       default:
2923                         if (appData.debugMode)
2924                           fprintf(debugFP, "%d ", option);
2925                         /* Whatever this is, we don't want it. */
2926                         TelnetRequest(TN_DONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_WONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<WONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       case TN_ECHO:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "ECHO ");
2937                         /* Reply only if this is a change, according
2938                            to the protocol rules. */
2939                         if (!remoteEchoOption) break;
2940                         EchoOn();
2941                         TelnetRequest(TN_DONT, TN_ECHO);
2942                         remoteEchoOption = FALSE;
2943                         break;
2944                       default:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "%d ", (unsigned char) option);
2947                         /* Whatever this is, it must already be turned
2948                            off, because we never agree to turn on
2949                            anything non-default, so according to the
2950                            protocol rules, we don't reply. */
2951                         break;
2952                     }
2953                     break;
2954                   case TN_DO:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<DO ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       default:
2959                         /* Whatever this is, we refuse to do it. */
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         TelnetRequest(TN_WONT, option);
2963                         break;
2964                     }
2965                     break;
2966                   case TN_DONT:
2967                     if (appData.debugMode)
2968                       fprintf(debugFP, "\n<DONT ");
2969                     switch (option = (unsigned char) buf[++i]) {
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we are already not doing
2974                            it, because we never agree to do anything
2975                            non-default, so according to the protocol
2976                            rules, we don't reply. */
2977                         break;
2978                     }
2979                     break;
2980                   case TN_IAC:
2981                     if (appData.debugMode)
2982                       fprintf(debugFP, "\n<IAC ");
2983                     /* Doubled IAC; pass it through */
2984                     i--;
2985                     break;
2986                   default:
2987                     if (appData.debugMode)
2988                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2989                     /* Drop all other telnet commands on the floor */
2990                     break;
2991                 }
2992                 if (oldi > next_out)
2993                   SendToPlayer(&buf[next_out], oldi - next_out);
2994                 if (++i > next_out)
2995                   next_out = i;
2996                 continue;
2997             }
2998
2999             /* OK, this at least will *usually* work */
3000             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3001                 loggedOn = TRUE;
3002             }
3003
3004             if (loggedOn && !intfSet) {
3005                 if (ics_type == ICS_ICC) {
3006                   snprintf(str, MSG_SIZ,
3007                           "/set-quietly interface %s\n/set-quietly style 12\n",
3008                           programVersion);
3009                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3010                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3011                 } else if (ics_type == ICS_CHESSNET) {
3012                   snprintf(str, MSG_SIZ, "/style 12\n");
3013                 } else {
3014                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3015                   strcat(str, programVersion);
3016                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3017                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3018                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3019 #ifdef WIN32
3020                   strcat(str, "$iset nohighlight 1\n");
3021 #endif
3022                   strcat(str, "$iset lock 1\n$style 12\n");
3023                 }
3024                 SendToICS(str);
3025                 NotifyFrontendLogin();
3026                 intfSet = TRUE;
3027             }
3028
3029             if (started == STARTED_COMMENT) {
3030                 /* Accumulate characters in comment */
3031                 parse[parse_pos++] = buf[i];
3032                 if (buf[i] == '\n') {
3033                     parse[parse_pos] = NULLCHAR;
3034                     if(chattingPartner>=0) {
3035                         char mess[MSG_SIZ];
3036                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3037                         OutputChatMessage(chattingPartner, mess);
3038                         chattingPartner = -1;
3039                         next_out = i+1; // [HGM] suppress printing in ICS window
3040                     } else
3041                     if(!suppressKibitz) // [HGM] kibitz
3042                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3043                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3044                         int nrDigit = 0, nrAlph = 0, j;
3045                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3046                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3047                         parse[parse_pos] = NULLCHAR;
3048                         // try to be smart: if it does not look like search info, it should go to
3049                         // ICS interaction window after all, not to engine-output window.
3050                         for(j=0; j<parse_pos; j++) { // count letters and digits
3051                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3052                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3053                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3054                         }
3055                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3056                             int depth=0; float score;
3057                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3058                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3059                                 pvInfoList[forwardMostMove-1].depth = depth;
3060                                 pvInfoList[forwardMostMove-1].score = 100*score;
3061                             }
3062                             OutputKibitz(suppressKibitz, parse);
3063                         } else {
3064                             char tmp[MSG_SIZ];
3065                             if(gameMode == IcsObserving) // restore original ICS messages
3066                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3067                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3068                             else
3069                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3070                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3071                             SendToPlayer(tmp, strlen(tmp));
3072                         }
3073                         next_out = i+1; // [HGM] suppress printing in ICS window
3074                     }
3075                     started = STARTED_NONE;
3076                 } else {
3077                     /* Don't match patterns against characters in comment */
3078                     i++;
3079                     continue;
3080                 }
3081             }
3082             if (started == STARTED_CHATTER) {
3083                 if (buf[i] != '\n') {
3084                     /* Don't match patterns against characters in chatter */
3085                     i++;
3086                     continue;
3087                 }
3088                 started = STARTED_NONE;
3089                 if(suppressKibitz) next_out = i+1;
3090             }
3091
3092             /* Kludge to deal with rcmd protocol */
3093             if (firstTime && looking_at(buf, &i, "\001*")) {
3094                 DisplayFatalError(&buf[1], 0, 1);
3095                 continue;
3096             } else {
3097                 firstTime = FALSE;
3098             }
3099
3100             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3101                 ics_type = ICS_ICC;
3102                 ics_prefix = "/";
3103                 if (appData.debugMode)
3104                   fprintf(debugFP, "ics_type %d\n", ics_type);
3105                 continue;
3106             }
3107             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3108                 ics_type = ICS_FICS;
3109                 ics_prefix = "$";
3110                 if (appData.debugMode)
3111                   fprintf(debugFP, "ics_type %d\n", ics_type);
3112                 continue;
3113             }
3114             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3115                 ics_type = ICS_CHESSNET;
3116                 ics_prefix = "/";
3117                 if (appData.debugMode)
3118                   fprintf(debugFP, "ics_type %d\n", ics_type);
3119                 continue;
3120             }
3121
3122             if (!loggedOn &&
3123                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3124                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3125                  looking_at(buf, &i, "will be \"*\""))) {
3126               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3127               continue;
3128             }
3129
3130             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3131               char buf[MSG_SIZ];
3132               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3133               DisplayIcsInteractionTitle(buf);
3134               have_set_title = TRUE;
3135             }
3136
3137             /* skip finger notes */
3138             if (started == STARTED_NONE &&
3139                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3140                  (buf[i] == '1' && buf[i+1] == '0')) &&
3141                 buf[i+2] == ':' && buf[i+3] == ' ') {
3142               started = STARTED_CHATTER;
3143               i += 3;
3144               continue;
3145             }
3146
3147             oldi = i;
3148             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3149             if(appData.seekGraph) {
3150                 if(soughtPending && MatchSoughtLine(buf+i)) {
3151                     i = strstr(buf+i, "rated") - buf;
3152                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153                     next_out = leftover_start = i;
3154                     started = STARTED_CHATTER;
3155                     suppressKibitz = TRUE;
3156                     continue;
3157                 }
3158                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3159                         && looking_at(buf, &i, "* ads displayed")) {
3160                     soughtPending = FALSE;
3161                     seekGraphUp = TRUE;
3162                     DrawSeekGraph();
3163                     continue;
3164                 }
3165                 if(appData.autoRefresh) {
3166                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3167                         int s = (ics_type == ICS_ICC); // ICC format differs
3168                         if(seekGraphUp)
3169                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3170                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3171                         looking_at(buf, &i, "*% "); // eat prompt
3172                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3173                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174                         next_out = i; // suppress
3175                         continue;
3176                     }
3177                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3178                         char *p = star_match[0];
3179                         while(*p) {
3180                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3181                             while(*p && *p++ != ' '); // next
3182                         }
3183                         looking_at(buf, &i, "*% "); // eat prompt
3184                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3185                         next_out = i;
3186                         continue;
3187                     }
3188                 }
3189             }
3190
3191             /* skip formula vars */
3192             if (started == STARTED_NONE &&
3193                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3194               started = STARTED_CHATTER;
3195               i += 3;
3196               continue;
3197             }
3198
3199             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3200             if (appData.autoKibitz && started == STARTED_NONE &&
3201                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3202                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3203                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3204                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3205                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3206                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3207                         suppressKibitz = TRUE;
3208                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3209                         next_out = i;
3210                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3211                                 && (gameMode == IcsPlayingWhite)) ||
3212                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3213                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3214                             started = STARTED_CHATTER; // own kibitz we simply discard
3215                         else {
3216                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3217                             parse_pos = 0; parse[0] = NULLCHAR;
3218                             savingComment = TRUE;
3219                             suppressKibitz = gameMode != IcsObserving ? 2 :
3220                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3221                         }
3222                         continue;
3223                 } else
3224                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3225                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3226                          && atoi(star_match[0])) {
3227                     // suppress the acknowledgements of our own autoKibitz
3228                     char *p;
3229                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3231                     SendToPlayer(star_match[0], strlen(star_match[0]));
3232                     if(looking_at(buf, &i, "*% ")) // eat prompt
3233                         suppressKibitz = FALSE;
3234                     next_out = i;
3235                     continue;
3236                 }
3237             } // [HGM] kibitz: end of patch
3238
3239             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3240
3241             // [HGM] chat: intercept tells by users for which we have an open chat window
3242             channel = -1;
3243             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3244                                            looking_at(buf, &i, "* whispers:") ||
3245                                            looking_at(buf, &i, "* kibitzes:") ||
3246                                            looking_at(buf, &i, "* shouts:") ||
3247                                            looking_at(buf, &i, "* c-shouts:") ||
3248                                            looking_at(buf, &i, "--> * ") ||
3249                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3250                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3251                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3252                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3253                 int p;
3254                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3255                 chattingPartner = -1;
3256
3257                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3258                 for(p=0; p<MAX_CHAT; p++) {
3259                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3260                     talker[0] = '['; strcat(talker, "] ");
3261                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3262                     chattingPartner = p; break;
3263                     }
3264                 } else
3265                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3266                 for(p=0; p<MAX_CHAT; p++) {
3267                     if(!strcmp("kibitzes", chatPartner[p])) {
3268                         talker[0] = '['; strcat(talker, "] ");
3269                         chattingPartner = p; break;
3270                     }
3271                 } else
3272                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3273                 for(p=0; p<MAX_CHAT; p++) {
3274                     if(!strcmp("whispers", chatPartner[p])) {
3275                         talker[0] = '['; strcat(talker, "] ");
3276                         chattingPartner = p; break;
3277                     }
3278                 } else
3279                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3280                   if(buf[i-8] == '-' && buf[i-3] == 't')
3281                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3282                     if(!strcmp("c-shouts", chatPartner[p])) {
3283                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3284                         chattingPartner = p; break;
3285                     }
3286                   }
3287                   if(chattingPartner < 0)
3288                   for(p=0; p<MAX_CHAT; p++) {
3289                     if(!strcmp("shouts", chatPartner[p])) {
3290                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3291                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3292                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3293                         chattingPartner = p; break;
3294                     }
3295                   }
3296                 }
3297                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3298                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3299                     talker[0] = 0; Colorize(ColorTell, FALSE);
3300                     chattingPartner = p; break;
3301                 }
3302                 if(chattingPartner<0) i = oldi; else {
3303                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3304                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3305                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3306                     started = STARTED_COMMENT;
3307                     parse_pos = 0; parse[0] = NULLCHAR;
3308                     savingComment = 3 + chattingPartner; // counts as TRUE
3309                     suppressKibitz = TRUE;
3310                     continue;
3311                 }
3312             } // [HGM] chat: end of patch
3313
3314           backup = i;
3315             if (appData.zippyTalk || appData.zippyPlay) {
3316                 /* [DM] Backup address for color zippy lines */
3317 #if ZIPPY
3318                if (loggedOn == TRUE)
3319                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3320                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3321 #endif
3322             } // [DM] 'else { ' deleted
3323                 if (
3324                     /* Regular tells and says */
3325                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3326                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3327                     looking_at(buf, &i, "* says: ") ||
3328                     /* Don't color "message" or "messages" output */
3329                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3330                     looking_at(buf, &i, "*. * at *:*: ") ||
3331                     looking_at(buf, &i, "--* (*:*): ") ||
3332                     /* Message notifications (same color as tells) */
3333                     looking_at(buf, &i, "* has left a message ") ||
3334                     looking_at(buf, &i, "* just sent you a message:\n") ||
3335                     /* Whispers and kibitzes */
3336                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3337                     looking_at(buf, &i, "* kibitzes: ") ||
3338                     /* Channel tells */
3339                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3340
3341                   if (tkind == 1 && strchr(star_match[0], ':')) {
3342                       /* Avoid "tells you:" spoofs in channels */
3343                      tkind = 3;
3344                   }
3345                   if (star_match[0][0] == NULLCHAR ||
3346                       strchr(star_match[0], ' ') ||
3347                       (tkind == 3 && strchr(star_match[1], ' '))) {
3348                     /* Reject bogus matches */
3349                     i = oldi;
3350                   } else {
3351                     if (appData.colorize) {
3352                       if (oldi > next_out) {
3353                         SendToPlayer(&buf[next_out], oldi - next_out);
3354                         next_out = oldi;
3355                       }
3356                       switch (tkind) {
3357                       case 1:
3358                         Colorize(ColorTell, FALSE);
3359                         curColor = ColorTell;
3360                         break;
3361                       case 2:
3362                         Colorize(ColorKibitz, FALSE);
3363                         curColor = ColorKibitz;
3364                         break;
3365                       case 3:
3366                         p = strrchr(star_match[1], '(');
3367                         if (p == NULL) {
3368                           p = star_match[1];
3369                         } else {
3370                           p++;
3371                         }
3372                         if (atoi(p) == 1) {
3373                           Colorize(ColorChannel1, FALSE);
3374                           curColor = ColorChannel1;
3375                         } else {
3376                           Colorize(ColorChannel, FALSE);
3377                           curColor = ColorChannel;
3378                         }
3379                         break;
3380                       case 5:
3381                         curColor = ColorNormal;
3382                         break;
3383                       }
3384                     }
3385                     if (started == STARTED_NONE && appData.autoComment &&
3386                         (gameMode == IcsObserving ||
3387                          gameMode == IcsPlayingWhite ||
3388                          gameMode == IcsPlayingBlack)) {
3389                       parse_pos = i - oldi;
3390                       memcpy(parse, &buf[oldi], parse_pos);
3391                       parse[parse_pos] = NULLCHAR;
3392                       started = STARTED_COMMENT;
3393                       savingComment = TRUE;
3394                     } else {
3395                       started = STARTED_CHATTER;
3396                       savingComment = FALSE;
3397                     }
3398                     loggedOn = TRUE;
3399                     continue;
3400                   }
3401                 }
3402
3403                 if (looking_at(buf, &i, "* s-shouts: ") ||
3404                     looking_at(buf, &i, "* c-shouts: ")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorSShout, FALSE);
3411                         curColor = ColorSShout;
3412                     }
3413                     loggedOn = TRUE;
3414                     started = STARTED_CHATTER;
3415                     continue;
3416                 }
3417
3418                 if (looking_at(buf, &i, "--->")) {
3419                     loggedOn = TRUE;
3420                     continue;
3421                 }
3422
3423                 if (looking_at(buf, &i, "* shouts: ") ||
3424                     looking_at(buf, &i, "--> ")) {
3425                     if (appData.colorize) {
3426                         if (oldi > next_out) {
3427                             SendToPlayer(&buf[next_out], oldi - next_out);
3428                             next_out = oldi;
3429                         }
3430                         Colorize(ColorShout, FALSE);
3431                         curColor = ColorShout;
3432                     }
3433                     loggedOn = TRUE;
3434                     started = STARTED_CHATTER;
3435                     continue;
3436                 }
3437
3438                 if (looking_at( buf, &i, "Challenge:")) {
3439                     if (appData.colorize) {
3440                         if (oldi > next_out) {
3441                             SendToPlayer(&buf[next_out], oldi - next_out);
3442                             next_out = oldi;
3443                         }
3444                         Colorize(ColorChallenge, FALSE);
3445                         curColor = ColorChallenge;
3446                     }
3447                     loggedOn = TRUE;
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* offers you") ||
3452                     looking_at(buf, &i, "* offers to be") ||
3453                     looking_at(buf, &i, "* would like to") ||
3454                     looking_at(buf, &i, "* requests to") ||
3455                     looking_at(buf, &i, "Your opponent offers") ||
3456                     looking_at(buf, &i, "Your opponent requests")) {
3457
3458                     if (appData.colorize) {
3459                         if (oldi > next_out) {
3460                             SendToPlayer(&buf[next_out], oldi - next_out);
3461                             next_out = oldi;
3462                         }
3463                         Colorize(ColorRequest, FALSE);
3464                         curColor = ColorRequest;
3465                     }
3466                     continue;
3467                 }
3468
3469                 if (looking_at(buf, &i, "* (*) seeking")) {
3470                     if (appData.colorize) {
3471                         if (oldi > next_out) {
3472                             SendToPlayer(&buf[next_out], oldi - next_out);
3473                             next_out = oldi;
3474                         }
3475                         Colorize(ColorSeek, FALSE);
3476                         curColor = ColorSeek;
3477                     }
3478                     continue;
3479             }
3480
3481           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3482
3483             if (looking_at(buf, &i, "\\   ")) {
3484                 if (prevColor != ColorNormal) {
3485                     if (oldi > next_out) {
3486                         SendToPlayer(&buf[next_out], oldi - next_out);
3487                         next_out = oldi;
3488                     }
3489                     Colorize(prevColor, TRUE);
3490                     curColor = prevColor;
3491                 }
3492                 if (savingComment) {
3493                     parse_pos = i - oldi;
3494                     memcpy(parse, &buf[oldi], parse_pos);
3495                     parse[parse_pos] = NULLCHAR;
3496                     started = STARTED_COMMENT;
3497                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3498                         chattingPartner = savingComment - 3; // kludge to remember the box
3499                 } else {
3500                     started = STARTED_CHATTER;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "Black Strength :") ||
3506                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3507                 looking_at(buf, &i, "<10>") ||
3508                 looking_at(buf, &i, "#@#")) {
3509                 /* Wrong board style */
3510                 loggedOn = TRUE;
3511                 SendToICS(ics_prefix);
3512                 SendToICS("set style 12\n");
3513                 SendToICS(ics_prefix);
3514                 SendToICS("refresh\n");
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "login:")) {
3519               if (!have_sent_ICS_logon) {
3520                 if(ICSInitScript())
3521                   have_sent_ICS_logon = 1;
3522                 else // no init script was found
3523                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3524               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3525                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3526               }
3527                 continue;
3528             }
3529
3530             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3531                 (looking_at(buf, &i, "\n<12> ") ||
3532                  looking_at(buf, &i, "<12> "))) {
3533                 loggedOn = TRUE;
3534                 if (oldi > next_out) {
3535                     SendToPlayer(&buf[next_out], oldi - next_out);
3536                 }
3537                 next_out = i;
3538                 started = STARTED_BOARD;
3539                 parse_pos = 0;
3540                 continue;
3541             }
3542
3543             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3544                 looking_at(buf, &i, "<b1> ")) {
3545                 if (oldi > next_out) {
3546                     SendToPlayer(&buf[next_out], oldi - next_out);
3547                 }
3548                 next_out = i;
3549                 started = STARTED_HOLDINGS;
3550                 parse_pos = 0;
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3555                 loggedOn = TRUE;
3556                 /* Header for a move list -- first line */
3557
3558                 switch (ics_getting_history) {
3559                   case H_FALSE:
3560                     switch (gameMode) {
3561                       case IcsIdle:
3562                       case BeginningOfGame:
3563                         /* User typed "moves" or "oldmoves" while we
3564                            were idle.  Pretend we asked for these
3565                            moves and soak them up so user can step
3566                            through them and/or save them.
3567                            */
3568                         Reset(FALSE, TRUE);
3569                         gameMode = IcsObserving;
3570                         ModeHighlight();
3571                         ics_gamenum = -1;
3572                         ics_getting_history = H_GOT_UNREQ_HEADER;
3573                         break;
3574                       case EditGame: /*?*/
3575                       case EditPosition: /*?*/
3576                         /* Should above feature work in these modes too? */
3577                         /* For now it doesn't */
3578                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3579                         break;
3580                       default:
3581                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3582                         break;
3583                     }
3584                     break;
3585                   case H_REQUESTED:
3586                     /* Is this the right one? */
3587                     if (gameInfo.white && gameInfo.black &&
3588                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3589                         strcmp(gameInfo.black, star_match[2]) == 0) {
3590                         /* All is well */
3591                         ics_getting_history = H_GOT_REQ_HEADER;
3592                     }
3593                     break;
3594                   case H_GOT_REQ_HEADER:
3595                   case H_GOT_UNREQ_HEADER:
3596                   case H_GOT_UNWANTED_HEADER:
3597                   case H_GETTING_MOVES:
3598                     /* Should not happen */
3599                     DisplayError(_("Error gathering move list: two headers"), 0);
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603
3604                 /* Save player ratings into gameInfo if needed */
3605                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3606                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3607                     (gameInfo.whiteRating == -1 ||
3608                      gameInfo.blackRating == -1)) {
3609
3610                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3611                     gameInfo.blackRating = string_to_rating(star_match[3]);
3612                     if (appData.debugMode)
3613                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3614                               gameInfo.whiteRating, gameInfo.blackRating);
3615                 }
3616                 continue;
3617             }
3618
3619             if (looking_at(buf, &i,
3620               "* * match, initial time: * minute*, increment: * second")) {
3621                 /* Header for a move list -- second line */
3622                 /* Initial board will follow if this is a wild game */
3623                 if (gameInfo.event != NULL) free(gameInfo.event);
3624                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3625                 gameInfo.event = StrSave(str);
3626                 /* [HGM] we switched variant. Translate boards if needed. */
3627                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "Move  ")) {
3632                 /* Beginning of a move list */
3633                 switch (ics_getting_history) {
3634                   case H_FALSE:
3635                     /* Normally should not happen */
3636                     /* Maybe user hit reset while we were parsing */
3637                     break;
3638                   case H_REQUESTED:
3639                     /* Happens if we are ignoring a move list that is not
3640                      * the one we just requested.  Common if the user
3641                      * tries to observe two games without turning off
3642                      * getMoveList */
3643                     break;
3644                   case H_GETTING_MOVES:
3645                     /* Should not happen */
3646                     DisplayError(_("Error gathering move list: nested"), 0);
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649                   case H_GOT_REQ_HEADER:
3650                     ics_getting_history = H_GETTING_MOVES;
3651                     started = STARTED_MOVES;
3652                     parse_pos = 0;
3653                     if (oldi > next_out) {
3654                         SendToPlayer(&buf[next_out], oldi - next_out);
3655                     }
3656                     break;
3657                   case H_GOT_UNREQ_HEADER:
3658                     ics_getting_history = H_GETTING_MOVES;
3659                     started = STARTED_MOVES_NOHIDE;
3660                     parse_pos = 0;
3661                     break;
3662                   case H_GOT_UNWANTED_HEADER:
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666                 continue;
3667             }
3668
3669             if (looking_at(buf, &i, "% ") ||
3670                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3671                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3672                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3673                     soughtPending = FALSE;
3674                     seekGraphUp = TRUE;
3675                     DrawSeekGraph();
3676                 }
3677                 if(suppressKibitz) next_out = i;
3678                 savingComment = FALSE;
3679                 suppressKibitz = 0;
3680                 switch (started) {
3681                   case STARTED_MOVES:
3682                   case STARTED_MOVES_NOHIDE:
3683                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3684                     parse[parse_pos + i - oldi] = NULLCHAR;
3685                     ParseGameHistory(parse);
3686 #if ZIPPY
3687                     if (appData.zippyPlay && first.initDone) {
3688                         FeedMovesToProgram(&first, forwardMostMove);
3689                         if (gameMode == IcsPlayingWhite) {
3690                             if (WhiteOnMove(forwardMostMove)) {
3691                                 if (first.sendTime) {
3692                                   if (first.useColors) {
3693                                     SendToProgram("black\n", &first);
3694                                   }
3695                                   SendTimeRemaining(&first, TRUE);
3696                                 }
3697                                 if (first.useColors) {
3698                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3699                                 }
3700                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3701                                 first.maybeThinking = TRUE;
3702                             } else {
3703                                 if (first.usePlayother) {
3704                                   if (first.sendTime) {
3705                                     SendTimeRemaining(&first, TRUE);
3706                                   }
3707                                   SendToProgram("playother\n", &first);
3708                                   firstMove = FALSE;
3709                                 } else {
3710                                   firstMove = TRUE;
3711                                 }
3712                             }
3713                         } else if (gameMode == IcsPlayingBlack) {
3714                             if (!WhiteOnMove(forwardMostMove)) {
3715                                 if (first.sendTime) {
3716                                   if (first.useColors) {
3717                                     SendToProgram("white\n", &first);
3718                                   }
3719                                   SendTimeRemaining(&first, FALSE);
3720                                 }
3721                                 if (first.useColors) {
3722                                   SendToProgram("black\n", &first);
3723                                 }
3724                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3725                                 first.maybeThinking = TRUE;
3726                             } else {
3727                                 if (first.usePlayother) {
3728                                   if (first.sendTime) {
3729                                     SendTimeRemaining(&first, FALSE);
3730                                   }
3731                                   SendToProgram("playother\n", &first);
3732                                   firstMove = FALSE;
3733                                 } else {
3734                                   firstMove = TRUE;
3735                                 }
3736                             }
3737                         }
3738                     }
3739 #endif
3740                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3741                         /* Moves came from oldmoves or moves command
3742                            while we weren't doing anything else.
3743                            */
3744                         currentMove = forwardMostMove;
3745                         ClearHighlights();/*!!could figure this out*/
3746                         flipView = appData.flipView;
3747                         DrawPosition(TRUE, boards[currentMove]);
3748                         DisplayBothClocks();
3749                         snprintf(str, MSG_SIZ, "%s %s %s",
3750                                 gameInfo.white, _("vs."),  gameInfo.black);
3751                         DisplayTitle(str);
3752                         gameMode = IcsIdle;
3753                     } else {
3754                         /* Moves were history of an active game */
3755                         if (gameInfo.resultDetails != NULL) {
3756                             free(gameInfo.resultDetails);
3757                             gameInfo.resultDetails = NULL;
3758                         }
3759                     }
3760                     HistorySet(parseList, backwardMostMove,
3761                                forwardMostMove, currentMove-1);
3762                     DisplayMove(currentMove - 1);
3763                     if (started == STARTED_MOVES) next_out = i;
3764                     started = STARTED_NONE;
3765                     ics_getting_history = H_FALSE;
3766                     break;
3767
3768                   case STARTED_OBSERVE:
3769                     started = STARTED_NONE;
3770                     SendToICS(ics_prefix);
3771                     SendToICS("refresh\n");
3772                     break;
3773
3774                   default:
3775                     break;
3776                 }
3777                 if(bookHit) { // [HGM] book: simulate book reply
3778                     static char bookMove[MSG_SIZ]; // a bit generous?
3779
3780                     programStats.nodes = programStats.depth = programStats.time =
3781                     programStats.score = programStats.got_only_move = 0;
3782                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3783
3784                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3785                     strcat(bookMove, bookHit);
3786                     HandleMachineMove(bookMove, &first);
3787                 }
3788                 continue;
3789             }
3790
3791             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3792                  started == STARTED_HOLDINGS ||
3793                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3794                 /* Accumulate characters in move list or board */
3795                 parse[parse_pos++] = buf[i];
3796             }
3797
3798             /* Start of game messages.  Mostly we detect start of game
3799                when the first board image arrives.  On some versions
3800                of the ICS, though, we need to do a "refresh" after starting
3801                to observe in order to get the current board right away. */
3802             if (looking_at(buf, &i, "Adding game * to observation list")) {
3803                 started = STARTED_OBSERVE;
3804                 continue;
3805             }
3806
3807             /* Handle auto-observe */
3808             if (appData.autoObserve &&
3809                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3810                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3811                 char *player;
3812                 /* Choose the player that was highlighted, if any. */
3813                 if (star_match[0][0] == '\033' ||
3814                     star_match[1][0] != '\033') {
3815                     player = star_match[0];
3816                 } else {
3817                     player = star_match[2];
3818                 }
3819                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3820                         ics_prefix, StripHighlightAndTitle(player));
3821                 SendToICS(str);
3822
3823                 /* Save ratings from notify string */
3824                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3825                 player1Rating = string_to_rating(star_match[1]);
3826                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3827                 player2Rating = string_to_rating(star_match[3]);
3828
3829                 if (appData.debugMode)
3830                   fprintf(debugFP,
3831                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3832                           player1Name, player1Rating,
3833                           player2Name, player2Rating);
3834
3835                 continue;
3836             }
3837
3838             /* Deal with automatic examine mode after a game,
3839                and with IcsObserving -> IcsExamining transition */
3840             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3841                 looking_at(buf, &i, "has made you an examiner of game *")) {
3842
3843                 int gamenum = atoi(star_match[0]);
3844                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3845                     gamenum == ics_gamenum) {
3846                     /* We were already playing or observing this game;
3847                        no need to refetch history */
3848                     gameMode = IcsExamining;
3849                     if (pausing) {
3850                         pauseExamForwardMostMove = forwardMostMove;
3851                     } else if (currentMove < forwardMostMove) {
3852                         ForwardInner(forwardMostMove);
3853                     }
3854                 } else {
3855                     /* I don't think this case really can happen */
3856                     SendToICS(ics_prefix);
3857                     SendToICS("refresh\n");
3858                 }
3859                 continue;
3860             }
3861
3862             /* Error messages */
3863 //          if (ics_user_moved) {
3864             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3865                 if (looking_at(buf, &i, "Illegal move") ||
3866                     looking_at(buf, &i, "Not a legal move") ||
3867                     looking_at(buf, &i, "Your king is in check") ||
3868                     looking_at(buf, &i, "It isn't your turn") ||
3869                     looking_at(buf, &i, "It is not your move")) {
3870                     /* Illegal move */
3871                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3872                         currentMove = forwardMostMove-1;
3873                         DisplayMove(currentMove - 1); /* before DMError */
3874                         DrawPosition(FALSE, boards[currentMove]);
3875                         SwitchClocks(forwardMostMove-1); // [HGM] race
3876                         DisplayBothClocks();
3877                     }
3878                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3879                     ics_user_moved = 0;
3880                     continue;
3881                 }
3882             }
3883
3884             if (looking_at(buf, &i, "still have time") ||
3885                 looking_at(buf, &i, "not out of time") ||
3886                 looking_at(buf, &i, "either player is out of time") ||
3887                 looking_at(buf, &i, "has timeseal; checking")) {
3888                 /* We must have called his flag a little too soon */
3889                 whiteFlag = blackFlag = FALSE;
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "added * seconds to") ||
3894                 looking_at(buf, &i, "seconds were added to")) {
3895                 /* Update the clocks */
3896                 SendToICS(ics_prefix);
3897                 SendToICS("refresh\n");
3898                 continue;
3899             }
3900
3901             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3902                 ics_clock_paused = TRUE;
3903                 StopClocks();
3904                 continue;
3905             }
3906
3907             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3908                 ics_clock_paused = FALSE;
3909                 StartClocks();
3910                 continue;
3911             }
3912
3913             /* Grab player ratings from the Creating: message.
3914                Note we have to check for the special case when
3915                the ICS inserts things like [white] or [black]. */
3916             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3917                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3918                 /* star_matches:
3919                    0    player 1 name (not necessarily white)
3920                    1    player 1 rating
3921                    2    empty, white, or black (IGNORED)
3922                    3    player 2 name (not necessarily black)
3923                    4    player 2 rating
3924
3925                    The names/ratings are sorted out when the game
3926                    actually starts (below).
3927                 */
3928                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3929                 player1Rating = string_to_rating(star_match[1]);
3930                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3931                 player2Rating = string_to_rating(star_match[4]);
3932
3933                 if (appData.debugMode)
3934                   fprintf(debugFP,
3935                           "Ratings from 'Creating:' %s %d, %s %d\n",
3936                           player1Name, player1Rating,
3937                           player2Name, player2Rating);
3938
3939                 continue;
3940             }
3941
3942             /* Improved generic start/end-of-game messages */
3943             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3944                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3945                 /* If tkind == 0: */
3946                 /* star_match[0] is the game number */
3947                 /*           [1] is the white player's name */
3948                 /*           [2] is the black player's name */
3949                 /* For end-of-game: */
3950                 /*           [3] is the reason for the game end */
3951                 /*           [4] is a PGN end game-token, preceded by " " */
3952                 /* For start-of-game: */
3953                 /*           [3] begins with "Creating" or "Continuing" */
3954                 /*           [4] is " *" or empty (don't care). */
3955                 int gamenum = atoi(star_match[0]);
3956                 char *whitename, *blackname, *why, *endtoken;
3957                 ChessMove endtype = EndOfFile;
3958
3959                 if (tkind == 0) {
3960                   whitename = star_match[1];
3961                   blackname = star_match[2];
3962                   why = star_match[3];
3963                   endtoken = star_match[4];
3964                 } else {
3965                   whitename = star_match[1];
3966                   blackname = star_match[3];
3967                   why = star_match[5];
3968                   endtoken = star_match[6];
3969                 }
3970
3971                 /* Game start messages */
3972                 if (strncmp(why, "Creating ", 9) == 0 ||
3973                     strncmp(why, "Continuing ", 11) == 0) {
3974                     gs_gamenum = gamenum;
3975                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3976                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3977                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3978 #if ZIPPY
3979                     if (appData.zippyPlay) {
3980                         ZippyGameStart(whitename, blackname);
3981                     }
3982 #endif /*ZIPPY*/
3983                     partnerBoardValid = FALSE; // [HGM] bughouse
3984                     continue;
3985                 }
3986
3987                 /* Game end messages */
3988                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3989                     ics_gamenum != gamenum) {
3990                     continue;
3991                 }
3992                 while (endtoken[0] == ' ') endtoken++;
3993                 switch (endtoken[0]) {
3994                   case '*':
3995                   default:
3996                     endtype = GameUnfinished;
3997                     break;
3998                   case '0':
3999                     endtype = BlackWins;
4000                     break;
4001                   case '1':
4002                     if (endtoken[1] == '/')
4003                       endtype = GameIsDrawn;
4004                     else
4005                       endtype = WhiteWins;
4006                     break;
4007                 }
4008                 GameEnds(endtype, why, GE_ICS);
4009 #if ZIPPY
4010                 if (appData.zippyPlay && first.initDone) {
4011                     ZippyGameEnd(endtype, why);
4012                     if (first.pr == NoProc) {
4013                       /* Start the next process early so that we'll
4014                          be ready for the next challenge */
4015                       StartChessProgram(&first);
4016                     }
4017                     /* Send "new" early, in case this command takes
4018                        a long time to finish, so that we'll be ready
4019                        for the next challenge. */
4020                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4021                     Reset(TRUE, TRUE);
4022                 }
4023 #endif /*ZIPPY*/
4024                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4025                 continue;
4026             }
4027
4028             if (looking_at(buf, &i, "Removing game * from observation") ||
4029                 looking_at(buf, &i, "no longer observing game *") ||
4030                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4031                 if (gameMode == IcsObserving &&
4032                     atoi(star_match[0]) == ics_gamenum)
4033                   {
4034                       /* icsEngineAnalyze */
4035                       if (appData.icsEngineAnalyze) {
4036                             ExitAnalyzeMode();
4037                             ModeHighlight();
4038                       }
4039                       StopClocks();
4040                       gameMode = IcsIdle;
4041                       ics_gamenum = -1;
4042                       ics_user_moved = FALSE;
4043                   }
4044                 continue;
4045             }
4046
4047             if (looking_at(buf, &i, "no longer examining game *")) {
4048                 if (gameMode == IcsExamining &&
4049                     atoi(star_match[0]) == ics_gamenum)
4050                   {
4051                       gameMode = IcsIdle;
4052                       ics_gamenum = -1;
4053                       ics_user_moved = FALSE;
4054                   }
4055                 continue;
4056             }
4057
4058             /* Advance leftover_start past any newlines we find,
4059                so only partial lines can get reparsed */
4060             if (looking_at(buf, &i, "\n")) {
4061                 prevColor = curColor;
4062                 if (curColor != ColorNormal) {
4063                     if (oldi > next_out) {
4064                         SendToPlayer(&buf[next_out], oldi - next_out);
4065                         next_out = oldi;
4066                     }
4067                     Colorize(ColorNormal, FALSE);
4068                     curColor = ColorNormal;
4069                 }
4070                 if (started == STARTED_BOARD) {
4071                     started = STARTED_NONE;
4072                     parse[parse_pos] = NULLCHAR;
4073                     ParseBoard12(parse);
4074                     ics_user_moved = 0;
4075
4076                     /* Send premove here */
4077                     if (appData.premove) {
4078                       char str[MSG_SIZ];
4079                       if (currentMove == 0 &&
4080                           gameMode == IcsPlayingWhite &&
4081                           appData.premoveWhite) {
4082                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4083                         if (appData.debugMode)
4084                           fprintf(debugFP, "Sending premove:\n");
4085                         SendToICS(str);
4086                       } else if (currentMove == 1 &&
4087                                  gameMode == IcsPlayingBlack &&
4088                                  appData.premoveBlack) {
4089                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4090                         if (appData.debugMode)
4091                           fprintf(debugFP, "Sending premove:\n");
4092                         SendToICS(str);
4093                       } else if (gotPremove) {
4094                         gotPremove = 0;
4095                         ClearPremoveHighlights();
4096                         if (appData.debugMode)
4097                           fprintf(debugFP, "Sending premove:\n");
4098                           UserMoveEvent(premoveFromX, premoveFromY,
4099                                         premoveToX, premoveToY,
4100                                         premovePromoChar);
4101                       }
4102                     }
4103
4104                     /* Usually suppress following prompt */
4105                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4106                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4107                         if (looking_at(buf, &i, "*% ")) {
4108                             savingComment = FALSE;
4109                             suppressKibitz = 0;
4110                         }
4111                     }
4112                     next_out = i;
4113                 } else if (started == STARTED_HOLDINGS) {
4114                     int gamenum;
4115                     char new_piece[MSG_SIZ];
4116                     started = STARTED_NONE;
4117                     parse[parse_pos] = NULLCHAR;
4118                     if (appData.debugMode)
4119                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4120                                                         parse, currentMove);
4121                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4122                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4123                         if (gameInfo.variant == VariantNormal) {
4124                           /* [HGM] We seem to switch variant during a game!
4125                            * Presumably no holdings were displayed, so we have
4126                            * to move the position two files to the right to
4127                            * create room for them!
4128                            */
4129                           VariantClass newVariant;
4130                           switch(gameInfo.boardWidth) { // base guess on board width
4131                                 case 9:  newVariant = VariantShogi; break;
4132                                 case 10: newVariant = VariantGreat; break;
4133                                 default: newVariant = VariantCrazyhouse; break;
4134                           }
4135                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4136                           /* Get a move list just to see the header, which
4137                              will tell us whether this is really bug or zh */
4138                           if (ics_getting_history == H_FALSE) {
4139                             ics_getting_history = H_REQUESTED;
4140                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4141                             SendToICS(str);
4142                           }
4143                         }
4144                         new_piece[0] = NULLCHAR;
4145                         sscanf(parse, "game %d white [%s black [%s <- %s",
4146                                &gamenum, white_holding, black_holding,
4147                                new_piece);
4148                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4149                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4150                         /* [HGM] copy holdings to board holdings area */
4151                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4152                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4153                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4154 #if ZIPPY
4155                         if (appData.zippyPlay && first.initDone) {
4156                             ZippyHoldings(white_holding, black_holding,
4157                                           new_piece);
4158                         }
4159 #endif /*ZIPPY*/
4160                         if (tinyLayout || smallLayout) {
4161                             char wh[16], bh[16];
4162                             PackHolding(wh, white_holding);
4163                             PackHolding(bh, black_holding);
4164                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4165                                     gameInfo.white, gameInfo.black);
4166                         } else {
4167                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4168                                     gameInfo.white, white_holding, _("vs."),
4169                                     gameInfo.black, black_holding);
4170                         }
4171                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4172                         DrawPosition(FALSE, boards[currentMove]);
4173                         DisplayTitle(str);
4174                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4175                         sscanf(parse, "game %d white [%s black [%s <- %s",
4176                                &gamenum, white_holding, black_holding,
4177                                new_piece);
4178                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4179                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4180                         /* [HGM] copy holdings to partner-board holdings area */
4181                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4182                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4183                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4184                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4185                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4186                       }
4187                     }
4188                     /* Suppress following prompt */
4189                     if (looking_at(buf, &i, "*% ")) {
4190                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4191                         savingComment = FALSE;
4192                         suppressKibitz = 0;
4193                     }
4194                     next_out = i;
4195                 }
4196                 continue;
4197             }
4198
4199             i++;                /* skip unparsed character and loop back */
4200         }
4201
4202         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4203 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4204 //          SendToPlayer(&buf[next_out], i - next_out);
4205             started != STARTED_HOLDINGS && leftover_start > next_out) {
4206             SendToPlayer(&buf[next_out], leftover_start - next_out);
4207             next_out = i;
4208         }
4209
4210         leftover_len = buf_len - leftover_start;
4211         /* if buffer ends with something we couldn't parse,
4212            reparse it after appending the next read */
4213
4214     } else if (count == 0) {
4215         RemoveInputSource(isr);
4216         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4217     } else {
4218         DisplayFatalError(_("Error reading from ICS"), error, 1);
4219     }
4220 }
4221
4222
4223 /* Board style 12 looks like this:
4224
4225    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4226
4227  * The "<12> " is stripped before it gets to this routine.  The two
4228  * trailing 0's (flip state and clock ticking) are later addition, and
4229  * some chess servers may not have them, or may have only the first.
4230  * Additional trailing fields may be added in the future.
4231  */
4232
4233 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4234
4235 #define RELATION_OBSERVING_PLAYED    0
4236 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4237 #define RELATION_PLAYING_MYMOVE      1
4238 #define RELATION_PLAYING_NOTMYMOVE  -1
4239 #define RELATION_EXAMINING           2
4240 #define RELATION_ISOLATED_BOARD     -3
4241 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4242
4243 void
4244 ParseBoard12 (char *string)
4245 {
4246 #if ZIPPY
4247     int i, takeback;
4248     char *bookHit = NULL; // [HGM] book
4249 #endif
4250     GameMode newGameMode;
4251     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4252     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4253     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4254     char to_play, board_chars[200];
4255     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4256     char black[32], white[32];
4257     Board board;
4258     int prevMove = currentMove;
4259     int ticking = 2;
4260     ChessMove moveType;
4261     int fromX, fromY, toX, toY;
4262     char promoChar;
4263     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4264     Boolean weird = FALSE, reqFlag = FALSE;
4265
4266     fromX = fromY = toX = toY = -1;
4267
4268     newGame = FALSE;
4269
4270     if (appData.debugMode)
4271       fprintf(debugFP, "Parsing board: %s\n", string);
4272
4273     move_str[0] = NULLCHAR;
4274     elapsed_time[0] = NULLCHAR;
4275     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4276         int  i = 0, j;
4277         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4278             if(string[i] == ' ') { ranks++; files = 0; }
4279             else files++;
4280             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4281             i++;
4282         }
4283         for(j = 0; j <i; j++) board_chars[j] = string[j];
4284         board_chars[i] = '\0';
4285         string += i + 1;
4286     }
4287     n = sscanf(string, PATTERN, &to_play, &double_push,
4288                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4289                &gamenum, white, black, &relation, &basetime, &increment,
4290                &white_stren, &black_stren, &white_time, &black_time,
4291                &moveNum, str, elapsed_time, move_str, &ics_flip,
4292                &ticking);
4293
4294     if (n < 21) {
4295         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4296         DisplayError(str, 0);
4297         return;
4298     }
4299
4300     /* Convert the move number to internal form */
4301     moveNum = (moveNum - 1) * 2;
4302     if (to_play == 'B') moveNum++;
4303     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4304       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4305                         0, 1);
4306       return;
4307     }
4308
4309     switch (relation) {
4310       case RELATION_OBSERVING_PLAYED:
4311       case RELATION_OBSERVING_STATIC:
4312         if (gamenum == -1) {
4313             /* Old ICC buglet */
4314             relation = RELATION_OBSERVING_STATIC;
4315         }
4316         newGameMode = IcsObserving;
4317         break;
4318       case RELATION_PLAYING_MYMOVE:
4319       case RELATION_PLAYING_NOTMYMOVE:
4320         newGameMode =
4321           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4322             IcsPlayingWhite : IcsPlayingBlack;
4323         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4324         break;
4325       case RELATION_EXAMINING:
4326         newGameMode = IcsExamining;
4327         break;
4328       case RELATION_ISOLATED_BOARD:
4329       default:
4330         /* Just display this board.  If user was doing something else,
4331            we will forget about it until the next board comes. */
4332         newGameMode = IcsIdle;
4333         break;
4334       case RELATION_STARTING_POSITION:
4335         newGameMode = gameMode;
4336         break;
4337     }
4338
4339     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4340         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4341          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4342       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4343       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4344       static int lastBgGame = -1;
4345       char *toSqr;
4346       for (k = 0; k < ranks; k++) {
4347         for (j = 0; j < files; j++)
4348           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4349         if(gameInfo.holdingsWidth > 1) {
4350              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4351              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4352         }
4353       }
4354       CopyBoard(partnerBoard, board);
4355       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4356         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4357         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4358       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4359       if(toSqr = strchr(str, '-')) {
4360         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4361         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4362       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4363       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4364       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4365       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4366       if(twoBoards) {
4367           DisplayWhiteClock(white_time*fac, to_play == 'W');
4368           DisplayBlackClock(black_time*fac, to_play != 'W');
4369           activePartner = to_play;
4370           if(gamenum != lastBgGame) {
4371               char buf[MSG_SIZ];
4372               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4373               DisplayTitle(buf);
4374           }
4375           lastBgGame = gamenum;
4376           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4377                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4378       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4379                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4380       if(!twoBoards) DisplayMessage(partnerStatus, "");
4381         partnerBoardValid = TRUE;
4382       return;
4383     }
4384
4385     if(appData.dualBoard && appData.bgObserve) {
4386         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4387             SendToICS(ics_prefix), SendToICS("pobserve\n");
4388         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4389             char buf[MSG_SIZ];
4390             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4391             SendToICS(buf);
4392         }
4393     }
4394
4395     /* Modify behavior for initial board display on move listing
4396        of wild games.
4397        */
4398     switch (ics_getting_history) {
4399       case H_FALSE:
4400       case H_REQUESTED:
4401         break;
4402       case H_GOT_REQ_HEADER:
4403       case H_GOT_UNREQ_HEADER:
4404         /* This is the initial position of the current game */
4405         gamenum = ics_gamenum;
4406         moveNum = 0;            /* old ICS bug workaround */
4407         if (to_play == 'B') {
4408           startedFromSetupPosition = TRUE;
4409           blackPlaysFirst = TRUE;
4410           moveNum = 1;
4411           if (forwardMostMove == 0) forwardMostMove = 1;
4412           if (backwardMostMove == 0) backwardMostMove = 1;
4413           if (currentMove == 0) currentMove = 1;
4414         }
4415         newGameMode = gameMode;
4416         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4417         break;
4418       case H_GOT_UNWANTED_HEADER:
4419         /* This is an initial board that we don't want */
4420         return;
4421       case H_GETTING_MOVES:
4422         /* Should not happen */
4423         DisplayError(_("Error gathering move list: extra board"), 0);
4424         ics_getting_history = H_FALSE;
4425         return;
4426     }
4427
4428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4429                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4430                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4431      /* [HGM] We seem to have switched variant unexpectedly
4432       * Try to guess new variant from board size
4433       */
4434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4438           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4439           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4440           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4441           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4442           /* Get a move list just to see the header, which
4443              will tell us whether this is really bug or zh */
4444           if (ics_getting_history == H_FALSE) {
4445             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4446             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4447             SendToICS(str);
4448           }
4449     }
4450
4451     /* Take action if this is the first board of a new game, or of a
4452        different game than is currently being displayed.  */
4453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4454         relation == RELATION_ISOLATED_BOARD) {
4455
4456         /* Forget the old game and get the history (if any) of the new one */
4457         if (gameMode != BeginningOfGame) {
4458           Reset(TRUE, TRUE);
4459         }
4460         newGame = TRUE;
4461         if (appData.autoRaiseBoard) BoardToTop();
4462         prevMove = -3;
4463         if (gamenum == -1) {
4464             newGameMode = IcsIdle;
4465         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4466                    appData.getMoveList && !reqFlag) {
4467             /* Need to get game history */
4468             ics_getting_history = H_REQUESTED;
4469             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4470             SendToICS(str);
4471         }
4472
4473         /* Initially flip the board to have black on the bottom if playing
4474            black or if the ICS flip flag is set, but let the user change
4475            it with the Flip View button. */
4476         flipView = appData.autoFlipView ?
4477           (newGameMode == IcsPlayingBlack) || ics_flip :
4478           appData.flipView;
4479
4480         /* Done with values from previous mode; copy in new ones */
4481         gameMode = newGameMode;
4482         ModeHighlight();
4483         ics_gamenum = gamenum;
4484         if (gamenum == gs_gamenum) {
4485             int klen = strlen(gs_kind);
4486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4487             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4488             gameInfo.event = StrSave(str);
4489         } else {
4490             gameInfo.event = StrSave("ICS game");
4491         }
4492         gameInfo.site = StrSave(appData.icsHost);
4493         gameInfo.date = PGNDate();
4494         gameInfo.round = StrSave("-");
4495         gameInfo.white = StrSave(white);
4496         gameInfo.black = StrSave(black);
4497         timeControl = basetime * 60 * 1000;
4498         timeControl_2 = 0;
4499         timeIncrement = increment * 1000;
4500         movesPerSession = 0;
4501         gameInfo.timeControl = TimeControlTagValue();
4502         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4503   if (appData.debugMode) {
4504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4506     setbuf(debugFP, NULL);
4507   }
4508
4509         gameInfo.outOfBook = NULL;
4510
4511         /* Do we have the ratings? */
4512         if (strcmp(player1Name, white) == 0 &&
4513             strcmp(player2Name, black) == 0) {
4514             if (appData.debugMode)
4515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4516                       player1Rating, player2Rating);
4517             gameInfo.whiteRating = player1Rating;
4518             gameInfo.blackRating = player2Rating;
4519         } else if (strcmp(player2Name, white) == 0 &&
4520                    strcmp(player1Name, black) == 0) {
4521             if (appData.debugMode)
4522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4523                       player2Rating, player1Rating);
4524             gameInfo.whiteRating = player2Rating;
4525             gameInfo.blackRating = player1Rating;
4526         }
4527         player1Name[0] = player2Name[0] = NULLCHAR;
4528
4529         /* Silence shouts if requested */
4530         if (appData.quietPlay &&
4531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4532             SendToICS(ics_prefix);
4533             SendToICS("set shout 0\n");
4534         }
4535     }
4536
4537     /* Deal with midgame name changes */
4538     if (!newGame) {
4539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4540             if (gameInfo.white) free(gameInfo.white);
4541             gameInfo.white = StrSave(white);
4542         }
4543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4544             if (gameInfo.black) free(gameInfo.black);
4545             gameInfo.black = StrSave(black);
4546         }
4547     }
4548
4549     /* Throw away game result if anything actually changes in examine mode */
4550     if (gameMode == IcsExamining && !newGame) {
4551         gameInfo.result = GameUnfinished;
4552         if (gameInfo.resultDetails != NULL) {
4553             free(gameInfo.resultDetails);
4554             gameInfo.resultDetails = NULL;
4555         }
4556     }
4557
4558     /* In pausing && IcsExamining mode, we ignore boards coming
4559        in if they are in a different variation than we are. */
4560     if (pauseExamInvalid) return;
4561     if (pausing && gameMode == IcsExamining) {
4562         if (moveNum <= pauseExamForwardMostMove) {
4563             pauseExamInvalid = TRUE;
4564             forwardMostMove = pauseExamForwardMostMove;
4565             return;
4566         }
4567     }
4568
4569   if (appData.debugMode) {
4570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4571   }
4572     /* Parse the board */
4573     for (k = 0; k < ranks; k++) {
4574       for (j = 0; j < files; j++)
4575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4576       if(gameInfo.holdingsWidth > 1) {
4577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4579       }
4580     }
4581     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4582       board[5][BOARD_RGHT+1] = WhiteAngel;
4583       board[6][BOARD_RGHT+1] = WhiteMarshall;
4584       board[1][0] = BlackMarshall;
4585       board[2][0] = BlackAngel;
4586       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4587     }
4588     CopyBoard(boards[moveNum], board);
4589     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4590     if (moveNum == 0) {
4591         startedFromSetupPosition =
4592           !CompareBoards(board, initialPosition);
4593         if(startedFromSetupPosition)
4594             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4595     }
4596
4597     /* [HGM] Set castling rights. Take the outermost Rooks,
4598        to make it also work for FRC opening positions. Note that board12
4599        is really defective for later FRC positions, as it has no way to
4600        indicate which Rook can castle if they are on the same side of King.
4601        For the initial position we grant rights to the outermost Rooks,
4602        and remember thos rights, and we then copy them on positions
4603        later in an FRC game. This means WB might not recognize castlings with
4604        Rooks that have moved back to their original position as illegal,
4605        but in ICS mode that is not its job anyway.
4606     */
4607     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4608     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4609
4610         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4611             if(board[0][i] == WhiteRook) j = i;
4612         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4614             if(board[0][i] == WhiteRook) j = i;
4615         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4616         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4617             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4618         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4619         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4620             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4621         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4622
4623         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4624         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4625         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4626             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4627         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4628             if(board[BOARD_HEIGHT-1][k] == bKing)
4629                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4630         if(gameInfo.variant == VariantTwoKings) {
4631             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4632             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4633             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4634         }
4635     } else { int r;
4636         r = boards[moveNum][CASTLING][0] = initialRights[0];
4637         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4638         r = boards[moveNum][CASTLING][1] = initialRights[1];
4639         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4640         r = boards[moveNum][CASTLING][3] = initialRights[3];
4641         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4642         r = boards[moveNum][CASTLING][4] = initialRights[4];
4643         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4644         /* wildcastle kludge: always assume King has rights */
4645         r = boards[moveNum][CASTLING][2] = initialRights[2];
4646         r = boards[moveNum][CASTLING][5] = initialRights[5];
4647     }
4648     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4649     boards[moveNum][EP_STATUS] = EP_NONE;
4650     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4651     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4652     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4653
4654
4655     if (ics_getting_history == H_GOT_REQ_HEADER ||
4656         ics_getting_history == H_GOT_UNREQ_HEADER) {
4657         /* This was an initial position from a move list, not
4658            the current position */
4659         return;
4660     }
4661
4662     /* Update currentMove and known move number limits */
4663     newMove = newGame || moveNum > forwardMostMove;
4664
4665     if (newGame) {
4666         forwardMostMove = backwardMostMove = currentMove = moveNum;
4667         if (gameMode == IcsExamining && moveNum == 0) {
4668           /* Workaround for ICS limitation: we are not told the wild
4669              type when starting to examine a game.  But if we ask for
4670              the move list, the move list header will tell us */
4671             ics_getting_history = H_REQUESTED;
4672             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4673             SendToICS(str);
4674         }
4675     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4676                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4677 #if ZIPPY
4678         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4679         /* [HGM] applied this also to an engine that is silently watching        */
4680         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4681             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4682             gameInfo.variant == currentlyInitializedVariant) {
4683           takeback = forwardMostMove - moveNum;
4684           for (i = 0; i < takeback; i++) {
4685             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4686             SendToProgram("undo\n", &first);
4687           }
4688         }
4689 #endif
4690
4691         forwardMostMove = moveNum;
4692         if (!pausing || currentMove > forwardMostMove)
4693           currentMove = forwardMostMove;
4694     } else {
4695         /* New part of history that is not contiguous with old part */
4696         if (pausing && gameMode == IcsExamining) {
4697             pauseExamInvalid = TRUE;
4698             forwardMostMove = pauseExamForwardMostMove;
4699             return;
4700         }
4701         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4702 #if ZIPPY
4703             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4704                 // [HGM] when we will receive the move list we now request, it will be
4705                 // fed to the engine from the first move on. So if the engine is not
4706                 // in the initial position now, bring it there.
4707                 InitChessProgram(&first, 0);
4708             }
4709 #endif
4710             ics_getting_history = H_REQUESTED;
4711             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4712             SendToICS(str);
4713         }
4714         forwardMostMove = backwardMostMove = currentMove = moveNum;
4715     }
4716
4717     /* Update the clocks */
4718     if (strchr(elapsed_time, '.')) {
4719       /* Time is in ms */
4720       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4721       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4722     } else {
4723       /* Time is in seconds */
4724       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4725       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4726     }
4727
4728
4729 #if ZIPPY
4730     if (appData.zippyPlay && newGame &&
4731         gameMode != IcsObserving && gameMode != IcsIdle &&
4732         gameMode != IcsExamining)
4733       ZippyFirstBoard(moveNum, basetime, increment);
4734 #endif
4735
4736     /* Put the move on the move list, first converting
4737        to canonical algebraic form. */
4738     if (moveNum > 0) {
4739   if (appData.debugMode) {
4740     int f = forwardMostMove;
4741     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4742             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4743             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4744     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4745     fprintf(debugFP, "moveNum = %d\n", moveNum);
4746     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4747     setbuf(debugFP, NULL);
4748   }
4749         if (moveNum <= backwardMostMove) {
4750             /* We don't know what the board looked like before
4751                this move.  Punt. */
4752           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4753             strcat(parseList[moveNum - 1], " ");
4754             strcat(parseList[moveNum - 1], elapsed_time);
4755             moveList[moveNum - 1][0] = NULLCHAR;
4756         } else if (strcmp(move_str, "none") == 0) {
4757             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4758             /* Again, we don't know what the board looked like;
4759                this is really the start of the game. */
4760             parseList[moveNum - 1][0] = NULLCHAR;
4761             moveList[moveNum - 1][0] = NULLCHAR;
4762             backwardMostMove = moveNum;
4763             startedFromSetupPosition = TRUE;
4764             fromX = fromY = toX = toY = -1;
4765         } else {
4766           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4767           //                 So we parse the long-algebraic move string in stead of the SAN move
4768           int valid; char buf[MSG_SIZ], *prom;
4769
4770           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4771                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4772           // str looks something like "Q/a1-a2"; kill the slash
4773           if(str[1] == '/')
4774             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4775           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4776           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4777                 strcat(buf, prom); // long move lacks promo specification!
4778           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4779                 if(appData.debugMode)
4780                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4781                 safeStrCpy(move_str, buf, MSG_SIZ);
4782           }
4783           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4784                                 &fromX, &fromY, &toX, &toY, &promoChar)
4785                || ParseOneMove(buf, moveNum - 1, &moveType,
4786                                 &fromX, &fromY, &toX, &toY, &promoChar);
4787           // end of long SAN patch
4788           if (valid) {
4789             (void) CoordsToAlgebraic(boards[moveNum - 1],
4790                                      PosFlags(moveNum - 1),
4791                                      fromY, fromX, toY, toX, promoChar,
4792                                      parseList[moveNum-1]);
4793             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4794               case MT_NONE:
4795               case MT_STALEMATE:
4796               default:
4797                 break;
4798               case MT_CHECK:
4799                 if(gameInfo.variant != VariantShogi)
4800                     strcat(parseList[moveNum - 1], "+");
4801                 break;
4802               case MT_CHECKMATE:
4803               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4804                 strcat(parseList[moveNum - 1], "#");
4805                 break;
4806             }
4807             strcat(parseList[moveNum - 1], " ");
4808             strcat(parseList[moveNum - 1], elapsed_time);
4809             /* currentMoveString is set as a side-effect of ParseOneMove */
4810             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4811             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4812             strcat(moveList[moveNum - 1], "\n");
4813
4814             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4815                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4816               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4817                 ChessSquare old, new = boards[moveNum][k][j];
4818                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4819                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4820                   if(old == new) continue;
4821                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4822                   else if(new == WhiteWazir || new == BlackWazir) {
4823                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4824                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4825                       else boards[moveNum][k][j] = old; // preserve type of Gold
4826                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4827                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4828               }
4829           } else {
4830             /* Move from ICS was illegal!?  Punt. */
4831             if (appData.debugMode) {
4832               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4833               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4834             }
4835             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4836             strcat(parseList[moveNum - 1], " ");
4837             strcat(parseList[moveNum - 1], elapsed_time);
4838             moveList[moveNum - 1][0] = NULLCHAR;
4839             fromX = fromY = toX = toY = -1;
4840           }
4841         }
4842   if (appData.debugMode) {
4843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4844     setbuf(debugFP, NULL);
4845   }
4846
4847 #if ZIPPY
4848         /* Send move to chess program (BEFORE animating it). */
4849         if (appData.zippyPlay && !newGame && newMove &&
4850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4851
4852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4855                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4856                             move_str);
4857                     DisplayError(str, 0);
4858                 } else {
4859                     if (first.sendTime) {
4860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4861                     }
4862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4863                     if (firstMove && !bookHit) {
4864                         firstMove = FALSE;
4865                         if (first.useColors) {
4866                           SendToProgram(gameMode == IcsPlayingWhite ?
4867                                         "white\ngo\n" :
4868                                         "black\ngo\n", &first);
4869                         } else {
4870                           SendToProgram("go\n", &first);
4871                         }
4872                         first.maybeThinking = TRUE;
4873                     }
4874                 }
4875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4876               if (moveList[moveNum - 1][0] == NULLCHAR) {
4877                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4878                 DisplayError(str, 0);
4879               } else {
4880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4881                 SendMoveToProgram(moveNum - 1, &first);
4882               }
4883             }
4884         }
4885 #endif
4886     }
4887
4888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4889         /* If move comes from a remote source, animate it.  If it
4890            isn't remote, it will have already been animated. */
4891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4893         }
4894         if (!pausing && appData.highlightLastMove) {
4895             SetHighlights(fromX, fromY, toX, toY);
4896         }
4897     }
4898
4899     /* Start the clocks */
4900     whiteFlag = blackFlag = FALSE;
4901     appData.clockMode = !(basetime == 0 && increment == 0);
4902     if (ticking == 0) {
4903       ics_clock_paused = TRUE;
4904       StopClocks();
4905     } else if (ticking == 1) {
4906       ics_clock_paused = FALSE;
4907     }
4908     if (gameMode == IcsIdle ||
4909         relation == RELATION_OBSERVING_STATIC ||
4910         relation == RELATION_EXAMINING ||
4911         ics_clock_paused)
4912       DisplayBothClocks();
4913     else
4914       StartClocks();
4915
4916     /* Display opponents and material strengths */
4917     if (gameInfo.variant != VariantBughouse &&
4918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4919         if (tinyLayout || smallLayout) {
4920             if(gameInfo.variant == VariantNormal)
4921               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4923                     basetime, increment);
4924             else
4925               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4927                     basetime, increment, (int) gameInfo.variant);
4928         } else {
4929             if(gameInfo.variant == VariantNormal)
4930               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4931                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4932                     basetime, increment);
4933             else
4934               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4935                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4936                     basetime, increment, VariantName(gameInfo.variant));
4937         }
4938         DisplayTitle(str);
4939   if (appData.debugMode) {
4940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4941   }
4942     }
4943
4944
4945     /* Display the board */
4946     if (!pausing && !appData.noGUI) {
4947
4948       if (appData.premove)
4949           if (!gotPremove ||
4950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4952               ClearPremoveHighlights();
4953
4954       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4955         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4956       DrawPosition(j, boards[currentMove]);
4957
4958       DisplayMove(moveNum - 1);
4959       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4960             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4961               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4962         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4963       }
4964     }
4965
4966     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4967 #if ZIPPY
4968     if(bookHit) { // [HGM] book: simulate book reply
4969         static char bookMove[MSG_SIZ]; // a bit generous?
4970
4971         programStats.nodes = programStats.depth = programStats.time =
4972         programStats.score = programStats.got_only_move = 0;
4973         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4974
4975         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4976         strcat(bookMove, bookHit);
4977         HandleMachineMove(bookMove, &first);
4978     }
4979 #endif
4980 }
4981
4982 void
4983 GetMoveListEvent ()
4984 {
4985     char buf[MSG_SIZ];
4986     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4987         ics_getting_history = H_REQUESTED;
4988         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4989         SendToICS(buf);
4990     }
4991 }
4992
4993 void
4994 SendToBoth (char *msg)
4995 {   // to make it easy to keep two engines in step in dual analysis
4996     SendToProgram(msg, &first);
4997     if(second.analyzing) SendToProgram(msg, &second);
4998 }
4999
5000 void
5001 AnalysisPeriodicEvent (int force)
5002 {
5003     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5004          && !force) || !appData.periodicUpdates)
5005       return;
5006
5007     /* Send . command to Crafty to collect stats */
5008     SendToBoth(".\n");
5009
5010     /* Don't send another until we get a response (this makes
5011        us stop sending to old Crafty's which don't understand
5012        the "." command (sending illegal cmds resets node count & time,
5013        which looks bad)) */
5014     programStats.ok_to_send = 0;
5015 }
5016
5017 void
5018 ics_update_width (int new_width)
5019 {
5020         ics_printf("set width %d\n", new_width);
5021 }
5022
5023 void
5024 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5025 {
5026     char buf[MSG_SIZ];
5027
5028     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5029         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5030             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5031             SendToProgram(buf, cps);
5032             return;
5033         }
5034         // null move in variant where engine does not understand it (for analysis purposes)
5035         SendBoard(cps, moveNum + 1); // send position after move in stead.
5036         return;
5037     }
5038     if (cps->useUsermove) {
5039       SendToProgram("usermove ", cps);
5040     }
5041     if (cps->useSAN) {
5042       char *space;
5043       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5044         int len = space - parseList[moveNum];
5045         memcpy(buf, parseList[moveNum], len);
5046         buf[len++] = '\n';
5047         buf[len] = NULLCHAR;
5048       } else {
5049         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5050       }
5051       SendToProgram(buf, cps);
5052     } else {
5053       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5054         AlphaRank(moveList[moveNum], 4);
5055         SendToProgram(moveList[moveNum], cps);
5056         AlphaRank(moveList[moveNum], 4); // and back
5057       } else
5058       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5059        * the engine. It would be nice to have a better way to identify castle
5060        * moves here. */
5061       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5062                                                                          && cps->useOOCastle) {
5063         int fromX = moveList[moveNum][0] - AAA;
5064         int fromY = moveList[moveNum][1] - ONE;
5065         int toX = moveList[moveNum][2] - AAA;
5066         int toY = moveList[moveNum][3] - ONE;
5067         if((boards[moveNum][fromY][fromX] == WhiteKing
5068             && boards[moveNum][toY][toX] == WhiteRook)
5069            || (boards[moveNum][fromY][fromX] == BlackKing
5070                && boards[moveNum][toY][toX] == BlackRook)) {
5071           if(toX > fromX) SendToProgram("O-O\n", cps);
5072           else SendToProgram("O-O-O\n", cps);
5073         }
5074         else SendToProgram(moveList[moveNum], cps);
5075       } else
5076       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5077           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5078                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5079                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5080                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5081           SendToProgram(buf, cps);
5082       } else
5083       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5084         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5085           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5086           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5087                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5088         } else
5089           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5090                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5091         SendToProgram(buf, cps);
5092       }
5093       else SendToProgram(moveList[moveNum], cps);
5094       /* End of additions by Tord */
5095     }
5096
5097     /* [HGM] setting up the opening has brought engine in force mode! */
5098     /*       Send 'go' if we are in a mode where machine should play. */
5099     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5100         (gameMode == TwoMachinesPlay   ||
5101 #if ZIPPY
5102          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5103 #endif
5104          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5105         SendToProgram("go\n", cps);
5106   if (appData.debugMode) {
5107     fprintf(debugFP, "(extra)\n");
5108   }
5109     }
5110     setboardSpoiledMachineBlack = 0;
5111 }
5112
5113 void
5114 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5115 {
5116     char user_move[MSG_SIZ];
5117     char suffix[4];
5118
5119     if(gameInfo.variant == VariantSChess && promoChar) {
5120         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5121         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5122     } else suffix[0] = NULLCHAR;
5123
5124     switch (moveType) {
5125       default:
5126         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5127                 (int)moveType, fromX, fromY, toX, toY);
5128         DisplayError(user_move + strlen("say "), 0);
5129         break;
5130       case WhiteKingSideCastle:
5131       case BlackKingSideCastle:
5132       case WhiteQueenSideCastleWild:
5133       case BlackQueenSideCastleWild:
5134       /* PUSH Fabien */
5135       case WhiteHSideCastleFR:
5136       case BlackHSideCastleFR:
5137       /* POP Fabien */
5138         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5139         break;
5140       case WhiteQueenSideCastle:
5141       case BlackQueenSideCastle:
5142       case WhiteKingSideCastleWild:
5143       case BlackKingSideCastleWild:
5144       /* PUSH Fabien */
5145       case WhiteASideCastleFR:
5146       case BlackASideCastleFR:
5147       /* POP Fabien */
5148         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5149         break;
5150       case WhiteNonPromotion:
5151       case BlackNonPromotion:
5152         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5153         break;
5154       case WhitePromotion:
5155       case BlackPromotion:
5156         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5157            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5158           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5159                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5160                 PieceToChar(WhiteFerz));
5161         else if(gameInfo.variant == VariantGreat)
5162           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5163                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5164                 PieceToChar(WhiteMan));
5165         else
5166           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5167                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5168                 promoChar);
5169         break;
5170       case WhiteDrop:
5171       case BlackDrop:
5172       drop:
5173         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5174                  ToUpper(PieceToChar((ChessSquare) fromX)),
5175                  AAA + toX, ONE + toY);
5176         break;
5177       case IllegalMove:  /* could be a variant we don't quite understand */
5178         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5179       case NormalMove:
5180       case WhiteCapturesEnPassant:
5181       case BlackCapturesEnPassant:
5182         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5184         break;
5185     }
5186     SendToICS(user_move);
5187     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5188         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5189 }
5190
5191 void
5192 UploadGameEvent ()
5193 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5194     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5195     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5196     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5197       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5198       return;
5199     }
5200     if(gameMode != IcsExamining) { // is this ever not the case?
5201         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5202
5203         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5204           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5205         } else { // on FICS we must first go to general examine mode
5206           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5207         }
5208         if(gameInfo.variant != VariantNormal) {
5209             // try figure out wild number, as xboard names are not always valid on ICS
5210             for(i=1; i<=36; i++) {
5211               snprintf(buf, MSG_SIZ, "wild/%d", i);
5212                 if(StringToVariant(buf) == gameInfo.variant) break;
5213             }
5214             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5215             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5216             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5217         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5218         SendToICS(ics_prefix);
5219         SendToICS(buf);
5220         if(startedFromSetupPosition || backwardMostMove != 0) {
5221           fen = PositionToFEN(backwardMostMove, NULL, 1);
5222           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5223             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5224             SendToICS(buf);
5225           } else { // FICS: everything has to set by separate bsetup commands
5226             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5227             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5228             SendToICS(buf);
5229             if(!WhiteOnMove(backwardMostMove)) {
5230                 SendToICS("bsetup tomove black\n");
5231             }
5232             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5233             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5234             SendToICS(buf);
5235             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5236             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5237             SendToICS(buf);
5238             i = boards[backwardMostMove][EP_STATUS];
5239             if(i >= 0) { // set e.p.
5240               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5241                 SendToICS(buf);
5242             }
5243             bsetup++;
5244           }
5245         }
5246       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5247             SendToICS("bsetup done\n"); // switch to normal examining.
5248     }
5249     for(i = backwardMostMove; i<last; i++) {
5250         char buf[20];
5251         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5252         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5253             int len = strlen(moveList[i]);
5254             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5255             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5256         }
5257         SendToICS(buf);
5258     }
5259     SendToICS(ics_prefix);
5260     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5261 }
5262
5263 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5264
5265 void
5266 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5267 {
5268     if (rf == DROP_RANK) {
5269       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5270       sprintf(move, "%c@%c%c\n",
5271                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5272     } else {
5273         if (promoChar == 'x' || promoChar == NULLCHAR) {
5274           sprintf(move, "%c%c%c%c\n",
5275                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5276           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5277         } else {
5278             sprintf(move, "%c%c%c%c%c\n",
5279                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5280         }
5281     }
5282 }
5283
5284 void
5285 ProcessICSInitScript (FILE *f)
5286 {
5287     char buf[MSG_SIZ];
5288
5289     while (fgets(buf, MSG_SIZ, f)) {
5290         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5291     }
5292
5293     fclose(f);
5294 }
5295
5296
5297 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5298 int dragging;
5299 static ClickType lastClickType;
5300
5301 int
5302 Partner (ChessSquare *p)
5303 { // change piece into promotion partner if one shogi-promotes to the other
5304   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5305   ChessSquare partner;
5306   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5307   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5308   *p = partner;
5309   return 1;
5310 }
5311
5312 void
5313 Sweep (int step)
5314 {
5315     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5316     static int toggleFlag;
5317     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5318     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5319     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5320     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5321     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5322     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5323     do {
5324         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5325         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5326         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5327         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5328         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5329         if(!step) step = -1;
5330     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5331             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion));
5332     if(toX >= 0) {
5333         int victim = boards[currentMove][toY][toX];
5334         boards[currentMove][toY][toX] = promoSweep;
5335         DrawPosition(FALSE, boards[currentMove]);
5336         boards[currentMove][toY][toX] = victim;
5337     } else
5338     ChangeDragPiece(promoSweep);
5339 }
5340
5341 int
5342 PromoScroll (int x, int y)
5343 {
5344   int step = 0;
5345
5346   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5347   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5348   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5349   if(!step) return FALSE;
5350   lastX = x; lastY = y;
5351   if((promoSweep < BlackPawn) == flipView) step = -step;
5352   if(step > 0) selectFlag = 1;
5353   if(!selectFlag) Sweep(step);
5354   return FALSE;
5355 }
5356
5357 void
5358 NextPiece (int step)
5359 {
5360     ChessSquare piece = boards[currentMove][toY][toX];
5361     do {
5362         pieceSweep -= step;
5363         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5364         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5365         if(!step) step = -1;
5366     } while(PieceToChar(pieceSweep) == '.');
5367     boards[currentMove][toY][toX] = pieceSweep;
5368     DrawPosition(FALSE, boards[currentMove]);
5369     boards[currentMove][toY][toX] = piece;
5370 }
5371 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5372 void
5373 AlphaRank (char *move, int n)
5374 {
5375 //    char *p = move, c; int x, y;
5376
5377     if (appData.debugMode) {
5378         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5379     }
5380
5381     if(move[1]=='*' &&
5382        move[2]>='0' && move[2]<='9' &&
5383        move[3]>='a' && move[3]<='x'    ) {
5384         move[1] = '@';
5385         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5386         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5387     } else
5388     if(move[0]>='0' && move[0]<='9' &&
5389        move[1]>='a' && move[1]<='x' &&
5390        move[2]>='0' && move[2]<='9' &&
5391        move[3]>='a' && move[3]<='x'    ) {
5392         /* input move, Shogi -> normal */
5393         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5394         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5395         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5396         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5397     } else
5398     if(move[1]=='@' &&
5399        move[3]>='0' && move[3]<='9' &&
5400        move[2]>='a' && move[2]<='x'    ) {
5401         move[1] = '*';
5402         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5403         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5404     } else
5405     if(
5406        move[0]>='a' && move[0]<='x' &&
5407        move[3]>='0' && move[3]<='9' &&
5408        move[2]>='a' && move[2]<='x'    ) {
5409          /* output move, normal -> Shogi */
5410         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5411         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5412         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5413         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5414         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5415     }
5416     if (appData.debugMode) {
5417         fprintf(debugFP, "   out = '%s'\n", move);
5418     }
5419 }
5420
5421 char yy_textstr[8000];
5422
5423 /* Parser for moves from gnuchess, ICS, or user typein box */
5424 Boolean
5425 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5426 {
5427     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5428
5429     switch (*moveType) {
5430       case WhitePromotion:
5431       case BlackPromotion:
5432       case WhiteNonPromotion:
5433       case BlackNonPromotion:
5434       case NormalMove:
5435       case FirstLeg:
5436       case WhiteCapturesEnPassant:
5437       case BlackCapturesEnPassant:
5438       case WhiteKingSideCastle:
5439       case WhiteQueenSideCastle:
5440       case BlackKingSideCastle:
5441       case BlackQueenSideCastle:
5442       case WhiteKingSideCastleWild:
5443       case WhiteQueenSideCastleWild:
5444       case BlackKingSideCastleWild:
5445       case BlackQueenSideCastleWild:
5446       /* Code added by Tord: */
5447       case WhiteHSideCastleFR:
5448       case WhiteASideCastleFR:
5449       case BlackHSideCastleFR:
5450       case BlackASideCastleFR:
5451       /* End of code added by Tord */
5452       case IllegalMove:         /* bug or odd chess variant */
5453         *fromX = currentMoveString[0] - AAA;
5454         *fromY = currentMoveString[1] - ONE;
5455         *toX = currentMoveString[2] - AAA;
5456         *toY = currentMoveString[3] - ONE;
5457         *promoChar = currentMoveString[4];
5458         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5459             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5460     if (appData.debugMode) {
5461         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5462     }
5463             *fromX = *fromY = *toX = *toY = 0;
5464             return FALSE;
5465         }
5466         if (appData.testLegality) {
5467           return (*moveType != IllegalMove);
5468         } else {
5469           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5470                          // [HGM] lion: if this is a double move we are less critical
5471                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5472         }
5473
5474       case WhiteDrop:
5475       case BlackDrop:
5476         *fromX = *moveType == WhiteDrop ?
5477           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5478           (int) CharToPiece(ToLower(currentMoveString[0]));
5479         *fromY = DROP_RANK;
5480         *toX = currentMoveString[2] - AAA;
5481         *toY = currentMoveString[3] - ONE;
5482         *promoChar = NULLCHAR;
5483         return TRUE;
5484
5485       case AmbiguousMove:
5486       case ImpossibleMove:
5487       case EndOfFile:
5488       case ElapsedTime:
5489       case Comment:
5490       case PGNTag:
5491       case NAG:
5492       case WhiteWins:
5493       case BlackWins:
5494       case GameIsDrawn:
5495       default:
5496     if (appData.debugMode) {
5497         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5498     }
5499         /* bug? */
5500         *fromX = *fromY = *toX = *toY = 0;
5501         *promoChar = NULLCHAR;
5502         return FALSE;
5503     }
5504 }
5505
5506 Boolean pushed = FALSE;
5507 char *lastParseAttempt;
5508
5509 void
5510 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5511 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5512   int fromX, fromY, toX, toY; char promoChar;
5513   ChessMove moveType;
5514   Boolean valid;
5515   int nr = 0;
5516
5517   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5518   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5519     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5520     pushed = TRUE;
5521   }
5522   endPV = forwardMostMove;
5523   do {
5524     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5525     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5526     lastParseAttempt = pv;
5527     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5528     if(!valid && nr == 0 &&
5529        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5530         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5531         // Hande case where played move is different from leading PV move
5532         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5533         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5534         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5535         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5536           endPV += 2; // if position different, keep this
5537           moveList[endPV-1][0] = fromX + AAA;
5538           moveList[endPV-1][1] = fromY + ONE;
5539           moveList[endPV-1][2] = toX + AAA;
5540           moveList[endPV-1][3] = toY + ONE;
5541           parseList[endPV-1][0] = NULLCHAR;
5542           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5543         }
5544       }
5545     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5546     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5547     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5548     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5549         valid++; // allow comments in PV
5550         continue;
5551     }
5552     nr++;
5553     if(endPV+1 > framePtr) break; // no space, truncate
5554     if(!valid) break;
5555     endPV++;
5556     CopyBoard(boards[endPV], boards[endPV-1]);
5557     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5558     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5559     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5560     CoordsToAlgebraic(boards[endPV - 1],
5561                              PosFlags(endPV - 1),
5562                              fromY, fromX, toY, toX, promoChar,
5563                              parseList[endPV - 1]);
5564   } while(valid);
5565   if(atEnd == 2) return; // used hidden, for PV conversion
5566   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5567   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5568   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5569                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5570   DrawPosition(TRUE, boards[currentMove]);
5571 }
5572
5573 int
5574 MultiPV (ChessProgramState *cps)
5575 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5576         int i;
5577         for(i=0; i<cps->nrOptions; i++)
5578             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5579                 return i;
5580         return -1;
5581 }
5582
5583 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5584
5585 Boolean
5586 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5587 {
5588         int startPV, multi, lineStart, origIndex = index;
5589         char *p, buf2[MSG_SIZ];
5590         ChessProgramState *cps = (pane ? &second : &first);
5591
5592         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5593         lastX = x; lastY = y;
5594         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5595         lineStart = startPV = index;
5596         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5597         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5598         index = startPV;
5599         do{ while(buf[index] && buf[index] != '\n') index++;
5600         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5601         buf[index] = 0;
5602         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5603                 int n = cps->option[multi].value;
5604                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5605                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5606                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5607                 cps->option[multi].value = n;
5608                 *start = *end = 0;
5609                 return FALSE;
5610         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5611                 ExcludeClick(origIndex - lineStart);
5612                 return FALSE;
5613         }
5614         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5615         *start = startPV; *end = index-1;
5616         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5617         return TRUE;
5618 }
5619
5620 char *
5621 PvToSAN (char *pv)
5622 {
5623         static char buf[10*MSG_SIZ];
5624         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5625         *buf = NULLCHAR;
5626         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5627         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5628         for(i = forwardMostMove; i<endPV; i++){
5629             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5630             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5631             k += strlen(buf+k);
5632         }
5633         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5634         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5635         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5636         endPV = savedEnd;
5637         return buf;
5638 }
5639
5640 Boolean
5641 LoadPV (int x, int y)
5642 { // called on right mouse click to load PV
5643   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5644   lastX = x; lastY = y;
5645   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5646   extendGame = FALSE;
5647   return TRUE;
5648 }
5649
5650 void
5651 UnLoadPV ()
5652 {
5653   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5654   if(endPV < 0) return;
5655   if(appData.autoCopyPV) CopyFENToClipboard();
5656   endPV = -1;
5657   if(extendGame && currentMove > forwardMostMove) {
5658         Boolean saveAnimate = appData.animate;
5659         if(pushed) {
5660             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5661                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5662             } else storedGames--; // abandon shelved tail of original game
5663         }
5664         pushed = FALSE;
5665         forwardMostMove = currentMove;
5666         currentMove = oldFMM;
5667         appData.animate = FALSE;
5668         ToNrEvent(forwardMostMove);
5669         appData.animate = saveAnimate;
5670   }
5671   currentMove = forwardMostMove;
5672   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5673   ClearPremoveHighlights();
5674   DrawPosition(TRUE, boards[currentMove]);
5675 }
5676
5677 void
5678 MovePV (int x, int y, int h)
5679 { // step through PV based on mouse coordinates (called on mouse move)
5680   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5681
5682   // we must somehow check if right button is still down (might be released off board!)
5683   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5684   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5685   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5686   if(!step) return;
5687   lastX = x; lastY = y;
5688
5689   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5690   if(endPV < 0) return;
5691   if(y < margin) step = 1; else
5692   if(y > h - margin) step = -1;
5693   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5694   currentMove += step;
5695   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5696   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5697                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5698   DrawPosition(FALSE, boards[currentMove]);
5699 }
5700
5701
5702 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5703 // All positions will have equal probability, but the current method will not provide a unique
5704 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5705 #define DARK 1
5706 #define LITE 2
5707 #define ANY 3
5708
5709 int squaresLeft[4];
5710 int piecesLeft[(int)BlackPawn];
5711 int seed, nrOfShuffles;
5712
5713 void
5714 GetPositionNumber ()
5715 {       // sets global variable seed
5716         int i;
5717
5718         seed = appData.defaultFrcPosition;
5719         if(seed < 0) { // randomize based on time for negative FRC position numbers
5720                 for(i=0; i<50; i++) seed += random();
5721                 seed = random() ^ random() >> 8 ^ random() << 8;
5722                 if(seed<0) seed = -seed;
5723         }
5724 }
5725
5726 int
5727 put (Board board, int pieceType, int rank, int n, int shade)
5728 // put the piece on the (n-1)-th empty squares of the given shade
5729 {
5730         int i;
5731
5732         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5733                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5734                         board[rank][i] = (ChessSquare) pieceType;
5735                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5736                         squaresLeft[ANY]--;
5737                         piecesLeft[pieceType]--;
5738                         return i;
5739                 }
5740         }
5741         return -1;
5742 }
5743
5744
5745 void
5746 AddOnePiece (Board board, int pieceType, int rank, int shade)
5747 // calculate where the next piece goes, (any empty square), and put it there
5748 {
5749         int i;
5750
5751         i = seed % squaresLeft[shade];
5752         nrOfShuffles *= squaresLeft[shade];
5753         seed /= squaresLeft[shade];
5754         put(board, pieceType, rank, i, shade);
5755 }
5756
5757 void
5758 AddTwoPieces (Board board, int pieceType, int rank)
5759 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5760 {
5761         int i, n=squaresLeft[ANY], j=n-1, k;
5762
5763         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5764         i = seed % k;  // pick one
5765         nrOfShuffles *= k;
5766         seed /= k;
5767         while(i >= j) i -= j--;
5768         j = n - 1 - j; i += j;
5769         put(board, pieceType, rank, j, ANY);
5770         put(board, pieceType, rank, i, ANY);
5771 }
5772
5773 void
5774 SetUpShuffle (Board board, int number)
5775 {
5776         int i, p, first=1;
5777
5778         GetPositionNumber(); nrOfShuffles = 1;
5779
5780         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5781         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5782         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5783
5784         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5785
5786         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5787             p = (int) board[0][i];
5788             if(p < (int) BlackPawn) piecesLeft[p] ++;
5789             board[0][i] = EmptySquare;
5790         }
5791
5792         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5793             // shuffles restricted to allow normal castling put KRR first
5794             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5795                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5796             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5797                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5798             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5799                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5800             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5801                 put(board, WhiteRook, 0, 0, ANY);
5802             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5803         }
5804
5805         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5806             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5807             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5808                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5809                 while(piecesLeft[p] >= 2) {
5810                     AddOnePiece(board, p, 0, LITE);
5811                     AddOnePiece(board, p, 0, DARK);
5812                 }
5813                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5814             }
5815
5816         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5817             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5818             // but we leave King and Rooks for last, to possibly obey FRC restriction
5819             if(p == (int)WhiteRook) continue;
5820             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5821             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5822         }
5823
5824         // now everything is placed, except perhaps King (Unicorn) and Rooks
5825
5826         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5827             // Last King gets castling rights
5828             while(piecesLeft[(int)WhiteUnicorn]) {
5829                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5830                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5831             }
5832
5833             while(piecesLeft[(int)WhiteKing]) {
5834                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5835                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5836             }
5837
5838
5839         } else {
5840             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5841             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5842         }
5843
5844         // Only Rooks can be left; simply place them all
5845         while(piecesLeft[(int)WhiteRook]) {
5846                 i = put(board, WhiteRook, 0, 0, ANY);
5847                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5848                         if(first) {
5849                                 first=0;
5850                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5851                         }
5852                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5853                 }
5854         }
5855         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5856             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5857         }
5858
5859         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5860 }
5861
5862 int
5863 SetCharTable (char *table, const char * map)
5864 /* [HGM] moved here from winboard.c because of its general usefulness */
5865 /*       Basically a safe strcpy that uses the last character as King */
5866 {
5867     int result = FALSE; int NrPieces;
5868
5869     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5870                     && NrPieces >= 12 && !(NrPieces&1)) {
5871         int i; /* [HGM] Accept even length from 12 to 34 */
5872
5873         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5874         for( i=0; i<NrPieces/2-1; i++ ) {
5875             table[i] = map[i];
5876             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5877         }
5878         table[(int) WhiteKing]  = map[NrPieces/2-1];
5879         table[(int) BlackKing]  = map[NrPieces-1];
5880
5881         result = TRUE;
5882     }
5883
5884     return result;
5885 }
5886
5887 void
5888 Prelude (Board board)
5889 {       // [HGM] superchess: random selection of exo-pieces
5890         int i, j, k; ChessSquare p;
5891         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5892
5893         GetPositionNumber(); // use FRC position number
5894
5895         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5896             SetCharTable(pieceToChar, appData.pieceToCharTable);
5897             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5898                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5899         }
5900
5901         j = seed%4;                 seed /= 4;
5902         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3;
5906         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5907         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5908         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5909         j = seed%3;                 seed /= 3;
5910         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5911         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5912         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5913         j = seed%2 + (seed%2 >= j); seed /= 2;
5914         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5915         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5916         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5917         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5918         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5919         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5920         put(board, exoPieces[0],    0, 0, ANY);
5921         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5922 }
5923
5924 void
5925 InitPosition (int redraw)
5926 {
5927     ChessSquare (* pieces)[BOARD_FILES];
5928     int i, j, pawnRow=1, pieceRows=1, overrule,
5929     oldx = gameInfo.boardWidth,
5930     oldy = gameInfo.boardHeight,
5931     oldh = gameInfo.holdingsWidth;
5932     static int oldv;
5933
5934     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5935
5936     /* [AS] Initialize pv info list [HGM] and game status */
5937     {
5938         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5939             pvInfoList[i].depth = 0;
5940             boards[i][EP_STATUS] = EP_NONE;
5941             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5942         }
5943
5944         initialRulePlies = 0; /* 50-move counter start */
5945
5946         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5947         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5948     }
5949
5950
5951     /* [HGM] logic here is completely changed. In stead of full positions */
5952     /* the initialized data only consist of the two backranks. The switch */
5953     /* selects which one we will use, which is than copied to the Board   */
5954     /* initialPosition, which for the rest is initialized by Pawns and    */
5955     /* empty squares. This initial position is then copied to boards[0],  */
5956     /* possibly after shuffling, so that it remains available.            */
5957
5958     gameInfo.holdingsWidth = 0; /* default board sizes */
5959     gameInfo.boardWidth    = 8;
5960     gameInfo.boardHeight   = 8;
5961     gameInfo.holdingsSize  = 0;
5962     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5963     for(i=0; i<BOARD_FILES-2; i++)
5964       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5965     initialPosition[EP_STATUS] = EP_NONE;
5966     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5967     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5968          SetCharTable(pieceNickName, appData.pieceNickNames);
5969     else SetCharTable(pieceNickName, "............");
5970     pieces = FIDEArray;
5971
5972     switch (gameInfo.variant) {
5973     case VariantFischeRandom:
5974       shuffleOpenings = TRUE;
5975     default:
5976       break;
5977     case VariantShatranj:
5978       pieces = ShatranjArray;
5979       nrCastlingRights = 0;
5980       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5981       break;
5982     case VariantMakruk:
5983       pieces = makrukArray;
5984       nrCastlingRights = 0;
5985       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5986       break;
5987     case VariantASEAN:
5988       pieces = aseanArray;
5989       nrCastlingRights = 0;
5990       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5991       break;
5992     case VariantTwoKings:
5993       pieces = twoKingsArray;
5994       break;
5995     case VariantGrand:
5996       pieces = GrandArray;
5997       nrCastlingRights = 0;
5998       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5999       gameInfo.boardWidth = 10;
6000       gameInfo.boardHeight = 10;
6001       gameInfo.holdingsSize = 7;
6002       break;
6003     case VariantCapaRandom:
6004       shuffleOpenings = TRUE;
6005     case VariantCapablanca:
6006       pieces = CapablancaArray;
6007       gameInfo.boardWidth = 10;
6008       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6009       break;
6010     case VariantGothic:
6011       pieces = GothicArray;
6012       gameInfo.boardWidth = 10;
6013       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6014       break;
6015     case VariantSChess:
6016       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6017       gameInfo.holdingsSize = 7;
6018       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6019       break;
6020     case VariantJanus:
6021       pieces = JanusArray;
6022       gameInfo.boardWidth = 10;
6023       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6024       nrCastlingRights = 6;
6025         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6026         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6027         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6028         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6029         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6030         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6031       break;
6032     case VariantFalcon:
6033       pieces = FalconArray;
6034       gameInfo.boardWidth = 10;
6035       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6036       break;
6037     case VariantXiangqi:
6038       pieces = XiangqiArray;
6039       gameInfo.boardWidth  = 9;
6040       gameInfo.boardHeight = 10;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6043       break;
6044     case VariantShogi:
6045       pieces = ShogiArray;
6046       gameInfo.boardWidth  = 9;
6047       gameInfo.boardHeight = 9;
6048       gameInfo.holdingsSize = 7;
6049       nrCastlingRights = 0;
6050       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6051       break;
6052     case VariantChu:
6053       pieces = ChuArray; pieceRows = 3;
6054       gameInfo.boardWidth  = 12;
6055       gameInfo.boardHeight = 12;
6056       nrCastlingRights = 0;
6057       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6058                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6059       break;
6060     case VariantCourier:
6061       pieces = CourierArray;
6062       gameInfo.boardWidth  = 12;
6063       nrCastlingRights = 0;
6064       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6065       break;
6066     case VariantKnightmate:
6067       pieces = KnightmateArray;
6068       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6069       break;
6070     case VariantSpartan:
6071       pieces = SpartanArray;
6072       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6073       break;
6074     case VariantLion:
6075       pieces = lionArray;
6076       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6077       break;
6078     case VariantChuChess:
6079       pieces = ChuChessArray;
6080       gameInfo.boardWidth = 10;
6081       gameInfo.boardHeight = 10;
6082       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6083       break;
6084     case VariantFairy:
6085       pieces = fairyArray;
6086       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6087       break;
6088     case VariantGreat:
6089       pieces = GreatArray;
6090       gameInfo.boardWidth = 10;
6091       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6092       gameInfo.holdingsSize = 8;
6093       break;
6094     case VariantSuper:
6095       pieces = FIDEArray;
6096       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6097       gameInfo.holdingsSize = 8;
6098       startedFromSetupPosition = TRUE;
6099       break;
6100     case VariantCrazyhouse:
6101     case VariantBughouse:
6102       pieces = FIDEArray;
6103       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6104       gameInfo.holdingsSize = 5;
6105       break;
6106     case VariantWildCastle:
6107       pieces = FIDEArray;
6108       /* !!?shuffle with kings guaranteed to be on d or e file */
6109       shuffleOpenings = 1;
6110       break;
6111     case VariantNoCastle:
6112       pieces = FIDEArray;
6113       nrCastlingRights = 0;
6114       /* !!?unconstrained back-rank shuffle */
6115       shuffleOpenings = 1;
6116       break;
6117     }
6118
6119     overrule = 0;
6120     if(appData.NrFiles >= 0) {
6121         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6122         gameInfo.boardWidth = appData.NrFiles;
6123     }
6124     if(appData.NrRanks >= 0) {
6125         gameInfo.boardHeight = appData.NrRanks;
6126     }
6127     if(appData.holdingsSize >= 0) {
6128         i = appData.holdingsSize;
6129         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6130         gameInfo.holdingsSize = i;
6131     }
6132     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6133     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6134         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6135
6136     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6137     if(pawnRow < 1) pawnRow = 1;
6138     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6139        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6140     if(gameInfo.variant == VariantChu) pawnRow = 3;
6141
6142     /* User pieceToChar list overrules defaults */
6143     if(appData.pieceToCharTable != NULL)
6144         SetCharTable(pieceToChar, appData.pieceToCharTable);
6145
6146     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6147
6148         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6149             s = (ChessSquare) 0; /* account holding counts in guard band */
6150         for( i=0; i<BOARD_HEIGHT; i++ )
6151             initialPosition[i][j] = s;
6152
6153         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6154         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6155         initialPosition[pawnRow][j] = WhitePawn;
6156         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6157         if(gameInfo.variant == VariantXiangqi) {
6158             if(j&1) {
6159                 initialPosition[pawnRow][j] =
6160                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6161                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6162                    initialPosition[2][j] = WhiteCannon;
6163                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6164                 }
6165             }
6166         }
6167         if(gameInfo.variant == VariantChu) {
6168              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6169                initialPosition[pawnRow+1][j] = WhiteCobra,
6170                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6171              for(i=1; i<pieceRows; i++) {
6172                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6173                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6174              }
6175         }
6176         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6177             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6178                initialPosition[0][j] = WhiteRook;
6179                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6180             }
6181         }
6182         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6183     }
6184     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6185     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6186
6187             j=BOARD_LEFT+1;
6188             initialPosition[1][j] = WhiteBishop;
6189             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6190             j=BOARD_RGHT-2;
6191             initialPosition[1][j] = WhiteRook;
6192             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6193     }
6194
6195     if( nrCastlingRights == -1) {
6196         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6197         /*       This sets default castling rights from none to normal corners   */
6198         /* Variants with other castling rights must set them themselves above    */
6199         nrCastlingRights = 6;
6200
6201         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6202         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6203         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6204         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6205         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6206         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6207      }
6208
6209      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6210      if(gameInfo.variant == VariantGreat) { // promotion commoners
6211         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6212         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6213         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6214         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6215      }
6216      if( gameInfo.variant == VariantSChess ) {
6217       initialPosition[1][0] = BlackMarshall;
6218       initialPosition[2][0] = BlackAngel;
6219       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6220       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6221       initialPosition[1][1] = initialPosition[2][1] =
6222       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6223      }
6224   if (appData.debugMode) {
6225     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6226   }
6227     if(shuffleOpenings) {
6228         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6229         startedFromSetupPosition = TRUE;
6230     }
6231     if(startedFromPositionFile) {
6232       /* [HGM] loadPos: use PositionFile for every new game */
6233       CopyBoard(initialPosition, filePosition);
6234       for(i=0; i<nrCastlingRights; i++)
6235           initialRights[i] = filePosition[CASTLING][i];
6236       startedFromSetupPosition = TRUE;
6237     }
6238
6239     CopyBoard(boards[0], initialPosition);
6240
6241     if(oldx != gameInfo.boardWidth ||
6242        oldy != gameInfo.boardHeight ||
6243        oldv != gameInfo.variant ||
6244        oldh != gameInfo.holdingsWidth
6245                                          )
6246             InitDrawingSizes(-2 ,0);
6247
6248     oldv = gameInfo.variant;
6249     if (redraw)
6250       DrawPosition(TRUE, boards[currentMove]);
6251 }
6252
6253 void
6254 SendBoard (ChessProgramState *cps, int moveNum)
6255 {
6256     char message[MSG_SIZ];
6257
6258     if (cps->useSetboard) {
6259       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6260       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6261       SendToProgram(message, cps);
6262       free(fen);
6263
6264     } else {
6265       ChessSquare *bp;
6266       int i, j, left=0, right=BOARD_WIDTH;
6267       /* Kludge to set black to move, avoiding the troublesome and now
6268        * deprecated "black" command.
6269        */
6270       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6271         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6272
6273       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6274
6275       SendToProgram("edit\n", cps);
6276       SendToProgram("#\n", cps);
6277       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6278         bp = &boards[moveNum][i][left];
6279         for (j = left; j < right; j++, bp++) {
6280           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6281           if ((int) *bp < (int) BlackPawn) {
6282             if(j == BOARD_RGHT+1)
6283                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6284             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6285             if(message[0] == '+' || message[0] == '~') {
6286               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6287                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6288                         AAA + j, ONE + i);
6289             }
6290             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6291                 message[1] = BOARD_RGHT   - 1 - j + '1';
6292                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6293             }
6294             SendToProgram(message, cps);
6295           }
6296         }
6297       }
6298
6299       SendToProgram("c\n", cps);
6300       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6301         bp = &boards[moveNum][i][left];
6302         for (j = left; j < right; j++, bp++) {
6303           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6304           if (((int) *bp != (int) EmptySquare)
6305               && ((int) *bp >= (int) BlackPawn)) {
6306             if(j == BOARD_LEFT-2)
6307                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6308             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6309                     AAA + j, ONE + i);
6310             if(message[0] == '+' || message[0] == '~') {
6311               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6312                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6313                         AAA + j, ONE + i);
6314             }
6315             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6316                 message[1] = BOARD_RGHT   - 1 - j + '1';
6317                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6318             }
6319             SendToProgram(message, cps);
6320           }
6321         }
6322       }
6323
6324       SendToProgram(".\n", cps);
6325     }
6326     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6327 }
6328
6329 char exclusionHeader[MSG_SIZ];
6330 int exCnt, excludePtr;
6331 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6332 static Exclusion excluTab[200];
6333 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6334
6335 static void
6336 WriteMap (int s)
6337 {
6338     int j;
6339     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6340     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6341 }
6342
6343 static void
6344 ClearMap ()
6345 {
6346     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6347     excludePtr = 24; exCnt = 0;
6348     WriteMap(0);
6349 }
6350
6351 static void
6352 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6353 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6354     char buf[2*MOVE_LEN], *p;
6355     Exclusion *e = excluTab;
6356     int i;
6357     for(i=0; i<exCnt; i++)
6358         if(e[i].ff == fromX && e[i].fr == fromY &&
6359            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6360     if(i == exCnt) { // was not in exclude list; add it
6361         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6362         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6363             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6364             return; // abort
6365         }
6366         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6367         excludePtr++; e[i].mark = excludePtr++;
6368         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6369         exCnt++;
6370     }
6371     exclusionHeader[e[i].mark] = state;
6372 }
6373
6374 static int
6375 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6376 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6377     char buf[MSG_SIZ];
6378     int j, k;
6379     ChessMove moveType;
6380     if((signed char)promoChar == -1) { // kludge to indicate best move
6381         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6382             return 1; // if unparsable, abort
6383     }
6384     // update exclusion map (resolving toggle by consulting existing state)
6385     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6386     j = k%8; k >>= 3;
6387     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6388     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6389          excludeMap[k] |=   1<<j;
6390     else excludeMap[k] &= ~(1<<j);
6391     // update header
6392     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6393     // inform engine
6394     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6395     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6396     SendToBoth(buf);
6397     return (state == '+');
6398 }
6399
6400 static void
6401 ExcludeClick (int index)
6402 {
6403     int i, j;
6404     Exclusion *e = excluTab;
6405     if(index < 25) { // none, best or tail clicked
6406         if(index < 13) { // none: include all
6407             WriteMap(0); // clear map
6408             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6409             SendToBoth("include all\n"); // and inform engine
6410         } else if(index > 18) { // tail
6411             if(exclusionHeader[19] == '-') { // tail was excluded
6412                 SendToBoth("include all\n");
6413                 WriteMap(0); // clear map completely
6414                 // now re-exclude selected moves
6415                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6416                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6417             } else { // tail was included or in mixed state
6418                 SendToBoth("exclude all\n");
6419                 WriteMap(0xFF); // fill map completely
6420                 // now re-include selected moves
6421                 j = 0; // count them
6422                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6423                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6424                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6425             }
6426         } else { // best
6427             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6428         }
6429     } else {
6430         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6431             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6432             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6433             break;
6434         }
6435     }
6436 }
6437
6438 ChessSquare
6439 DefaultPromoChoice (int white)
6440 {
6441     ChessSquare result;
6442     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6443        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6444         result = WhiteFerz; // no choice
6445     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6446         result= WhiteKing; // in Suicide Q is the last thing we want
6447     else if(gameInfo.variant == VariantSpartan)
6448         result = white ? WhiteQueen : WhiteAngel;
6449     else result = WhiteQueen;
6450     if(!white) result = WHITE_TO_BLACK result;
6451     return result;
6452 }
6453
6454 static int autoQueen; // [HGM] oneclick
6455
6456 int
6457 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6458 {
6459     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6460     /* [HGM] add Shogi promotions */
6461     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6462     ChessSquare piece, partner;
6463     ChessMove moveType;
6464     Boolean premove;
6465
6466     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6467     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6468
6469     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6470       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6471         return FALSE;
6472
6473     piece = boards[currentMove][fromY][fromX];
6474     if(gameInfo.variant == VariantChu) {
6475         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6476         promotionZoneSize = BOARD_HEIGHT/3;
6477         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6478     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6479         promotionZoneSize = BOARD_HEIGHT/3;
6480         highestPromotingPiece = (int)WhiteAlfil;
6481     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6482         promotionZoneSize = 3;
6483     }
6484
6485     // Treat Lance as Pawn when it is not representing Amazon
6486     if(gameInfo.variant != VariantSuper) {
6487         if(piece == WhiteLance) piece = WhitePawn; else
6488         if(piece == BlackLance) piece = BlackPawn;
6489     }
6490
6491     // next weed out all moves that do not touch the promotion zone at all
6492     if((int)piece >= BlackPawn) {
6493         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6494              return FALSE;
6495         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6496         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6497     } else {
6498         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6499            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6500         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6501              return FALSE;
6502     }
6503
6504     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6505
6506     // weed out mandatory Shogi promotions
6507     if(gameInfo.variant == VariantShogi) {
6508         if(piece >= BlackPawn) {
6509             if(toY == 0 && piece == BlackPawn ||
6510                toY == 0 && piece == BlackQueen ||
6511                toY <= 1 && piece == BlackKnight) {
6512                 *promoChoice = '+';
6513                 return FALSE;
6514             }
6515         } else {
6516             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6517                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6518                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6519                 *promoChoice = '+';
6520                 return FALSE;
6521             }
6522         }
6523     }
6524
6525     // weed out obviously illegal Pawn moves
6526     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6527         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6528         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6529         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6530         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6531         // note we are not allowed to test for valid (non-)capture, due to premove
6532     }
6533
6534     // we either have a choice what to promote to, or (in Shogi) whether to promote
6535     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6536        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6537         *promoChoice = PieceToChar(BlackFerz);  // no choice
6538         return FALSE;
6539     }
6540     // no sense asking what we must promote to if it is going to explode...
6541     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6542         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6543         return FALSE;
6544     }
6545     // give caller the default choice even if we will not make it
6546     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6547     partner = piece; // pieces can promote if the pieceToCharTable says so
6548     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6549     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6550     if(        sweepSelect && gameInfo.variant != VariantGreat
6551                            && gameInfo.variant != VariantGrand
6552                            && gameInfo.variant != VariantSuper) return FALSE;
6553     if(autoQueen) return FALSE; // predetermined
6554
6555     // suppress promotion popup on illegal moves that are not premoves
6556     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6557               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6558     if(appData.testLegality && !premove) {
6559         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6560                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6561         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6562             return FALSE;
6563     }
6564
6565     return TRUE;
6566 }
6567
6568 int
6569 InPalace (int row, int column)
6570 {   /* [HGM] for Xiangqi */
6571     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6572          column < (BOARD_WIDTH + 4)/2 &&
6573          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6574     return FALSE;
6575 }
6576
6577 int
6578 PieceForSquare (int x, int y)
6579 {
6580   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6581      return -1;
6582   else
6583      return boards[currentMove][y][x];
6584 }
6585
6586 int
6587 OKToStartUserMove (int x, int y)
6588 {
6589     ChessSquare from_piece;
6590     int white_piece;
6591
6592     if (matchMode) return FALSE;
6593     if (gameMode == EditPosition) return TRUE;
6594
6595     if (x >= 0 && y >= 0)
6596       from_piece = boards[currentMove][y][x];
6597     else
6598       from_piece = EmptySquare;
6599
6600     if (from_piece == EmptySquare) return FALSE;
6601
6602     white_piece = (int)from_piece >= (int)WhitePawn &&
6603       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6604
6605     switch (gameMode) {
6606       case AnalyzeFile:
6607       case TwoMachinesPlay:
6608       case EndOfGame:
6609         return FALSE;
6610
6611       case IcsObserving:
6612       case IcsIdle:
6613         return FALSE;
6614
6615       case MachinePlaysWhite:
6616       case IcsPlayingBlack:
6617         if (appData.zippyPlay) return FALSE;
6618         if (white_piece) {
6619             DisplayMoveError(_("You are playing Black"));
6620             return FALSE;
6621         }
6622         break;
6623
6624       case MachinePlaysBlack:
6625       case IcsPlayingWhite:
6626         if (appData.zippyPlay) return FALSE;
6627         if (!white_piece) {
6628             DisplayMoveError(_("You are playing White"));
6629             return FALSE;
6630         }
6631         break;
6632
6633       case PlayFromGameFile:
6634             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6635       case EditGame:
6636         if (!white_piece && WhiteOnMove(currentMove)) {
6637             DisplayMoveError(_("It is White's turn"));
6638             return FALSE;
6639         }
6640         if (white_piece && !WhiteOnMove(currentMove)) {
6641             DisplayMoveError(_("It is Black's turn"));
6642             return FALSE;
6643         }
6644         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6645             /* Editing correspondence game history */
6646             /* Could disallow this or prompt for confirmation */
6647             cmailOldMove = -1;
6648         }
6649         break;
6650
6651       case BeginningOfGame:
6652         if (appData.icsActive) return FALSE;
6653         if (!appData.noChessProgram) {
6654             if (!white_piece) {
6655                 DisplayMoveError(_("You are playing White"));
6656                 return FALSE;
6657             }
6658         }
6659         break;
6660
6661       case Training:
6662         if (!white_piece && WhiteOnMove(currentMove)) {
6663             DisplayMoveError(_("It is White's turn"));
6664             return FALSE;
6665         }
6666         if (white_piece && !WhiteOnMove(currentMove)) {
6667             DisplayMoveError(_("It is Black's turn"));
6668             return FALSE;
6669         }
6670         break;
6671
6672       default:
6673       case IcsExamining:
6674         break;
6675     }
6676     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6677         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6678         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6679         && gameMode != AnalyzeFile && gameMode != Training) {
6680         DisplayMoveError(_("Displayed position is not current"));
6681         return FALSE;
6682     }
6683     return TRUE;
6684 }
6685
6686 Boolean
6687 OnlyMove (int *x, int *y, Boolean captures)
6688 {
6689     DisambiguateClosure cl;
6690     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6691     switch(gameMode) {
6692       case MachinePlaysBlack:
6693       case IcsPlayingWhite:
6694       case BeginningOfGame:
6695         if(!WhiteOnMove(currentMove)) return FALSE;
6696         break;
6697       case MachinePlaysWhite:
6698       case IcsPlayingBlack:
6699         if(WhiteOnMove(currentMove)) return FALSE;
6700         break;
6701       case EditGame:
6702         break;
6703       default:
6704         return FALSE;
6705     }
6706     cl.pieceIn = EmptySquare;
6707     cl.rfIn = *y;
6708     cl.ffIn = *x;
6709     cl.rtIn = -1;
6710     cl.ftIn = -1;
6711     cl.promoCharIn = NULLCHAR;
6712     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6713     if( cl.kind == NormalMove ||
6714         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6715         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6716         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6717       fromX = cl.ff;
6718       fromY = cl.rf;
6719       *x = cl.ft;
6720       *y = cl.rt;
6721       return TRUE;
6722     }
6723     if(cl.kind != ImpossibleMove) return FALSE;
6724     cl.pieceIn = EmptySquare;
6725     cl.rfIn = -1;
6726     cl.ffIn = -1;
6727     cl.rtIn = *y;
6728     cl.ftIn = *x;
6729     cl.promoCharIn = NULLCHAR;
6730     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6731     if( cl.kind == NormalMove ||
6732         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6733         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6734         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6735       fromX = cl.ff;
6736       fromY = cl.rf;
6737       *x = cl.ft;
6738       *y = cl.rt;
6739       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6740       return TRUE;
6741     }
6742     return FALSE;
6743 }
6744
6745 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6746 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6747 int lastLoadGameUseList = FALSE;
6748 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6749 ChessMove lastLoadGameStart = EndOfFile;
6750 int doubleClick;
6751
6752 void
6753 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6754 {
6755     ChessMove moveType;
6756     ChessSquare pup;
6757     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6758
6759     /* Check if the user is playing in turn.  This is complicated because we
6760        let the user "pick up" a piece before it is his turn.  So the piece he
6761        tried to pick up may have been captured by the time he puts it down!
6762        Therefore we use the color the user is supposed to be playing in this
6763        test, not the color of the piece that is currently on the starting
6764        square---except in EditGame mode, where the user is playing both
6765        sides; fortunately there the capture race can't happen.  (It can
6766        now happen in IcsExamining mode, but that's just too bad.  The user
6767        will get a somewhat confusing message in that case.)
6768        */
6769
6770     switch (gameMode) {
6771       case AnalyzeFile:
6772       case TwoMachinesPlay:
6773       case EndOfGame:
6774       case IcsObserving:
6775       case IcsIdle:
6776         /* We switched into a game mode where moves are not accepted,
6777            perhaps while the mouse button was down. */
6778         return;
6779
6780       case MachinePlaysWhite:
6781         /* User is moving for Black */
6782         if (WhiteOnMove(currentMove)) {
6783             DisplayMoveError(_("It is White's turn"));
6784             return;
6785         }
6786         break;
6787
6788       case MachinePlaysBlack:
6789         /* User is moving for White */
6790         if (!WhiteOnMove(currentMove)) {
6791             DisplayMoveError(_("It is Black's turn"));
6792             return;
6793         }
6794         break;
6795
6796       case PlayFromGameFile:
6797             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6798       case EditGame:
6799       case IcsExamining:
6800       case BeginningOfGame:
6801       case AnalyzeMode:
6802       case Training:
6803         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6804         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6805             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6806             /* User is moving for Black */
6807             if (WhiteOnMove(currentMove)) {
6808                 DisplayMoveError(_("It is White's turn"));
6809                 return;
6810             }
6811         } else {
6812             /* User is moving for White */
6813             if (!WhiteOnMove(currentMove)) {
6814                 DisplayMoveError(_("It is Black's turn"));
6815                 return;
6816             }
6817         }
6818         break;
6819
6820       case IcsPlayingBlack:
6821         /* User is moving for Black */
6822         if (WhiteOnMove(currentMove)) {
6823             if (!appData.premove) {
6824                 DisplayMoveError(_("It is White's turn"));
6825             } else if (toX >= 0 && toY >= 0) {
6826                 premoveToX = toX;
6827                 premoveToY = toY;
6828                 premoveFromX = fromX;
6829                 premoveFromY = fromY;
6830                 premovePromoChar = promoChar;
6831                 gotPremove = 1;
6832                 if (appData.debugMode)
6833                     fprintf(debugFP, "Got premove: fromX %d,"
6834                             "fromY %d, toX %d, toY %d\n",
6835                             fromX, fromY, toX, toY);
6836             }
6837             return;
6838         }
6839         break;
6840
6841       case IcsPlayingWhite:
6842         /* User is moving for White */
6843         if (!WhiteOnMove(currentMove)) {
6844             if (!appData.premove) {
6845                 DisplayMoveError(_("It is Black's turn"));
6846             } else if (toX >= 0 && toY >= 0) {
6847                 premoveToX = toX;
6848                 premoveToY = toY;
6849                 premoveFromX = fromX;
6850                 premoveFromY = fromY;
6851                 premovePromoChar = promoChar;
6852                 gotPremove = 1;
6853                 if (appData.debugMode)
6854                     fprintf(debugFP, "Got premove: fromX %d,"
6855                             "fromY %d, toX %d, toY %d\n",
6856                             fromX, fromY, toX, toY);
6857             }
6858             return;
6859         }
6860         break;
6861
6862       default:
6863         break;
6864
6865       case EditPosition:
6866         /* EditPosition, empty square, or different color piece;
6867            click-click move is possible */
6868         if (toX == -2 || toY == -2) {
6869             boards[0][fromY][fromX] = EmptySquare;
6870             DrawPosition(FALSE, boards[currentMove]);
6871             return;
6872         } else if (toX >= 0 && toY >= 0) {
6873             boards[0][toY][toX] = boards[0][fromY][fromX];
6874             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6875                 if(boards[0][fromY][0] != EmptySquare) {
6876                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6877                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6878                 }
6879             } else
6880             if(fromX == BOARD_RGHT+1) {
6881                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6882                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6883                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6884                 }
6885             } else
6886             boards[0][fromY][fromX] = gatingPiece;
6887             DrawPosition(FALSE, boards[currentMove]);
6888             return;
6889         }
6890         return;
6891     }
6892
6893     if(toX < 0 || toY < 0) return;
6894     pup = boards[currentMove][toY][toX];
6895
6896     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6897     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6898          if( pup != EmptySquare ) return;
6899          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6900            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6901                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6902            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6903            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6904            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6905            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6906          fromY = DROP_RANK;
6907     }
6908
6909     /* [HGM] always test for legality, to get promotion info */
6910     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6911                                          fromY, fromX, toY, toX, promoChar);
6912
6913     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6914
6915     /* [HGM] but possibly ignore an IllegalMove result */
6916     if (appData.testLegality) {
6917         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6918             DisplayMoveError(_("Illegal move"));
6919             return;
6920         }
6921     }
6922
6923     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6924         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6925              ClearPremoveHighlights(); // was included
6926         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6927         return;
6928     }
6929
6930     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6931 }
6932
6933 /* Common tail of UserMoveEvent and DropMenuEvent */
6934 int
6935 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6936 {
6937     char *bookHit = 0;
6938
6939     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6940         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6941         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6942         if(WhiteOnMove(currentMove)) {
6943             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6944         } else {
6945             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6946         }
6947     }
6948
6949     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6950        move type in caller when we know the move is a legal promotion */
6951     if(moveType == NormalMove && promoChar)
6952         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6953
6954     /* [HGM] <popupFix> The following if has been moved here from
6955        UserMoveEvent(). Because it seemed to belong here (why not allow
6956        piece drops in training games?), and because it can only be
6957        performed after it is known to what we promote. */
6958     if (gameMode == Training) {
6959       /* compare the move played on the board to the next move in the
6960        * game. If they match, display the move and the opponent's response.
6961        * If they don't match, display an error message.
6962        */
6963       int saveAnimate;
6964       Board testBoard;
6965       CopyBoard(testBoard, boards[currentMove]);
6966       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6967
6968       if (CompareBoards(testBoard, boards[currentMove+1])) {
6969         ForwardInner(currentMove+1);
6970
6971         /* Autoplay the opponent's response.
6972          * if appData.animate was TRUE when Training mode was entered,
6973          * the response will be animated.
6974          */
6975         saveAnimate = appData.animate;
6976         appData.animate = animateTraining;
6977         ForwardInner(currentMove+1);
6978         appData.animate = saveAnimate;
6979
6980         /* check for the end of the game */
6981         if (currentMove >= forwardMostMove) {
6982           gameMode = PlayFromGameFile;
6983           ModeHighlight();
6984           SetTrainingModeOff();
6985           DisplayInformation(_("End of game"));
6986         }
6987       } else {
6988         DisplayError(_("Incorrect move"), 0);
6989       }
6990       return 1;
6991     }
6992
6993   /* Ok, now we know that the move is good, so we can kill
6994      the previous line in Analysis Mode */
6995   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6996                                 && currentMove < forwardMostMove) {
6997     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6998     else forwardMostMove = currentMove;
6999   }
7000
7001   ClearMap();
7002
7003   /* If we need the chess program but it's dead, restart it */
7004   ResurrectChessProgram();
7005
7006   /* A user move restarts a paused game*/
7007   if (pausing)
7008     PauseEvent();
7009
7010   thinkOutput[0] = NULLCHAR;
7011
7012   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7013
7014   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7015     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7016     return 1;
7017   }
7018
7019   if (gameMode == BeginningOfGame) {
7020     if (appData.noChessProgram) {
7021       gameMode = EditGame;
7022       SetGameInfo();
7023     } else {
7024       char buf[MSG_SIZ];
7025       gameMode = MachinePlaysBlack;
7026       StartClocks();
7027       SetGameInfo();
7028       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7029       DisplayTitle(buf);
7030       if (first.sendName) {
7031         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7032         SendToProgram(buf, &first);
7033       }
7034       StartClocks();
7035     }
7036     ModeHighlight();
7037   }
7038
7039   /* Relay move to ICS or chess engine */
7040   if (appData.icsActive) {
7041     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7042         gameMode == IcsExamining) {
7043       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7044         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7045         SendToICS("draw ");
7046         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7047       }
7048       // also send plain move, in case ICS does not understand atomic claims
7049       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7050       ics_user_moved = 1;
7051     }
7052   } else {
7053     if (first.sendTime && (gameMode == BeginningOfGame ||
7054                            gameMode == MachinePlaysWhite ||
7055                            gameMode == MachinePlaysBlack)) {
7056       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7057     }
7058     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7059          // [HGM] book: if program might be playing, let it use book
7060         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7061         first.maybeThinking = TRUE;
7062     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7063         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7064         SendBoard(&first, currentMove+1);
7065         if(second.analyzing) {
7066             if(!second.useSetboard) SendToProgram("undo\n", &second);
7067             SendBoard(&second, currentMove+1);
7068         }
7069     } else {
7070         SendMoveToProgram(forwardMostMove-1, &first);
7071         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7072     }
7073     if (currentMove == cmailOldMove + 1) {
7074       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7075     }
7076   }
7077
7078   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7079
7080   switch (gameMode) {
7081   case EditGame:
7082     if(appData.testLegality)
7083     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7084     case MT_NONE:
7085     case MT_CHECK:
7086       break;
7087     case MT_CHECKMATE:
7088     case MT_STAINMATE:
7089       if (WhiteOnMove(currentMove)) {
7090         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7091       } else {
7092         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7093       }
7094       break;
7095     case MT_STALEMATE:
7096       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7097       break;
7098     }
7099     break;
7100
7101   case MachinePlaysBlack:
7102   case MachinePlaysWhite:
7103     /* disable certain menu options while machine is thinking */
7104     SetMachineThinkingEnables();
7105     break;
7106
7107   default:
7108     break;
7109   }
7110
7111   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7112   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7113
7114   if(bookHit) { // [HGM] book: simulate book reply
7115         static char bookMove[MSG_SIZ]; // a bit generous?
7116
7117         programStats.nodes = programStats.depth = programStats.time =
7118         programStats.score = programStats.got_only_move = 0;
7119         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7120
7121         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7122         strcat(bookMove, bookHit);
7123         HandleMachineMove(bookMove, &first);
7124   }
7125   return 1;
7126 }
7127
7128 void
7129 MarkByFEN(char *fen)
7130 {
7131         int r, f;
7132         if(!appData.markers || !appData.highlightDragging) return;
7133         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7134         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7135         while(*fen) {
7136             int s = 0;
7137             marker[r][f] = 0;
7138             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7139             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7140             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7141             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7142             if(*fen == 'T') marker[r][f++] = 0; else
7143             if(*fen == 'Y') marker[r][f++] = 1; else
7144             if(*fen == 'G') marker[r][f++] = 3; else
7145             if(*fen == 'B') marker[r][f++] = 4; else
7146             if(*fen == 'C') marker[r][f++] = 5; else
7147             if(*fen == 'M') marker[r][f++] = 6; else
7148             if(*fen == 'W') marker[r][f++] = 7; else
7149             if(*fen == 'D') marker[r][f++] = 8; else
7150             if(*fen == 'R') marker[r][f++] = 2; else {
7151                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7152               f += s; fen -= s>0;
7153             }
7154             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7155             if(r < 0) break;
7156             fen++;
7157         }
7158         DrawPosition(TRUE, NULL);
7159 }
7160
7161 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7162
7163 void
7164 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7165 {
7166     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7167     Markers *m = (Markers *) closure;
7168     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7169         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7170                          || kind == WhiteCapturesEnPassant
7171                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7172     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7173 }
7174
7175 void
7176 MarkTargetSquares (int clear)
7177 {
7178   int x, y, sum=0;
7179   if(clear) { // no reason to ever suppress clearing
7180     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;
7181     if(!sum) return; // nothing was cleared,no redraw needed
7182   } else {
7183     int capt = 0;
7184     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7185        !appData.testLegality || gameMode == EditPosition) return;
7186     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7187     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7188       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7189       if(capt)
7190       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7191     }
7192   }
7193   DrawPosition(FALSE, NULL);
7194 }
7195
7196 int
7197 Explode (Board board, int fromX, int fromY, int toX, int toY)
7198 {
7199     if(gameInfo.variant == VariantAtomic &&
7200        (board[toY][toX] != EmptySquare ||                     // capture?
7201         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7202                          board[fromY][fromX] == BlackPawn   )
7203       )) {
7204         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7205         return TRUE;
7206     }
7207     return FALSE;
7208 }
7209
7210 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7211
7212 int
7213 CanPromote (ChessSquare piece, int y)
7214 {
7215         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7216         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7217         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7218         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7219            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7220            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7221          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7222         return (piece == BlackPawn && y <= zone ||
7223                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7224                 piece == BlackLance && y == 1 ||
7225                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7226 }
7227
7228 void
7229 HoverEvent (int xPix, int yPix, int x, int y)
7230 {
7231         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7232         int r, f;
7233         if(!first.highlight) return;
7234         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7235         if(x == oldX && y == oldY) return; // only do something if we enter new square
7236         oldFromX = fromX; oldFromY = fromY;
7237         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7238           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7239             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7240         else if(oldX != x || oldY != y) {
7241           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7242           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7243             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7244           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7245             char buf[MSG_SIZ];
7246             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7247             SendToProgram(buf, &first);
7248           }
7249           oldX = x; oldY = y;
7250 //        SetHighlights(fromX, fromY, x, y);
7251         }
7252 }
7253
7254 void ReportClick(char *action, int x, int y)
7255 {
7256         char buf[MSG_SIZ]; // Inform engine of what user does
7257         int r, f;
7258         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7259           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7260         if(!first.highlight || gameMode == EditPosition) return;
7261         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7262         SendToProgram(buf, &first);
7263 }
7264
7265 void
7266 LeftClick (ClickType clickType, int xPix, int yPix)
7267 {
7268     int x, y;
7269     Boolean saveAnimate;
7270     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7271     char promoChoice = NULLCHAR;
7272     ChessSquare piece;
7273     static TimeMark lastClickTime, prevClickTime;
7274
7275     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7276
7277     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7278
7279     if (clickType == Press) ErrorPopDown();
7280     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7281
7282     x = EventToSquare(xPix, BOARD_WIDTH);
7283     y = EventToSquare(yPix, BOARD_HEIGHT);
7284     if (!flipView && y >= 0) {
7285         y = BOARD_HEIGHT - 1 - y;
7286     }
7287     if (flipView && x >= 0) {
7288         x = BOARD_WIDTH - 1 - x;
7289     }
7290
7291     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7292         defaultPromoChoice = promoSweep;
7293         promoSweep = EmptySquare;   // terminate sweep
7294         promoDefaultAltered = TRUE;
7295         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7296     }
7297
7298     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7299         if(clickType == Release) return; // ignore upclick of click-click destination
7300         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7301         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7302         if(gameInfo.holdingsWidth &&
7303                 (WhiteOnMove(currentMove)
7304                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7305                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7306             // click in right holdings, for determining promotion piece
7307             ChessSquare p = boards[currentMove][y][x];
7308             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7309             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7310             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7311                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7312                 fromX = fromY = -1;
7313                 return;
7314             }
7315         }
7316         DrawPosition(FALSE, boards[currentMove]);
7317         return;
7318     }
7319
7320     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7321     if(clickType == Press
7322             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7323               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7324               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7325         return;
7326
7327     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7328         // could be static click on premove from-square: abort premove
7329         gotPremove = 0;
7330         ClearPremoveHighlights();
7331     }
7332
7333     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7334         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7335
7336     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7337         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7338                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7339         defaultPromoChoice = DefaultPromoChoice(side);
7340     }
7341
7342     autoQueen = appData.alwaysPromoteToQueen;
7343
7344     if (fromX == -1) {
7345       int originalY = y;
7346       gatingPiece = EmptySquare;
7347       if (clickType != Press) {
7348         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7349             DragPieceEnd(xPix, yPix); dragging = 0;
7350             DrawPosition(FALSE, NULL);
7351         }
7352         return;
7353       }
7354       doubleClick = FALSE;
7355       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7356         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7357       }
7358       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7359       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7360          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7361          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7362             /* First square */
7363             if (OKToStartUserMove(fromX, fromY)) {
7364                 second = 0;
7365                 ReportClick("lift", x, y);
7366                 MarkTargetSquares(0);
7367                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7368                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7369                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7370                     promoSweep = defaultPromoChoice;
7371                     selectFlag = 0; lastX = xPix; lastY = yPix;
7372                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7373                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7374                 }
7375                 if (appData.highlightDragging) {
7376                     SetHighlights(fromX, fromY, -1, -1);
7377                 } else {
7378                     ClearHighlights();
7379                 }
7380             } else fromX = fromY = -1;
7381             return;
7382         }
7383     }
7384
7385     /* fromX != -1 */
7386     if (clickType == Press && gameMode != EditPosition) {
7387         ChessSquare fromP;
7388         ChessSquare toP;
7389         int frc;
7390
7391         // ignore off-board to clicks
7392         if(y < 0 || x < 0) return;
7393
7394         /* Check if clicking again on the same color piece */
7395         fromP = boards[currentMove][fromY][fromX];
7396         toP = boards[currentMove][y][x];
7397         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7398         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7399            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7400              WhitePawn <= toP && toP <= WhiteKing &&
7401              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7402              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7403             (BlackPawn <= fromP && fromP <= BlackKing &&
7404              BlackPawn <= toP && toP <= BlackKing &&
7405              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7406              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7407             /* Clicked again on same color piece -- changed his mind */
7408             second = (x == fromX && y == fromY);
7409             killX = killY = -1;
7410             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7411                 second = FALSE; // first double-click rather than scond click
7412                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7413             }
7414             promoDefaultAltered = FALSE;
7415             MarkTargetSquares(1);
7416            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7417             if (appData.highlightDragging) {
7418                 SetHighlights(x, y, -1, -1);
7419             } else {
7420                 ClearHighlights();
7421             }
7422             if (OKToStartUserMove(x, y)) {
7423                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7424                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7425                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7426                  gatingPiece = boards[currentMove][fromY][fromX];
7427                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7428                 fromX = x;
7429                 fromY = y; dragging = 1;
7430                 ReportClick("lift", x, y);
7431                 MarkTargetSquares(0);
7432                 DragPieceBegin(xPix, yPix, FALSE);
7433                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7434                     promoSweep = defaultPromoChoice;
7435                     selectFlag = 0; lastX = xPix; lastY = yPix;
7436                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7437                 }
7438             }
7439            }
7440            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7441            second = FALSE;
7442         }
7443         // ignore clicks on holdings
7444         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7445     }
7446
7447     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7448         DragPieceEnd(xPix, yPix); dragging = 0;
7449         if(clearFlag) {
7450             // a deferred attempt to click-click move an empty square on top of a piece
7451             boards[currentMove][y][x] = EmptySquare;
7452             ClearHighlights();
7453             DrawPosition(FALSE, boards[currentMove]);
7454             fromX = fromY = -1; clearFlag = 0;
7455             return;
7456         }
7457         if (appData.animateDragging) {
7458             /* Undo animation damage if any */
7459             DrawPosition(FALSE, NULL);
7460         }
7461         if (second || sweepSelecting) {
7462             /* Second up/down in same square; just abort move */
7463             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7464             second = sweepSelecting = 0;
7465             fromX = fromY = -1;
7466             gatingPiece = EmptySquare;
7467             MarkTargetSquares(1);
7468             ClearHighlights();
7469             gotPremove = 0;
7470             ClearPremoveHighlights();
7471         } else {
7472             /* First upclick in same square; start click-click mode */
7473             SetHighlights(x, y, -1, -1);
7474         }
7475         return;
7476     }
7477
7478     clearFlag = 0;
7479
7480     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7481         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7482         DisplayMessage(_("only marked squares are legal"),"");
7483         DrawPosition(TRUE, NULL);
7484         return; // ignore to-click
7485     }
7486
7487     /* we now have a different from- and (possibly off-board) to-square */
7488     /* Completed move */
7489     if(!sweepSelecting) {
7490         toX = x;
7491         toY = y;
7492     }
7493
7494     saveAnimate = appData.animate;
7495     if (clickType == Press) {
7496         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7497             // must be Edit Position mode with empty-square selected
7498             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7499             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7500             return;
7501         }
7502         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7503             return;
7504         }
7505         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7506             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7507         } else
7508         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7509         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7510           if(appData.sweepSelect) {
7511             ChessSquare piece = boards[currentMove][fromY][fromX];
7512             promoSweep = defaultPromoChoice;
7513             if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) promoSweep = piece; else
7514             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7515             selectFlag = 0; lastX = xPix; lastY = yPix;
7516             Sweep(0); // Pawn that is going to promote: preview promotion piece
7517             sweepSelecting = 1;
7518             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7519             MarkTargetSquares(1);
7520           }
7521           return; // promo popup appears on up-click
7522         }
7523         /* Finish clickclick move */
7524         if (appData.animate || appData.highlightLastMove) {
7525             SetHighlights(fromX, fromY, toX, toY);
7526         } else {
7527             ClearHighlights();
7528         }
7529     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7530         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7531         if (appData.animate || appData.highlightLastMove) {
7532             SetHighlights(fromX, fromY, toX, toY);
7533         } else {
7534             ClearHighlights();
7535         }
7536     } else {
7537 #if 0
7538 // [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
7539         /* Finish drag move */
7540         if (appData.highlightLastMove) {
7541             SetHighlights(fromX, fromY, toX, toY);
7542         } else {
7543             ClearHighlights();
7544         }
7545 #endif
7546         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7547           dragging *= 2;            // flag button-less dragging if we are dragging
7548           MarkTargetSquares(1);
7549           if(x == killX && y == killY) killX = killY = -1; else {
7550             killX = x; killY = y;     //remeber this square as intermediate
7551             ReportClick("put", x, y); // and inform engine
7552             ReportClick("lift", x, y);
7553             MarkTargetSquares(0);
7554             return;
7555           }
7556         }
7557         DragPieceEnd(xPix, yPix); dragging = 0;
7558         /* Don't animate move and drag both */
7559         appData.animate = FALSE;
7560     }
7561
7562     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7563     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7564         ChessSquare piece = boards[currentMove][fromY][fromX];
7565         if(gameMode == EditPosition && piece != EmptySquare &&
7566            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7567             int n;
7568
7569             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7570                 n = PieceToNumber(piece - (int)BlackPawn);
7571                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7572                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7573                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7574             } else
7575             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7576                 n = PieceToNumber(piece);
7577                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7578                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7579                 boards[currentMove][n][BOARD_WIDTH-2]++;
7580             }
7581             boards[currentMove][fromY][fromX] = EmptySquare;
7582         }
7583         ClearHighlights();
7584         fromX = fromY = -1;
7585         MarkTargetSquares(1);
7586         DrawPosition(TRUE, boards[currentMove]);
7587         return;
7588     }
7589
7590     // off-board moves should not be highlighted
7591     if(x < 0 || y < 0) ClearHighlights();
7592     else ReportClick("put", x, y);
7593
7594     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7595
7596     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7597         SetHighlights(fromX, fromY, toX, toY);
7598         MarkTargetSquares(1);
7599         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7600             // [HGM] super: promotion to captured piece selected from holdings
7601             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7602             promotionChoice = TRUE;
7603             // kludge follows to temporarily execute move on display, without promoting yet
7604             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7605             boards[currentMove][toY][toX] = p;
7606             DrawPosition(FALSE, boards[currentMove]);
7607             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7608             boards[currentMove][toY][toX] = q;
7609             DisplayMessage("Click in holdings to choose piece", "");
7610             return;
7611         }
7612         PromotionPopUp(promoChoice);
7613     } else {
7614         int oldMove = currentMove;
7615         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7616         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7617         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7618         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7619            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7620             DrawPosition(TRUE, boards[currentMove]);
7621         MarkTargetSquares(1);
7622         fromX = fromY = -1;
7623     }
7624     appData.animate = saveAnimate;
7625     if (appData.animate || appData.animateDragging) {
7626         /* Undo animation damage if needed */
7627         DrawPosition(FALSE, NULL);
7628     }
7629 }
7630
7631 int
7632 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7633 {   // front-end-free part taken out of PieceMenuPopup
7634     int whichMenu; int xSqr, ySqr;
7635
7636     if(seekGraphUp) { // [HGM] seekgraph
7637         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7638         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7639         return -2;
7640     }
7641
7642     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7643          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7644         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7645         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7646         if(action == Press)   {
7647             originalFlip = flipView;
7648             flipView = !flipView; // temporarily flip board to see game from partners perspective
7649             DrawPosition(TRUE, partnerBoard);
7650             DisplayMessage(partnerStatus, "");
7651             partnerUp = TRUE;
7652         } else if(action == Release) {
7653             flipView = originalFlip;
7654             DrawPosition(TRUE, boards[currentMove]);
7655             partnerUp = FALSE;
7656         }
7657         return -2;
7658     }
7659
7660     xSqr = EventToSquare(x, BOARD_WIDTH);
7661     ySqr = EventToSquare(y, BOARD_HEIGHT);
7662     if (action == Release) {
7663         if(pieceSweep != EmptySquare) {
7664             EditPositionMenuEvent(pieceSweep, toX, toY);
7665             pieceSweep = EmptySquare;
7666         } else UnLoadPV(); // [HGM] pv
7667     }
7668     if (action != Press) return -2; // return code to be ignored
7669     switch (gameMode) {
7670       case IcsExamining:
7671         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7672       case EditPosition:
7673         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7674         if (xSqr < 0 || ySqr < 0) return -1;
7675         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7676         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7677         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7678         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7679         NextPiece(0);
7680         return 2; // grab
7681       case IcsObserving:
7682         if(!appData.icsEngineAnalyze) return -1;
7683       case IcsPlayingWhite:
7684       case IcsPlayingBlack:
7685         if(!appData.zippyPlay) goto noZip;
7686       case AnalyzeMode:
7687       case AnalyzeFile:
7688       case MachinePlaysWhite:
7689       case MachinePlaysBlack:
7690       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7691         if (!appData.dropMenu) {
7692           LoadPV(x, y);
7693           return 2; // flag front-end to grab mouse events
7694         }
7695         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7696            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7697       case EditGame:
7698       noZip:
7699         if (xSqr < 0 || ySqr < 0) return -1;
7700         if (!appData.dropMenu || appData.testLegality &&
7701             gameInfo.variant != VariantBughouse &&
7702             gameInfo.variant != VariantCrazyhouse) return -1;
7703         whichMenu = 1; // drop menu
7704         break;
7705       default:
7706         return -1;
7707     }
7708
7709     if (((*fromX = xSqr) < 0) ||
7710         ((*fromY = ySqr) < 0)) {
7711         *fromX = *fromY = -1;
7712         return -1;
7713     }
7714     if (flipView)
7715       *fromX = BOARD_WIDTH - 1 - *fromX;
7716     else
7717       *fromY = BOARD_HEIGHT - 1 - *fromY;
7718
7719     return whichMenu;
7720 }
7721
7722 void
7723 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7724 {
7725 //    char * hint = lastHint;
7726     FrontEndProgramStats stats;
7727
7728     stats.which = cps == &first ? 0 : 1;
7729     stats.depth = cpstats->depth;
7730     stats.nodes = cpstats->nodes;
7731     stats.score = cpstats->score;
7732     stats.time = cpstats->time;
7733     stats.pv = cpstats->movelist;
7734     stats.hint = lastHint;
7735     stats.an_move_index = 0;
7736     stats.an_move_count = 0;
7737
7738     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7739         stats.hint = cpstats->move_name;
7740         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7741         stats.an_move_count = cpstats->nr_moves;
7742     }
7743
7744     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
7745
7746     SetProgramStats( &stats );
7747 }
7748
7749 void
7750 ClearEngineOutputPane (int which)
7751 {
7752     static FrontEndProgramStats dummyStats;
7753     dummyStats.which = which;
7754     dummyStats.pv = "#";
7755     SetProgramStats( &dummyStats );
7756 }
7757
7758 #define MAXPLAYERS 500
7759
7760 char *
7761 TourneyStandings (int display)
7762 {
7763     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7764     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7765     char result, *p, *names[MAXPLAYERS];
7766
7767     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7768         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7769     names[0] = p = strdup(appData.participants);
7770     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7771
7772     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7773
7774     while(result = appData.results[nr]) {
7775         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7776         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7777         wScore = bScore = 0;
7778         switch(result) {
7779           case '+': wScore = 2; break;
7780           case '-': bScore = 2; break;
7781           case '=': wScore = bScore = 1; break;
7782           case ' ':
7783           case '*': return strdup("busy"); // tourney not finished
7784         }
7785         score[w] += wScore;
7786         score[b] += bScore;
7787         games[w]++;
7788         games[b]++;
7789         nr++;
7790     }
7791     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7792     for(w=0; w<nPlayers; w++) {
7793         bScore = -1;
7794         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7795         ranking[w] = b; points[w] = bScore; score[b] = -2;
7796     }
7797     p = malloc(nPlayers*34+1);
7798     for(w=0; w<nPlayers && w<display; w++)
7799         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7800     free(names[0]);
7801     return p;
7802 }
7803
7804 void
7805 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7806 {       // count all piece types
7807         int p, f, r;
7808         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7809         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7810         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7811                 p = board[r][f];
7812                 pCnt[p]++;
7813                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7814                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7815                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7816                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7817                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7818                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7819         }
7820 }
7821
7822 int
7823 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7824 {
7825         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7826         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7827
7828         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7829         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7830         if(myPawns == 2 && nMine == 3) // KPP
7831             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7832         if(myPawns == 1 && nMine == 2) // KP
7833             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7834         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7835             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7836         if(myPawns) return FALSE;
7837         if(pCnt[WhiteRook+side])
7838             return pCnt[BlackRook-side] ||
7839                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7840                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7841                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7842         if(pCnt[WhiteCannon+side]) {
7843             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7844             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7845         }
7846         if(pCnt[WhiteKnight+side])
7847             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7848         return FALSE;
7849 }
7850
7851 int
7852 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7853 {
7854         VariantClass v = gameInfo.variant;
7855
7856         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7857         if(v == VariantShatranj) return TRUE; // always winnable through baring
7858         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7859         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7860
7861         if(v == VariantXiangqi) {
7862                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7863
7864                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7865                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7866                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7867                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7868                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7869                 if(stale) // we have at least one last-rank P plus perhaps C
7870                     return majors // KPKX
7871                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7872                 else // KCA*E*
7873                     return pCnt[WhiteFerz+side] // KCAK
7874                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7875                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7876                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7877
7878         } else if(v == VariantKnightmate) {
7879                 if(nMine == 1) return FALSE;
7880                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7881         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7882                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7883
7884                 if(nMine == 1) return FALSE; // bare King
7885                 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
7886                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7887                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7888                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7889                 if(pCnt[WhiteKnight+side])
7890                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7891                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7892                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7893                 if(nBishops)
7894                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7895                 if(pCnt[WhiteAlfil+side])
7896                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7897                 if(pCnt[WhiteWazir+side])
7898                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7899         }
7900
7901         return TRUE;
7902 }
7903
7904 int
7905 CompareWithRights (Board b1, Board b2)
7906 {
7907     int rights = 0;
7908     if(!CompareBoards(b1, b2)) return FALSE;
7909     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7910     /* compare castling rights */
7911     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7912            rights++; /* King lost rights, while rook still had them */
7913     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7914         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7915            rights++; /* but at least one rook lost them */
7916     }
7917     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7918            rights++;
7919     if( b1[CASTLING][5] != NoRights ) {
7920         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7921            rights++;
7922     }
7923     return rights == 0;
7924 }
7925
7926 int
7927 Adjudicate (ChessProgramState *cps)
7928 {       // [HGM] some adjudications useful with buggy engines
7929         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7930         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7931         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7932         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7933         int k, drop, count = 0; static int bare = 1;
7934         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7935         Boolean canAdjudicate = !appData.icsActive;
7936
7937         // most tests only when we understand the game, i.e. legality-checking on
7938             if( appData.testLegality )
7939             {   /* [HGM] Some more adjudications for obstinate engines */
7940                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7941                 static int moveCount = 6;
7942                 ChessMove result;
7943                 char *reason = NULL;
7944
7945                 /* Count what is on board. */
7946                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7947
7948                 /* Some material-based adjudications that have to be made before stalemate test */
7949                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7950                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7951                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7952                      if(canAdjudicate && appData.checkMates) {
7953                          if(engineOpponent)
7954                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7955                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7956                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7957                          return 1;
7958                      }
7959                 }
7960
7961                 /* Bare King in Shatranj (loses) or Losers (wins) */
7962                 if( nrW == 1 || nrB == 1) {
7963                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7964                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7965                      if(canAdjudicate && appData.checkMates) {
7966                          if(engineOpponent)
7967                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7968                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7969                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7970                          return 1;
7971                      }
7972                   } else
7973                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7974                   {    /* bare King */
7975                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7976                         if(canAdjudicate && appData.checkMates) {
7977                             /* but only adjudicate if adjudication enabled */
7978                             if(engineOpponent)
7979                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7980                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7981                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7982                             return 1;
7983                         }
7984                   }
7985                 } else bare = 1;
7986
7987
7988             // don't wait for engine to announce game end if we can judge ourselves
7989             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7990               case MT_CHECK:
7991                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7992                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7993                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7994                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7995                             checkCnt++;
7996                         if(checkCnt >= 2) {
7997                             reason = "Xboard adjudication: 3rd check";
7998                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7999                             break;
8000                         }
8001                     }
8002                 }
8003               case MT_NONE:
8004               default:
8005                 break;
8006               case MT_STALEMATE:
8007               case MT_STAINMATE:
8008                 reason = "Xboard adjudication: Stalemate";
8009                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8010                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8011                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8012                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8013                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8014                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8015                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8016                                                                         EP_CHECKMATE : EP_WINS);
8017                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8018                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8019                 }
8020                 break;
8021               case MT_CHECKMATE:
8022                 reason = "Xboard adjudication: Checkmate";
8023                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8024                 if(gameInfo.variant == VariantShogi) {
8025                     if(forwardMostMove > backwardMostMove
8026                        && moveList[forwardMostMove-1][1] == '@'
8027                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8028                         reason = "XBoard adjudication: pawn-drop mate";
8029                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8030                     }
8031                 }
8032                 break;
8033             }
8034
8035                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8036                     case EP_STALEMATE:
8037                         result = GameIsDrawn; break;
8038                     case EP_CHECKMATE:
8039                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8040                     case EP_WINS:
8041                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8042                     default:
8043                         result = EndOfFile;
8044                 }
8045                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8046                     if(engineOpponent)
8047                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8048                     GameEnds( result, reason, GE_XBOARD );
8049                     return 1;
8050                 }
8051
8052                 /* Next absolutely insufficient mating material. */
8053                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8054                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8055                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8056
8057                      /* always flag draws, for judging claims */
8058                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8059
8060                      if(canAdjudicate && appData.materialDraws) {
8061                          /* but only adjudicate them if adjudication enabled */
8062                          if(engineOpponent) {
8063                            SendToProgram("force\n", engineOpponent); // suppress reply
8064                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8065                          }
8066                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8067                          return 1;
8068                      }
8069                 }
8070
8071                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8072                 if(gameInfo.variant == VariantXiangqi ?
8073                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8074                  : nrW + nrB == 4 &&
8075                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8076                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8077                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8078                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8079                    ) ) {
8080                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8081                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8082                           if(engineOpponent) {
8083                             SendToProgram("force\n", engineOpponent); // suppress reply
8084                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8085                           }
8086                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8087                           return 1;
8088                      }
8089                 } else moveCount = 6;
8090             }
8091
8092         // Repetition draws and 50-move rule can be applied independently of legality testing
8093
8094                 /* Check for rep-draws */
8095                 count = 0;
8096                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8097                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8098                 for(k = forwardMostMove-2;
8099                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8100                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8101                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8102                     k-=2)
8103                 {   int rights=0;
8104                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8105                         /* compare castling rights */
8106                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8107                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8108                                 rights++; /* King lost rights, while rook still had them */
8109                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8110                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8111                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8112                                    rights++; /* but at least one rook lost them */
8113                         }
8114                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8115                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8116                                 rights++;
8117                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8118                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8119                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8120                                    rights++;
8121                         }
8122                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8123                             && appData.drawRepeats > 1) {
8124                              /* adjudicate after user-specified nr of repeats */
8125                              int result = GameIsDrawn;
8126                              char *details = "XBoard adjudication: repetition draw";
8127                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8128                                 // [HGM] xiangqi: check for forbidden perpetuals
8129                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8130                                 for(m=forwardMostMove; m>k; m-=2) {
8131                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8132                                         ourPerpetual = 0; // the current mover did not always check
8133                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8134                                         hisPerpetual = 0; // the opponent did not always check
8135                                 }
8136                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8137                                                                         ourPerpetual, hisPerpetual);
8138                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8139                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8140                                     details = "Xboard adjudication: perpetual checking";
8141                                 } else
8142                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8143                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8144                                 } else
8145                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8146                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8147                                         result = BlackWins;
8148                                         details = "Xboard adjudication: repetition";
8149                                     }
8150                                 } else // it must be XQ
8151                                 // Now check for perpetual chases
8152                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8153                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8154                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8155                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8156                                         static char resdet[MSG_SIZ];
8157                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8158                                         details = resdet;
8159                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8160                                     } else
8161                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8162                                         break; // Abort repetition-checking loop.
8163                                 }
8164                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8165                              }
8166                              if(engineOpponent) {
8167                                SendToProgram("force\n", engineOpponent); // suppress reply
8168                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8169                              }
8170                              GameEnds( result, details, GE_XBOARD );
8171                              return 1;
8172                         }
8173                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8174                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8175                     }
8176                 }
8177
8178                 /* Now we test for 50-move draws. Determine ply count */
8179                 count = forwardMostMove;
8180                 /* look for last irreversble move */
8181                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8182                     count--;
8183                 /* if we hit starting position, add initial plies */
8184                 if( count == backwardMostMove )
8185                     count -= initialRulePlies;
8186                 count = forwardMostMove - count;
8187                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8188                         // adjust reversible move counter for checks in Xiangqi
8189                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8190                         if(i < backwardMostMove) i = backwardMostMove;
8191                         while(i <= forwardMostMove) {
8192                                 lastCheck = inCheck; // check evasion does not count
8193                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8194                                 if(inCheck || lastCheck) count--; // check does not count
8195                                 i++;
8196                         }
8197                 }
8198                 if( count >= 100)
8199                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8200                          /* this is used to judge if draw claims are legal */
8201                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8202                          if(engineOpponent) {
8203                            SendToProgram("force\n", engineOpponent); // suppress reply
8204                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8205                          }
8206                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8207                          return 1;
8208                 }
8209
8210                 /* if draw offer is pending, treat it as a draw claim
8211                  * when draw condition present, to allow engines a way to
8212                  * claim draws before making their move to avoid a race
8213                  * condition occurring after their move
8214                  */
8215                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8216                          char *p = NULL;
8217                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8218                              p = "Draw claim: 50-move rule";
8219                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8220                              p = "Draw claim: 3-fold repetition";
8221                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8222                              p = "Draw claim: insufficient mating material";
8223                          if( p != NULL && canAdjudicate) {
8224                              if(engineOpponent) {
8225                                SendToProgram("force\n", engineOpponent); // suppress reply
8226                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8227                              }
8228                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8229                              return 1;
8230                          }
8231                 }
8232
8233                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8234                     if(engineOpponent) {
8235                       SendToProgram("force\n", engineOpponent); // suppress reply
8236                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8237                     }
8238                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8239                     return 1;
8240                 }
8241         return 0;
8242 }
8243
8244 char *
8245 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8246 {   // [HGM] book: this routine intercepts moves to simulate book replies
8247     char *bookHit = NULL;
8248
8249     //first determine if the incoming move brings opponent into his book
8250     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8251         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8252     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8253     if(bookHit != NULL && !cps->bookSuspend) {
8254         // make sure opponent is not going to reply after receiving move to book position
8255         SendToProgram("force\n", cps);
8256         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8257     }
8258     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8259     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8260     // now arrange restart after book miss
8261     if(bookHit) {
8262         // after a book hit we never send 'go', and the code after the call to this routine
8263         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8264         char buf[MSG_SIZ], *move = bookHit;
8265         if(cps->useSAN) {
8266             int fromX, fromY, toX, toY;
8267             char promoChar;
8268             ChessMove moveType;
8269             move = buf + 30;
8270             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8271                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8272                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8273                                     PosFlags(forwardMostMove),
8274                                     fromY, fromX, toY, toX, promoChar, move);
8275             } else {
8276                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8277                 bookHit = NULL;
8278             }
8279         }
8280         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8281         SendToProgram(buf, cps);
8282         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8283     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8284         SendToProgram("go\n", cps);
8285         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8286     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8287         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8288             SendToProgram("go\n", cps);
8289         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8290     }
8291     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8292 }
8293
8294 int
8295 LoadError (char *errmess, ChessProgramState *cps)
8296 {   // unloads engine and switches back to -ncp mode if it was first
8297     if(cps->initDone) return FALSE;
8298     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8299     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8300     cps->pr = NoProc;
8301     if(cps == &first) {
8302         appData.noChessProgram = TRUE;
8303         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8304         gameMode = BeginningOfGame; ModeHighlight();
8305         SetNCPMode();
8306     }
8307     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8308     DisplayMessage("", ""); // erase waiting message
8309     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8310     return TRUE;
8311 }
8312
8313 char *savedMessage;
8314 ChessProgramState *savedState;
8315 void
8316 DeferredBookMove (void)
8317 {
8318         if(savedState->lastPing != savedState->lastPong)
8319                     ScheduleDelayedEvent(DeferredBookMove, 10);
8320         else
8321         HandleMachineMove(savedMessage, savedState);
8322 }
8323
8324 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8325 static ChessProgramState *stalledEngine;
8326 static char stashedInputMove[MSG_SIZ];
8327
8328 void
8329 HandleMachineMove (char *message, ChessProgramState *cps)
8330 {
8331     static char firstLeg[20];
8332     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8333     char realname[MSG_SIZ];
8334     int fromX, fromY, toX, toY;
8335     ChessMove moveType;
8336     char promoChar, roar;
8337     char *p, *pv=buf1;
8338     int machineWhite, oldError;
8339     char *bookHit;
8340
8341     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8342         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8343         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8344             DisplayError(_("Invalid pairing from pairing engine"), 0);
8345             return;
8346         }
8347         pairingReceived = 1;
8348         NextMatchGame();
8349         return; // Skim the pairing messages here.
8350     }
8351
8352     oldError = cps->userError; cps->userError = 0;
8353
8354 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8355     /*
8356      * Kludge to ignore BEL characters
8357      */
8358     while (*message == '\007') message++;
8359
8360     /*
8361      * [HGM] engine debug message: ignore lines starting with '#' character
8362      */
8363     if(cps->debug && *message == '#') return;
8364
8365     /*
8366      * Look for book output
8367      */
8368     if (cps == &first && bookRequested) {
8369         if (message[0] == '\t' || message[0] == ' ') {
8370             /* Part of the book output is here; append it */
8371             strcat(bookOutput, message);
8372             strcat(bookOutput, "  \n");
8373             return;
8374         } else if (bookOutput[0] != NULLCHAR) {
8375             /* All of book output has arrived; display it */
8376             char *p = bookOutput;
8377             while (*p != NULLCHAR) {
8378                 if (*p == '\t') *p = ' ';
8379                 p++;
8380             }
8381             DisplayInformation(bookOutput);
8382             bookRequested = FALSE;
8383             /* Fall through to parse the current output */
8384         }
8385     }
8386
8387     /*
8388      * Look for machine move.
8389      */
8390     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8391         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8392     {
8393         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8394             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8395             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8396             stalledEngine = cps;
8397             if(appData.ponderNextMove) { // bring opponent out of ponder
8398                 if(gameMode == TwoMachinesPlay) {
8399                     if(cps->other->pause)
8400                         PauseEngine(cps->other);
8401                     else
8402                         SendToProgram("easy\n", cps->other);
8403                 }
8404             }
8405             StopClocks();
8406             return;
8407         }
8408
8409         /* This method is only useful on engines that support ping */
8410         if (cps->lastPing != cps->lastPong) {
8411           if (gameMode == BeginningOfGame) {
8412             /* Extra move from before last new; ignore */
8413             if (appData.debugMode) {
8414                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8415             }
8416           } else {
8417             if (appData.debugMode) {
8418                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8419                         cps->which, gameMode);
8420             }
8421
8422             SendToProgram("undo\n", cps);
8423           }
8424           return;
8425         }
8426
8427         switch (gameMode) {
8428           case BeginningOfGame:
8429             /* Extra move from before last reset; ignore */
8430             if (appData.debugMode) {
8431                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8432             }
8433             return;
8434
8435           case EndOfGame:
8436           case IcsIdle:
8437           default:
8438             /* Extra move after we tried to stop.  The mode test is
8439                not a reliable way of detecting this problem, but it's
8440                the best we can do on engines that don't support ping.
8441             */
8442             if (appData.debugMode) {
8443                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8444                         cps->which, gameMode);
8445             }
8446             SendToProgram("undo\n", cps);
8447             return;
8448
8449           case MachinePlaysWhite:
8450           case IcsPlayingWhite:
8451             machineWhite = TRUE;
8452             break;
8453
8454           case MachinePlaysBlack:
8455           case IcsPlayingBlack:
8456             machineWhite = FALSE;
8457             break;
8458
8459           case TwoMachinesPlay:
8460             machineWhite = (cps->twoMachinesColor[0] == 'w');
8461             break;
8462         }
8463         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8464             if (appData.debugMode) {
8465                 fprintf(debugFP,
8466                         "Ignoring move out of turn by %s, gameMode %d"
8467                         ", forwardMost %d\n",
8468                         cps->which, gameMode, forwardMostMove);
8469             }
8470             return;
8471         }
8472
8473         if(cps->alphaRank) AlphaRank(machineMove, 4);
8474
8475         // [HGM] lion: (some very limited) support for Alien protocol
8476         killX = killY = -1;
8477         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8478             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8479             return;
8480         } else if(firstLeg[0]) { // there was a previous leg;
8481             // only support case where same piece makes two step (and don't even test that!)
8482             char buf[20], *p = machineMove+1, *q = buf+1, f;
8483             safeStrCpy(buf, machineMove, 20);
8484             while(isdigit(*q)) q++; // find start of to-square
8485             safeStrCpy(machineMove, firstLeg, 20);
8486             while(isdigit(*p)) p++;
8487             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8488             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8489             firstLeg[0] = NULLCHAR;
8490         }
8491
8492         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8493                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8494             /* Machine move could not be parsed; ignore it. */
8495           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8496                     machineMove, _(cps->which));
8497             DisplayMoveError(buf1);
8498             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8499                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8500             if (gameMode == TwoMachinesPlay) {
8501               GameEnds(machineWhite ? BlackWins : WhiteWins,
8502                        buf1, GE_XBOARD);
8503             }
8504             return;
8505         }
8506
8507         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8508         /* So we have to redo legality test with true e.p. status here,  */
8509         /* to make sure an illegal e.p. capture does not slip through,   */
8510         /* to cause a forfeit on a justified illegal-move complaint      */
8511         /* of the opponent.                                              */
8512         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8513            ChessMove moveType;
8514            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8515                              fromY, fromX, toY, toX, promoChar);
8516             if(moveType == IllegalMove) {
8517               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8518                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8519                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8520                            buf1, GE_XBOARD);
8521                 return;
8522            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8523            /* [HGM] Kludge to handle engines that send FRC-style castling
8524               when they shouldn't (like TSCP-Gothic) */
8525            switch(moveType) {
8526              case WhiteASideCastleFR:
8527              case BlackASideCastleFR:
8528                toX+=2;
8529                currentMoveString[2]++;
8530                break;
8531              case WhiteHSideCastleFR:
8532              case BlackHSideCastleFR:
8533                toX--;
8534                currentMoveString[2]--;
8535                break;
8536              default: ; // nothing to do, but suppresses warning of pedantic compilers
8537            }
8538         }
8539         hintRequested = FALSE;
8540         lastHint[0] = NULLCHAR;
8541         bookRequested = FALSE;
8542         /* Program may be pondering now */
8543         cps->maybeThinking = TRUE;
8544         if (cps->sendTime == 2) cps->sendTime = 1;
8545         if (cps->offeredDraw) cps->offeredDraw--;
8546
8547         /* [AS] Save move info*/
8548         pvInfoList[ forwardMostMove ].score = programStats.score;
8549         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8550         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8551
8552         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8553
8554         /* Test suites abort the 'game' after one move */
8555         if(*appData.finger) {
8556            static FILE *f;
8557            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8558            if(!f) f = fopen(appData.finger, "w");
8559            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8560            else { DisplayFatalError("Bad output file", errno, 0); return; }
8561            free(fen);
8562            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8563         }
8564
8565         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8566         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8567             int count = 0;
8568
8569             while( count < adjudicateLossPlies ) {
8570                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8571
8572                 if( count & 1 ) {
8573                     score = -score; /* Flip score for winning side */
8574                 }
8575
8576                 if( score > adjudicateLossThreshold ) {
8577                     break;
8578                 }
8579
8580                 count++;
8581             }
8582
8583             if( count >= adjudicateLossPlies ) {
8584                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8585
8586                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8587                     "Xboard adjudication",
8588                     GE_XBOARD );
8589
8590                 return;
8591             }
8592         }
8593
8594         if(Adjudicate(cps)) {
8595             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8596             return; // [HGM] adjudicate: for all automatic game ends
8597         }
8598
8599 #if ZIPPY
8600         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8601             first.initDone) {
8602           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8603                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8604                 SendToICS("draw ");
8605                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8606           }
8607           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8608           ics_user_moved = 1;
8609           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8610                 char buf[3*MSG_SIZ];
8611
8612                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8613                         programStats.score / 100.,
8614                         programStats.depth,
8615                         programStats.time / 100.,
8616                         (unsigned int)programStats.nodes,
8617                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8618                         programStats.movelist);
8619                 SendToICS(buf);
8620 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8621           }
8622         }
8623 #endif
8624
8625         /* [AS] Clear stats for next move */
8626         ClearProgramStats();
8627         thinkOutput[0] = NULLCHAR;
8628         hiddenThinkOutputState = 0;
8629
8630         bookHit = NULL;
8631         if (gameMode == TwoMachinesPlay) {
8632             /* [HGM] relaying draw offers moved to after reception of move */
8633             /* and interpreting offer as claim if it brings draw condition */
8634             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8635                 SendToProgram("draw\n", cps->other);
8636             }
8637             if (cps->other->sendTime) {
8638                 SendTimeRemaining(cps->other,
8639                                   cps->other->twoMachinesColor[0] == 'w');
8640             }
8641             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8642             if (firstMove && !bookHit) {
8643                 firstMove = FALSE;
8644                 if (cps->other->useColors) {
8645                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8646                 }
8647                 SendToProgram("go\n", cps->other);
8648             }
8649             cps->other->maybeThinking = TRUE;
8650         }
8651
8652         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8653
8654         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8655
8656         if (!pausing && appData.ringBellAfterMoves) {
8657             if(!roar) RingBell();
8658         }
8659
8660         /*
8661          * Reenable menu items that were disabled while
8662          * machine was thinking
8663          */
8664         if (gameMode != TwoMachinesPlay)
8665             SetUserThinkingEnables();
8666
8667         // [HGM] book: after book hit opponent has received move and is now in force mode
8668         // force the book reply into it, and then fake that it outputted this move by jumping
8669         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8670         if(bookHit) {
8671                 static char bookMove[MSG_SIZ]; // a bit generous?
8672
8673                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8674                 strcat(bookMove, bookHit);
8675                 message = bookMove;
8676                 cps = cps->other;
8677                 programStats.nodes = programStats.depth = programStats.time =
8678                 programStats.score = programStats.got_only_move = 0;
8679                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8680
8681                 if(cps->lastPing != cps->lastPong) {
8682                     savedMessage = message; // args for deferred call
8683                     savedState = cps;
8684                     ScheduleDelayedEvent(DeferredBookMove, 10);
8685                     return;
8686                 }
8687                 goto FakeBookMove;
8688         }
8689
8690         return;
8691     }
8692
8693     /* Set special modes for chess engines.  Later something general
8694      *  could be added here; for now there is just one kludge feature,
8695      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8696      *  when "xboard" is given as an interactive command.
8697      */
8698     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8699         cps->useSigint = FALSE;
8700         cps->useSigterm = FALSE;
8701     }
8702     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8703       ParseFeatures(message+8, cps);
8704       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8705     }
8706
8707     if (!strncmp(message, "setup ", 6) && 
8708         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8709           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8710                                         ) { // [HGM] allow first engine to define opening position
8711       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8712       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8713       *buf = NULLCHAR;
8714       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8715       if(startedFromSetupPosition) return;
8716       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8717       if(dummy >= 3) {
8718         while(message[s] && message[s++] != ' ');
8719         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8720            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8721             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8722             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8723           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8724           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8725         }
8726       }
8727       ParseFEN(boards[0], &dummy, message+s, FALSE);
8728       DrawPosition(TRUE, boards[0]);
8729       startedFromSetupPosition = TRUE;
8730       return;
8731     }
8732     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8733      * want this, I was asked to put it in, and obliged.
8734      */
8735     if (!strncmp(message, "setboard ", 9)) {
8736         Board initial_position;
8737
8738         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8739
8740         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8741             DisplayError(_("Bad FEN received from engine"), 0);
8742             return ;
8743         } else {
8744            Reset(TRUE, FALSE);
8745            CopyBoard(boards[0], initial_position);
8746            initialRulePlies = FENrulePlies;
8747            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8748            else gameMode = MachinePlaysBlack;
8749            DrawPosition(FALSE, boards[currentMove]);
8750         }
8751         return;
8752     }
8753
8754     /*
8755      * Look for communication commands
8756      */
8757     if (!strncmp(message, "telluser ", 9)) {
8758         if(message[9] == '\\' && message[10] == '\\')
8759             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8760         PlayTellSound();
8761         DisplayNote(message + 9);
8762         return;
8763     }
8764     if (!strncmp(message, "tellusererror ", 14)) {
8765         cps->userError = 1;
8766         if(message[14] == '\\' && message[15] == '\\')
8767             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8768         PlayTellSound();
8769         DisplayError(message + 14, 0);
8770         return;
8771     }
8772     if (!strncmp(message, "tellopponent ", 13)) {
8773       if (appData.icsActive) {
8774         if (loggedOn) {
8775           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8776           SendToICS(buf1);
8777         }
8778       } else {
8779         DisplayNote(message + 13);
8780       }
8781       return;
8782     }
8783     if (!strncmp(message, "tellothers ", 11)) {
8784       if (appData.icsActive) {
8785         if (loggedOn) {
8786           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8787           SendToICS(buf1);
8788         }
8789       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8790       return;
8791     }
8792     if (!strncmp(message, "tellall ", 8)) {
8793       if (appData.icsActive) {
8794         if (loggedOn) {
8795           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8796           SendToICS(buf1);
8797         }
8798       } else {
8799         DisplayNote(message + 8);
8800       }
8801       return;
8802     }
8803     if (strncmp(message, "warning", 7) == 0) {
8804         /* Undocumented feature, use tellusererror in new code */
8805         DisplayError(message, 0);
8806         return;
8807     }
8808     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8809         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8810         strcat(realname, " query");
8811         AskQuestion(realname, buf2, buf1, cps->pr);
8812         return;
8813     }
8814     /* Commands from the engine directly to ICS.  We don't allow these to be
8815      *  sent until we are logged on. Crafty kibitzes have been known to
8816      *  interfere with the login process.
8817      */
8818     if (loggedOn) {
8819         if (!strncmp(message, "tellics ", 8)) {
8820             SendToICS(message + 8);
8821             SendToICS("\n");
8822             return;
8823         }
8824         if (!strncmp(message, "tellicsnoalias ", 15)) {
8825             SendToICS(ics_prefix);
8826             SendToICS(message + 15);
8827             SendToICS("\n");
8828             return;
8829         }
8830         /* The following are for backward compatibility only */
8831         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8832             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8833             SendToICS(ics_prefix);
8834             SendToICS(message);
8835             SendToICS("\n");
8836             return;
8837         }
8838     }
8839     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8840         if(initPing == cps->lastPong) {
8841             if(gameInfo.variant == VariantUnknown) {
8842                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8843                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8844                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8845             }
8846             initPing = -1;
8847         }
8848         return;
8849     }
8850     if(!strncmp(message, "highlight ", 10)) {
8851         if(appData.testLegality && appData.markers) return;
8852         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8853         return;
8854     }
8855     if(!strncmp(message, "click ", 6)) {
8856         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8857         if(appData.testLegality || !appData.oneClick) return;
8858         sscanf(message+6, "%c%d%c", &f, &y, &c);
8859         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8860         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8861         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8862         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8863         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8864         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8865             LeftClick(Release, lastLeftX, lastLeftY);
8866         controlKey  = (c == ',');
8867         LeftClick(Press, x, y);
8868         LeftClick(Release, x, y);
8869         first.highlight = f;
8870         return;
8871     }
8872     /*
8873      * If the move is illegal, cancel it and redraw the board.
8874      * Also deal with other error cases.  Matching is rather loose
8875      * here to accommodate engines written before the spec.
8876      */
8877     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8878         strncmp(message, "Error", 5) == 0) {
8879         if (StrStr(message, "name") ||
8880             StrStr(message, "rating") || StrStr(message, "?") ||
8881             StrStr(message, "result") || StrStr(message, "board") ||
8882             StrStr(message, "bk") || StrStr(message, "computer") ||
8883             StrStr(message, "variant") || StrStr(message, "hint") ||
8884             StrStr(message, "random") || StrStr(message, "depth") ||
8885             StrStr(message, "accepted")) {
8886             return;
8887         }
8888         if (StrStr(message, "protover")) {
8889           /* Program is responding to input, so it's apparently done
8890              initializing, and this error message indicates it is
8891              protocol version 1.  So we don't need to wait any longer
8892              for it to initialize and send feature commands. */
8893           FeatureDone(cps, 1);
8894           cps->protocolVersion = 1;
8895           return;
8896         }
8897         cps->maybeThinking = FALSE;
8898
8899         if (StrStr(message, "draw")) {
8900             /* Program doesn't have "draw" command */
8901             cps->sendDrawOffers = 0;
8902             return;
8903         }
8904         if (cps->sendTime != 1 &&
8905             (StrStr(message, "time") || StrStr(message, "otim"))) {
8906           /* Program apparently doesn't have "time" or "otim" command */
8907           cps->sendTime = 0;
8908           return;
8909         }
8910         if (StrStr(message, "analyze")) {
8911             cps->analysisSupport = FALSE;
8912             cps->analyzing = FALSE;
8913 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8914             EditGameEvent(); // [HGM] try to preserve loaded game
8915             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8916             DisplayError(buf2, 0);
8917             return;
8918         }
8919         if (StrStr(message, "(no matching move)st")) {
8920           /* Special kludge for GNU Chess 4 only */
8921           cps->stKludge = TRUE;
8922           SendTimeControl(cps, movesPerSession, timeControl,
8923                           timeIncrement, appData.searchDepth,
8924                           searchTime);
8925           return;
8926         }
8927         if (StrStr(message, "(no matching move)sd")) {
8928           /* Special kludge for GNU Chess 4 only */
8929           cps->sdKludge = TRUE;
8930           SendTimeControl(cps, movesPerSession, timeControl,
8931                           timeIncrement, appData.searchDepth,
8932                           searchTime);
8933           return;
8934         }
8935         if (!StrStr(message, "llegal")) {
8936             return;
8937         }
8938         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8939             gameMode == IcsIdle) return;
8940         if (forwardMostMove <= backwardMostMove) return;
8941         if (pausing) PauseEvent();
8942       if(appData.forceIllegal) {
8943             // [HGM] illegal: machine refused move; force position after move into it
8944           SendToProgram("force\n", cps);
8945           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8946                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8947                 // when black is to move, while there might be nothing on a2 or black
8948                 // might already have the move. So send the board as if white has the move.
8949                 // But first we must change the stm of the engine, as it refused the last move
8950                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8951                 if(WhiteOnMove(forwardMostMove)) {
8952                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8953                     SendBoard(cps, forwardMostMove); // kludgeless board
8954                 } else {
8955                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8956                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8957                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8958                 }
8959           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8960             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8961                  gameMode == TwoMachinesPlay)
8962               SendToProgram("go\n", cps);
8963             return;
8964       } else
8965         if (gameMode == PlayFromGameFile) {
8966             /* Stop reading this game file */
8967             gameMode = EditGame;
8968             ModeHighlight();
8969         }
8970         /* [HGM] illegal-move claim should forfeit game when Xboard */
8971         /* only passes fully legal moves                            */
8972         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8973             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8974                                 "False illegal-move claim", GE_XBOARD );
8975             return; // do not take back move we tested as valid
8976         }
8977         currentMove = forwardMostMove-1;
8978         DisplayMove(currentMove-1); /* before DisplayMoveError */
8979         SwitchClocks(forwardMostMove-1); // [HGM] race
8980         DisplayBothClocks();
8981         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8982                 parseList[currentMove], _(cps->which));
8983         DisplayMoveError(buf1);
8984         DrawPosition(FALSE, boards[currentMove]);
8985
8986         SetUserThinkingEnables();
8987         return;
8988     }
8989     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8990         /* Program has a broken "time" command that
8991            outputs a string not ending in newline.
8992            Don't use it. */
8993         cps->sendTime = 0;
8994     }
8995
8996     /*
8997      * If chess program startup fails, exit with an error message.
8998      * Attempts to recover here are futile. [HGM] Well, we try anyway
8999      */
9000     if ((StrStr(message, "unknown host") != NULL)
9001         || (StrStr(message, "No remote directory") != NULL)
9002         || (StrStr(message, "not found") != NULL)
9003         || (StrStr(message, "No such file") != NULL)
9004         || (StrStr(message, "can't alloc") != NULL)
9005         || (StrStr(message, "Permission denied") != NULL)) {
9006
9007         cps->maybeThinking = FALSE;
9008         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9009                 _(cps->which), cps->program, cps->host, message);
9010         RemoveInputSource(cps->isr);
9011         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9012             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9013             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9014         }
9015         return;
9016     }
9017
9018     /*
9019      * Look for hint output
9020      */
9021     if (sscanf(message, "Hint: %s", buf1) == 1) {
9022         if (cps == &first && hintRequested) {
9023             hintRequested = FALSE;
9024             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9025                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9026                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9027                                     PosFlags(forwardMostMove),
9028                                     fromY, fromX, toY, toX, promoChar, buf1);
9029                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9030                 DisplayInformation(buf2);
9031             } else {
9032                 /* Hint move could not be parsed!? */
9033               snprintf(buf2, sizeof(buf2),
9034                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9035                         buf1, _(cps->which));
9036                 DisplayError(buf2, 0);
9037             }
9038         } else {
9039           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9040         }
9041         return;
9042     }
9043
9044     /*
9045      * Ignore other messages if game is not in progress
9046      */
9047     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9048         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9049
9050     /*
9051      * look for win, lose, draw, or draw offer
9052      */
9053     if (strncmp(message, "1-0", 3) == 0) {
9054         char *p, *q, *r = "";
9055         p = strchr(message, '{');
9056         if (p) {
9057             q = strchr(p, '}');
9058             if (q) {
9059                 *q = NULLCHAR;
9060                 r = p + 1;
9061             }
9062         }
9063         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9064         return;
9065     } else if (strncmp(message, "0-1", 3) == 0) {
9066         char *p, *q, *r = "";
9067         p = strchr(message, '{');
9068         if (p) {
9069             q = strchr(p, '}');
9070             if (q) {
9071                 *q = NULLCHAR;
9072                 r = p + 1;
9073             }
9074         }
9075         /* Kludge for Arasan 4.1 bug */
9076         if (strcmp(r, "Black resigns") == 0) {
9077             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9078             return;
9079         }
9080         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9081         return;
9082     } else if (strncmp(message, "1/2", 3) == 0) {
9083         char *p, *q, *r = "";
9084         p = strchr(message, '{');
9085         if (p) {
9086             q = strchr(p, '}');
9087             if (q) {
9088                 *q = NULLCHAR;
9089                 r = p + 1;
9090             }
9091         }
9092
9093         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9094         return;
9095
9096     } else if (strncmp(message, "White resign", 12) == 0) {
9097         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9098         return;
9099     } else if (strncmp(message, "Black resign", 12) == 0) {
9100         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9101         return;
9102     } else if (strncmp(message, "White matches", 13) == 0 ||
9103                strncmp(message, "Black matches", 13) == 0   ) {
9104         /* [HGM] ignore GNUShogi noises */
9105         return;
9106     } else if (strncmp(message, "White", 5) == 0 &&
9107                message[5] != '(' &&
9108                StrStr(message, "Black") == NULL) {
9109         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9110         return;
9111     } else if (strncmp(message, "Black", 5) == 0 &&
9112                message[5] != '(') {
9113         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9114         return;
9115     } else if (strcmp(message, "resign") == 0 ||
9116                strcmp(message, "computer resigns") == 0) {
9117         switch (gameMode) {
9118           case MachinePlaysBlack:
9119           case IcsPlayingBlack:
9120             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9121             break;
9122           case MachinePlaysWhite:
9123           case IcsPlayingWhite:
9124             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9125             break;
9126           case TwoMachinesPlay:
9127             if (cps->twoMachinesColor[0] == 'w')
9128               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9129             else
9130               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9131             break;
9132           default:
9133             /* can't happen */
9134             break;
9135         }
9136         return;
9137     } else if (strncmp(message, "opponent mates", 14) == 0) {
9138         switch (gameMode) {
9139           case MachinePlaysBlack:
9140           case IcsPlayingBlack:
9141             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9142             break;
9143           case MachinePlaysWhite:
9144           case IcsPlayingWhite:
9145             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9146             break;
9147           case TwoMachinesPlay:
9148             if (cps->twoMachinesColor[0] == 'w')
9149               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9150             else
9151               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9152             break;
9153           default:
9154             /* can't happen */
9155             break;
9156         }
9157         return;
9158     } else if (strncmp(message, "computer mates", 14) == 0) {
9159         switch (gameMode) {
9160           case MachinePlaysBlack:
9161           case IcsPlayingBlack:
9162             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9163             break;
9164           case MachinePlaysWhite:
9165           case IcsPlayingWhite:
9166             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9167             break;
9168           case TwoMachinesPlay:
9169             if (cps->twoMachinesColor[0] == 'w')
9170               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9171             else
9172               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9173             break;
9174           default:
9175             /* can't happen */
9176             break;
9177         }
9178         return;
9179     } else if (strncmp(message, "checkmate", 9) == 0) {
9180         if (WhiteOnMove(forwardMostMove)) {
9181             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9182         } else {
9183             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9184         }
9185         return;
9186     } else if (strstr(message, "Draw") != NULL ||
9187                strstr(message, "game is a draw") != NULL) {
9188         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9189         return;
9190     } else if (strstr(message, "offer") != NULL &&
9191                strstr(message, "draw") != NULL) {
9192 #if ZIPPY
9193         if (appData.zippyPlay && first.initDone) {
9194             /* Relay offer to ICS */
9195             SendToICS(ics_prefix);
9196             SendToICS("draw\n");
9197         }
9198 #endif
9199         cps->offeredDraw = 2; /* valid until this engine moves twice */
9200         if (gameMode == TwoMachinesPlay) {
9201             if (cps->other->offeredDraw) {
9202                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9203             /* [HGM] in two-machine mode we delay relaying draw offer      */
9204             /* until after we also have move, to see if it is really claim */
9205             }
9206         } else if (gameMode == MachinePlaysWhite ||
9207                    gameMode == MachinePlaysBlack) {
9208           if (userOfferedDraw) {
9209             DisplayInformation(_("Machine accepts your draw offer"));
9210             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9211           } else {
9212             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9213           }
9214         }
9215     }
9216
9217
9218     /*
9219      * Look for thinking output
9220      */
9221     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9222           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9223                                 ) {
9224         int plylev, mvleft, mvtot, curscore, time;
9225         char mvname[MOVE_LEN];
9226         u64 nodes; // [DM]
9227         char plyext;
9228         int ignore = FALSE;
9229         int prefixHint = FALSE;
9230         mvname[0] = NULLCHAR;
9231
9232         switch (gameMode) {
9233           case MachinePlaysBlack:
9234           case IcsPlayingBlack:
9235             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9236             break;
9237           case MachinePlaysWhite:
9238           case IcsPlayingWhite:
9239             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9240             break;
9241           case AnalyzeMode:
9242           case AnalyzeFile:
9243             break;
9244           case IcsObserving: /* [DM] icsEngineAnalyze */
9245             if (!appData.icsEngineAnalyze) ignore = TRUE;
9246             break;
9247           case TwoMachinesPlay:
9248             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9249                 ignore = TRUE;
9250             }
9251             break;
9252           default:
9253             ignore = TRUE;
9254             break;
9255         }
9256
9257         if (!ignore) {
9258             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9259             buf1[0] = NULLCHAR;
9260             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9261                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9262
9263                 if (plyext != ' ' && plyext != '\t') {
9264                     time *= 100;
9265                 }
9266
9267                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9268                 if( cps->scoreIsAbsolute &&
9269                     ( gameMode == MachinePlaysBlack ||
9270                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9271                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9272                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9273                      !WhiteOnMove(currentMove)
9274                     ) )
9275                 {
9276                     curscore = -curscore;
9277                 }
9278
9279                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9280
9281                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9282                         char buf[MSG_SIZ];
9283                         FILE *f;
9284                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9285                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9286                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9287                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9288                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9289                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9290                                 fclose(f);
9291                         }
9292                         else
9293                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9294                           DisplayError(_("failed writing PV"), 0);
9295                 }
9296
9297                 tempStats.depth = plylev;
9298                 tempStats.nodes = nodes;
9299                 tempStats.time = time;
9300                 tempStats.score = curscore;
9301                 tempStats.got_only_move = 0;
9302
9303                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9304                         int ticklen;
9305
9306                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9307                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9308                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9309                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9310                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9311                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9312                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9313                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9314                 }
9315
9316                 /* Buffer overflow protection */
9317                 if (pv[0] != NULLCHAR) {
9318                     if (strlen(pv) >= sizeof(tempStats.movelist)
9319                         && appData.debugMode) {
9320                         fprintf(debugFP,
9321                                 "PV is too long; using the first %u bytes.\n",
9322                                 (unsigned) sizeof(tempStats.movelist) - 1);
9323                     }
9324
9325                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9326                 } else {
9327                     sprintf(tempStats.movelist, " no PV\n");
9328                 }
9329
9330                 if (tempStats.seen_stat) {
9331                     tempStats.ok_to_send = 1;
9332                 }
9333
9334                 if (strchr(tempStats.movelist, '(') != NULL) {
9335                     tempStats.line_is_book = 1;
9336                     tempStats.nr_moves = 0;
9337                     tempStats.moves_left = 0;
9338                 } else {
9339                     tempStats.line_is_book = 0;
9340                 }
9341
9342                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9343                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9344
9345                 SendProgramStatsToFrontend( cps, &tempStats );
9346
9347                 /*
9348                     [AS] Protect the thinkOutput buffer from overflow... this
9349                     is only useful if buf1 hasn't overflowed first!
9350                 */
9351                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9352                          plylev,
9353                          (gameMode == TwoMachinesPlay ?
9354                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9355                          ((double) curscore) / 100.0,
9356                          prefixHint ? lastHint : "",
9357                          prefixHint ? " " : "" );
9358
9359                 if( buf1[0] != NULLCHAR ) {
9360                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9361
9362                     if( strlen(pv) > max_len ) {
9363                         if( appData.debugMode) {
9364                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9365                         }
9366                         pv[max_len+1] = '\0';
9367                     }
9368
9369                     strcat( thinkOutput, pv);
9370                 }
9371
9372                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9373                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9374                     DisplayMove(currentMove - 1);
9375                 }
9376                 return;
9377
9378             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9379                 /* crafty (9.25+) says "(only move) <move>"
9380                  * if there is only 1 legal move
9381                  */
9382                 sscanf(p, "(only move) %s", buf1);
9383                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9384                 sprintf(programStats.movelist, "%s (only move)", buf1);
9385                 programStats.depth = 1;
9386                 programStats.nr_moves = 1;
9387                 programStats.moves_left = 1;
9388                 programStats.nodes = 1;
9389                 programStats.time = 1;
9390                 programStats.got_only_move = 1;
9391
9392                 /* Not really, but we also use this member to
9393                    mean "line isn't going to change" (Crafty
9394                    isn't searching, so stats won't change) */
9395                 programStats.line_is_book = 1;
9396
9397                 SendProgramStatsToFrontend( cps, &programStats );
9398
9399                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9400                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9401                     DisplayMove(currentMove - 1);
9402                 }
9403                 return;
9404             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9405                               &time, &nodes, &plylev, &mvleft,
9406                               &mvtot, mvname) >= 5) {
9407                 /* The stat01: line is from Crafty (9.29+) in response
9408                    to the "." command */
9409                 programStats.seen_stat = 1;
9410                 cps->maybeThinking = TRUE;
9411
9412                 if (programStats.got_only_move || !appData.periodicUpdates)
9413                   return;
9414
9415                 programStats.depth = plylev;
9416                 programStats.time = time;
9417                 programStats.nodes = nodes;
9418                 programStats.moves_left = mvleft;
9419                 programStats.nr_moves = mvtot;
9420                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9421                 programStats.ok_to_send = 1;
9422                 programStats.movelist[0] = '\0';
9423
9424                 SendProgramStatsToFrontend( cps, &programStats );
9425
9426                 return;
9427
9428             } else if (strncmp(message,"++",2) == 0) {
9429                 /* Crafty 9.29+ outputs this */
9430                 programStats.got_fail = 2;
9431                 return;
9432
9433             } else if (strncmp(message,"--",2) == 0) {
9434                 /* Crafty 9.29+ outputs this */
9435                 programStats.got_fail = 1;
9436                 return;
9437
9438             } else if (thinkOutput[0] != NULLCHAR &&
9439                        strncmp(message, "    ", 4) == 0) {
9440                 unsigned message_len;
9441
9442                 p = message;
9443                 while (*p && *p == ' ') p++;
9444
9445                 message_len = strlen( p );
9446
9447                 /* [AS] Avoid buffer overflow */
9448                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9449                     strcat(thinkOutput, " ");
9450                     strcat(thinkOutput, p);
9451                 }
9452
9453                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9454                     strcat(programStats.movelist, " ");
9455                     strcat(programStats.movelist, p);
9456                 }
9457
9458                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9459                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9460                     DisplayMove(currentMove - 1);
9461                 }
9462                 return;
9463             }
9464         }
9465         else {
9466             buf1[0] = NULLCHAR;
9467
9468             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9469                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9470             {
9471                 ChessProgramStats cpstats;
9472
9473                 if (plyext != ' ' && plyext != '\t') {
9474                     time *= 100;
9475                 }
9476
9477                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9478                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9479                     curscore = -curscore;
9480                 }
9481
9482                 cpstats.depth = plylev;
9483                 cpstats.nodes = nodes;
9484                 cpstats.time = time;
9485                 cpstats.score = curscore;
9486                 cpstats.got_only_move = 0;
9487                 cpstats.movelist[0] = '\0';
9488
9489                 if (buf1[0] != NULLCHAR) {
9490                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9491                 }
9492
9493                 cpstats.ok_to_send = 0;
9494                 cpstats.line_is_book = 0;
9495                 cpstats.nr_moves = 0;
9496                 cpstats.moves_left = 0;
9497
9498                 SendProgramStatsToFrontend( cps, &cpstats );
9499             }
9500         }
9501     }
9502 }
9503
9504
9505 /* Parse a game score from the character string "game", and
9506    record it as the history of the current game.  The game
9507    score is NOT assumed to start from the standard position.
9508    The display is not updated in any way.
9509    */
9510 void
9511 ParseGameHistory (char *game)
9512 {
9513     ChessMove moveType;
9514     int fromX, fromY, toX, toY, boardIndex;
9515     char promoChar;
9516     char *p, *q;
9517     char buf[MSG_SIZ];
9518
9519     if (appData.debugMode)
9520       fprintf(debugFP, "Parsing game history: %s\n", game);
9521
9522     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9523     gameInfo.site = StrSave(appData.icsHost);
9524     gameInfo.date = PGNDate();
9525     gameInfo.round = StrSave("-");
9526
9527     /* Parse out names of players */
9528     while (*game == ' ') game++;
9529     p = buf;
9530     while (*game != ' ') *p++ = *game++;
9531     *p = NULLCHAR;
9532     gameInfo.white = StrSave(buf);
9533     while (*game == ' ') game++;
9534     p = buf;
9535     while (*game != ' ' && *game != '\n') *p++ = *game++;
9536     *p = NULLCHAR;
9537     gameInfo.black = StrSave(buf);
9538
9539     /* Parse moves */
9540     boardIndex = blackPlaysFirst ? 1 : 0;
9541     yynewstr(game);
9542     for (;;) {
9543         yyboardindex = boardIndex;
9544         moveType = (ChessMove) Myylex();
9545         switch (moveType) {
9546           case IllegalMove:             /* maybe suicide chess, etc. */
9547   if (appData.debugMode) {
9548     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9549     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9550     setbuf(debugFP, NULL);
9551   }
9552           case WhitePromotion:
9553           case BlackPromotion:
9554           case WhiteNonPromotion:
9555           case BlackNonPromotion:
9556           case NormalMove:
9557           case FirstLeg:
9558           case WhiteCapturesEnPassant:
9559           case BlackCapturesEnPassant:
9560           case WhiteKingSideCastle:
9561           case WhiteQueenSideCastle:
9562           case BlackKingSideCastle:
9563           case BlackQueenSideCastle:
9564           case WhiteKingSideCastleWild:
9565           case WhiteQueenSideCastleWild:
9566           case BlackKingSideCastleWild:
9567           case BlackQueenSideCastleWild:
9568           /* PUSH Fabien */
9569           case WhiteHSideCastleFR:
9570           case WhiteASideCastleFR:
9571           case BlackHSideCastleFR:
9572           case BlackASideCastleFR:
9573           /* POP Fabien */
9574             fromX = currentMoveString[0] - AAA;
9575             fromY = currentMoveString[1] - ONE;
9576             toX = currentMoveString[2] - AAA;
9577             toY = currentMoveString[3] - ONE;
9578             promoChar = currentMoveString[4];
9579             break;
9580           case WhiteDrop:
9581           case BlackDrop:
9582             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9583             fromX = moveType == WhiteDrop ?
9584               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9585             (int) CharToPiece(ToLower(currentMoveString[0]));
9586             fromY = DROP_RANK;
9587             toX = currentMoveString[2] - AAA;
9588             toY = currentMoveString[3] - ONE;
9589             promoChar = NULLCHAR;
9590             break;
9591           case AmbiguousMove:
9592             /* bug? */
9593             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9594   if (appData.debugMode) {
9595     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9596     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9597     setbuf(debugFP, NULL);
9598   }
9599             DisplayError(buf, 0);
9600             return;
9601           case ImpossibleMove:
9602             /* bug? */
9603             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9604   if (appData.debugMode) {
9605     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9606     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9607     setbuf(debugFP, NULL);
9608   }
9609             DisplayError(buf, 0);
9610             return;
9611           case EndOfFile:
9612             if (boardIndex < backwardMostMove) {
9613                 /* Oops, gap.  How did that happen? */
9614                 DisplayError(_("Gap in move list"), 0);
9615                 return;
9616             }
9617             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9618             if (boardIndex > forwardMostMove) {
9619                 forwardMostMove = boardIndex;
9620             }
9621             return;
9622           case ElapsedTime:
9623             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9624                 strcat(parseList[boardIndex-1], " ");
9625                 strcat(parseList[boardIndex-1], yy_text);
9626             }
9627             continue;
9628           case Comment:
9629           case PGNTag:
9630           case NAG:
9631           default:
9632             /* ignore */
9633             continue;
9634           case WhiteWins:
9635           case BlackWins:
9636           case GameIsDrawn:
9637           case GameUnfinished:
9638             if (gameMode == IcsExamining) {
9639                 if (boardIndex < backwardMostMove) {
9640                     /* Oops, gap.  How did that happen? */
9641                     return;
9642                 }
9643                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9644                 return;
9645             }
9646             gameInfo.result = moveType;
9647             p = strchr(yy_text, '{');
9648             if (p == NULL) p = strchr(yy_text, '(');
9649             if (p == NULL) {
9650                 p = yy_text;
9651                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9652             } else {
9653                 q = strchr(p, *p == '{' ? '}' : ')');
9654                 if (q != NULL) *q = NULLCHAR;
9655                 p++;
9656             }
9657             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9658             gameInfo.resultDetails = StrSave(p);
9659             continue;
9660         }
9661         if (boardIndex >= forwardMostMove &&
9662             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9663             backwardMostMove = blackPlaysFirst ? 1 : 0;
9664             return;
9665         }
9666         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9667                                  fromY, fromX, toY, toX, promoChar,
9668                                  parseList[boardIndex]);
9669         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9670         /* currentMoveString is set as a side-effect of yylex */
9671         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9672         strcat(moveList[boardIndex], "\n");
9673         boardIndex++;
9674         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9675         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9676           case MT_NONE:
9677           case MT_STALEMATE:
9678           default:
9679             break;
9680           case MT_CHECK:
9681             if(gameInfo.variant != VariantShogi)
9682                 strcat(parseList[boardIndex - 1], "+");
9683             break;
9684           case MT_CHECKMATE:
9685           case MT_STAINMATE:
9686             strcat(parseList[boardIndex - 1], "#");
9687             break;
9688         }
9689     }
9690 }
9691
9692
9693 /* Apply a move to the given board  */
9694 void
9695 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9696 {
9697   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9698   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9699
9700     /* [HGM] compute & store e.p. status and castling rights for new position */
9701     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9702
9703       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9704       oldEP = (signed char)board[EP_STATUS];
9705       board[EP_STATUS] = EP_NONE;
9706
9707   if (fromY == DROP_RANK) {
9708         /* must be first */
9709         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9710             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9711             return;
9712         }
9713         piece = board[toY][toX] = (ChessSquare) fromX;
9714   } else {
9715       ChessSquare victim;
9716       int i;
9717
9718       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9719            victim = board[killY][killX],
9720            board[killY][killX] = EmptySquare,
9721            board[EP_STATUS] = EP_CAPTURE;
9722
9723       if( board[toY][toX] != EmptySquare ) {
9724            board[EP_STATUS] = EP_CAPTURE;
9725            if( (fromX != toX || fromY != toY) && // not igui!
9726                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9727                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9728                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9729            }
9730       }
9731
9732       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9733            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9734                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9735       } else
9736       if( board[fromY][fromX] == WhitePawn ) {
9737            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9738                board[EP_STATUS] = EP_PAWN_MOVE;
9739            if( toY-fromY==2) {
9740                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9741                         gameInfo.variant != VariantBerolina || toX < fromX)
9742                       board[EP_STATUS] = toX | berolina;
9743                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9744                         gameInfo.variant != VariantBerolina || toX > fromX)
9745                       board[EP_STATUS] = toX;
9746            }
9747       } else
9748       if( board[fromY][fromX] == BlackPawn ) {
9749            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9750                board[EP_STATUS] = EP_PAWN_MOVE;
9751            if( toY-fromY== -2) {
9752                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9753                         gameInfo.variant != VariantBerolina || toX < fromX)
9754                       board[EP_STATUS] = toX | berolina;
9755                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9756                         gameInfo.variant != VariantBerolina || toX > fromX)
9757                       board[EP_STATUS] = toX;
9758            }
9759        }
9760
9761        for(i=0; i<nrCastlingRights; i++) {
9762            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9763               board[CASTLING][i] == toX   && castlingRank[i] == toY
9764              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9765        }
9766
9767        if(gameInfo.variant == VariantSChess) { // update virginity
9768            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9769            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9770            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9771            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9772        }
9773
9774      if (fromX == toX && fromY == toY) return;
9775
9776      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9777      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9778      if(gameInfo.variant == VariantKnightmate)
9779          king += (int) WhiteUnicorn - (int) WhiteKing;
9780
9781     /* Code added by Tord: */
9782     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9783     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9784         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9785       board[fromY][fromX] = EmptySquare;
9786       board[toY][toX] = EmptySquare;
9787       if((toX > fromX) != (piece == WhiteRook)) {
9788         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9789       } else {
9790         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9791       }
9792     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9793                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9794       board[fromY][fromX] = EmptySquare;
9795       board[toY][toX] = EmptySquare;
9796       if((toX > fromX) != (piece == BlackRook)) {
9797         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9798       } else {
9799         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9800       }
9801     /* End of code added by Tord */
9802
9803     } else if (board[fromY][fromX] == king
9804         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9805         && toY == fromY && toX > fromX+1) {
9806         board[fromY][fromX] = EmptySquare;
9807         board[toY][toX] = king;
9808         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9809         board[fromY][BOARD_RGHT-1] = EmptySquare;
9810     } else if (board[fromY][fromX] == king
9811         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9812                && toY == fromY && toX < fromX-1) {
9813         board[fromY][fromX] = EmptySquare;
9814         board[toY][toX] = king;
9815         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9816         board[fromY][BOARD_LEFT] = EmptySquare;
9817     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9818                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9819                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9820                ) {
9821         /* white pawn promotion */
9822         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9823         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9824             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9825         board[fromY][fromX] = EmptySquare;
9826     } else if ((fromY >= BOARD_HEIGHT>>1)
9827                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9828                && (toX != fromX)
9829                && gameInfo.variant != VariantXiangqi
9830                && gameInfo.variant != VariantBerolina
9831                && (board[fromY][fromX] == WhitePawn)
9832                && (board[toY][toX] == EmptySquare)) {
9833         board[fromY][fromX] = EmptySquare;
9834         board[toY][toX] = WhitePawn;
9835         captured = board[toY - 1][toX];
9836         board[toY - 1][toX] = EmptySquare;
9837     } else if ((fromY == BOARD_HEIGHT-4)
9838                && (toX == fromX)
9839                && gameInfo.variant == VariantBerolina
9840                && (board[fromY][fromX] == WhitePawn)
9841                && (board[toY][toX] == EmptySquare)) {
9842         board[fromY][fromX] = EmptySquare;
9843         board[toY][toX] = WhitePawn;
9844         if(oldEP & EP_BEROLIN_A) {
9845                 captured = board[fromY][fromX-1];
9846                 board[fromY][fromX-1] = EmptySquare;
9847         }else{  captured = board[fromY][fromX+1];
9848                 board[fromY][fromX+1] = EmptySquare;
9849         }
9850     } else if (board[fromY][fromX] == king
9851         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9852                && toY == fromY && toX > fromX+1) {
9853         board[fromY][fromX] = EmptySquare;
9854         board[toY][toX] = king;
9855         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9856         board[fromY][BOARD_RGHT-1] = EmptySquare;
9857     } else if (board[fromY][fromX] == king
9858         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9859                && toY == fromY && toX < fromX-1) {
9860         board[fromY][fromX] = EmptySquare;
9861         board[toY][toX] = king;
9862         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9863         board[fromY][BOARD_LEFT] = EmptySquare;
9864     } else if (fromY == 7 && fromX == 3
9865                && board[fromY][fromX] == BlackKing
9866                && toY == 7 && toX == 5) {
9867         board[fromY][fromX] = EmptySquare;
9868         board[toY][toX] = BlackKing;
9869         board[fromY][7] = EmptySquare;
9870         board[toY][4] = BlackRook;
9871     } else if (fromY == 7 && fromX == 3
9872                && board[fromY][fromX] == BlackKing
9873                && toY == 7 && toX == 1) {
9874         board[fromY][fromX] = EmptySquare;
9875         board[toY][toX] = BlackKing;
9876         board[fromY][0] = EmptySquare;
9877         board[toY][2] = BlackRook;
9878     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9879                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9880                && toY < promoRank && promoChar
9881                ) {
9882         /* black pawn promotion */
9883         board[toY][toX] = CharToPiece(ToLower(promoChar));
9884         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9885             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9886         board[fromY][fromX] = EmptySquare;
9887     } else if ((fromY < BOARD_HEIGHT>>1)
9888                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9889                && (toX != fromX)
9890                && gameInfo.variant != VariantXiangqi
9891                && gameInfo.variant != VariantBerolina
9892                && (board[fromY][fromX] == BlackPawn)
9893                && (board[toY][toX] == EmptySquare)) {
9894         board[fromY][fromX] = EmptySquare;
9895         board[toY][toX] = BlackPawn;
9896         captured = board[toY + 1][toX];
9897         board[toY + 1][toX] = EmptySquare;
9898     } else if ((fromY == 3)
9899                && (toX == fromX)
9900                && gameInfo.variant == VariantBerolina
9901                && (board[fromY][fromX] == BlackPawn)
9902                && (board[toY][toX] == EmptySquare)) {
9903         board[fromY][fromX] = EmptySquare;
9904         board[toY][toX] = BlackPawn;
9905         if(oldEP & EP_BEROLIN_A) {
9906                 captured = board[fromY][fromX-1];
9907                 board[fromY][fromX-1] = EmptySquare;
9908         }else{  captured = board[fromY][fromX+1];
9909                 board[fromY][fromX+1] = EmptySquare;
9910         }
9911     } else {
9912         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9913         board[fromY][fromX] = EmptySquare;
9914         board[toY][toX] = piece;
9915     }
9916   }
9917
9918     if (gameInfo.holdingsWidth != 0) {
9919
9920       /* !!A lot more code needs to be written to support holdings  */
9921       /* [HGM] OK, so I have written it. Holdings are stored in the */
9922       /* penultimate board files, so they are automaticlly stored   */
9923       /* in the game history.                                       */
9924       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9925                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9926         /* Delete from holdings, by decreasing count */
9927         /* and erasing image if necessary            */
9928         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9929         if(p < (int) BlackPawn) { /* white drop */
9930              p -= (int)WhitePawn;
9931                  p = PieceToNumber((ChessSquare)p);
9932              if(p >= gameInfo.holdingsSize) p = 0;
9933              if(--board[p][BOARD_WIDTH-2] <= 0)
9934                   board[p][BOARD_WIDTH-1] = EmptySquare;
9935              if((int)board[p][BOARD_WIDTH-2] < 0)
9936                         board[p][BOARD_WIDTH-2] = 0;
9937         } else {                  /* black drop */
9938              p -= (int)BlackPawn;
9939                  p = PieceToNumber((ChessSquare)p);
9940              if(p >= gameInfo.holdingsSize) p = 0;
9941              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9942                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9943              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9944                         board[BOARD_HEIGHT-1-p][1] = 0;
9945         }
9946       }
9947       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9948           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9949         /* [HGM] holdings: Add to holdings, if holdings exist */
9950         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9951                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9952                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9953         }
9954         p = (int) captured;
9955         if (p >= (int) BlackPawn) {
9956           p -= (int)BlackPawn;
9957           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9958                   /* in Shogi restore piece to its original  first */
9959                   captured = (ChessSquare) (DEMOTED captured);
9960                   p = DEMOTED p;
9961           }
9962           p = PieceToNumber((ChessSquare)p);
9963           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9964           board[p][BOARD_WIDTH-2]++;
9965           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9966         } else {
9967           p -= (int)WhitePawn;
9968           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9969                   captured = (ChessSquare) (DEMOTED captured);
9970                   p = DEMOTED p;
9971           }
9972           p = PieceToNumber((ChessSquare)p);
9973           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9974           board[BOARD_HEIGHT-1-p][1]++;
9975           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9976         }
9977       }
9978     } else if (gameInfo.variant == VariantAtomic) {
9979       if (captured != EmptySquare) {
9980         int y, x;
9981         for (y = toY-1; y <= toY+1; y++) {
9982           for (x = toX-1; x <= toX+1; x++) {
9983             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9984                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9985               board[y][x] = EmptySquare;
9986             }
9987           }
9988         }
9989         board[toY][toX] = EmptySquare;
9990       }
9991     }
9992     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9993         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9994     } else
9995     if(promoChar == '+') {
9996         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9997         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9998         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
9999           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10000     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10001         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10002         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10003            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10004         board[toY][toX] = newPiece;
10005     }
10006     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10007                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10008         // [HGM] superchess: take promotion piece out of holdings
10009         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10010         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10011             if(!--board[k][BOARD_WIDTH-2])
10012                 board[k][BOARD_WIDTH-1] = EmptySquare;
10013         } else {
10014             if(!--board[BOARD_HEIGHT-1-k][1])
10015                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10016         }
10017     }
10018 }
10019
10020 /* Updates forwardMostMove */
10021 void
10022 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10023 {
10024     int x = toX, y = toY;
10025     char *s = parseList[forwardMostMove];
10026     ChessSquare p = boards[forwardMostMove][toY][toX];
10027 //    forwardMostMove++; // [HGM] bare: moved downstream
10028
10029     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10030     (void) CoordsToAlgebraic(boards[forwardMostMove],
10031                              PosFlags(forwardMostMove),
10032                              fromY, fromX, y, x, promoChar,
10033                              s);
10034     if(killX >= 0 && killY >= 0)
10035         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10036
10037     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10038         int timeLeft; static int lastLoadFlag=0; int king, piece;
10039         piece = boards[forwardMostMove][fromY][fromX];
10040         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10041         if(gameInfo.variant == VariantKnightmate)
10042             king += (int) WhiteUnicorn - (int) WhiteKing;
10043         if(forwardMostMove == 0) {
10044             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10045                 fprintf(serverMoves, "%s;", UserName());
10046             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10047                 fprintf(serverMoves, "%s;", second.tidy);
10048             fprintf(serverMoves, "%s;", first.tidy);
10049             if(gameMode == MachinePlaysWhite)
10050                 fprintf(serverMoves, "%s;", UserName());
10051             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10052                 fprintf(serverMoves, "%s;", second.tidy);
10053         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10054         lastLoadFlag = loadFlag;
10055         // print base move
10056         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10057         // print castling suffix
10058         if( toY == fromY && piece == king ) {
10059             if(toX-fromX > 1)
10060                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10061             if(fromX-toX >1)
10062                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10063         }
10064         // e.p. suffix
10065         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10066              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10067              boards[forwardMostMove][toY][toX] == EmptySquare
10068              && fromX != toX && fromY != toY)
10069                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10070         // promotion suffix
10071         if(promoChar != NULLCHAR) {
10072             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10073                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10074                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10075             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10076         }
10077         if(!loadFlag) {
10078                 char buf[MOVE_LEN*2], *p; int len;
10079             fprintf(serverMoves, "/%d/%d",
10080                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10081             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10082             else                      timeLeft = blackTimeRemaining/1000;
10083             fprintf(serverMoves, "/%d", timeLeft);
10084                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10085                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10086                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10087                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10088             fprintf(serverMoves, "/%s", buf);
10089         }
10090         fflush(serverMoves);
10091     }
10092
10093     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10094         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10095       return;
10096     }
10097     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10098     if (commentList[forwardMostMove+1] != NULL) {
10099         free(commentList[forwardMostMove+1]);
10100         commentList[forwardMostMove+1] = NULL;
10101     }
10102     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10103     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10104     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10105     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10106     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10107     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10108     adjustedClock = FALSE;
10109     gameInfo.result = GameUnfinished;
10110     if (gameInfo.resultDetails != NULL) {
10111         free(gameInfo.resultDetails);
10112         gameInfo.resultDetails = NULL;
10113     }
10114     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10115                               moveList[forwardMostMove - 1]);
10116     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10117       case MT_NONE:
10118       case MT_STALEMATE:
10119       default:
10120         break;
10121       case MT_CHECK:
10122         if(gameInfo.variant != VariantShogi)
10123             strcat(parseList[forwardMostMove - 1], "+");
10124         break;
10125       case MT_CHECKMATE:
10126       case MT_STAINMATE:
10127         strcat(parseList[forwardMostMove - 1], "#");
10128         break;
10129     }
10130 }
10131
10132 /* Updates currentMove if not pausing */
10133 void
10134 ShowMove (int fromX, int fromY, int toX, int toY)
10135 {
10136     int instant = (gameMode == PlayFromGameFile) ?
10137         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10138     if(appData.noGUI) return;
10139     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10140         if (!instant) {
10141             if (forwardMostMove == currentMove + 1) {
10142                 AnimateMove(boards[forwardMostMove - 1],
10143                             fromX, fromY, toX, toY);
10144             }
10145         }
10146         currentMove = forwardMostMove;
10147     }
10148
10149     killX = killY = -1; // [HGM] lion: used up
10150
10151     if (instant) return;
10152
10153     DisplayMove(currentMove - 1);
10154     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10155             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10156                 SetHighlights(fromX, fromY, toX, toY);
10157             }
10158     }
10159     DrawPosition(FALSE, boards[currentMove]);
10160     DisplayBothClocks();
10161     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10162 }
10163
10164 void
10165 SendEgtPath (ChessProgramState *cps)
10166 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10167         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10168
10169         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10170
10171         while(*p) {
10172             char c, *q = name+1, *r, *s;
10173
10174             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10175             while(*p && *p != ',') *q++ = *p++;
10176             *q++ = ':'; *q = 0;
10177             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10178                 strcmp(name, ",nalimov:") == 0 ) {
10179                 // take nalimov path from the menu-changeable option first, if it is defined
10180               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10181                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10182             } else
10183             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10184                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10185                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10186                 s = r = StrStr(s, ":") + 1; // beginning of path info
10187                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10188                 c = *r; *r = 0;             // temporarily null-terminate path info
10189                     *--q = 0;               // strip of trailig ':' from name
10190                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10191                 *r = c;
10192                 SendToProgram(buf,cps);     // send egtbpath command for this format
10193             }
10194             if(*p == ',') p++; // read away comma to position for next format name
10195         }
10196 }
10197
10198 static int
10199 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10200 {
10201       int width = 8, height = 8, holdings = 0;             // most common sizes
10202       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10203       // correct the deviations default for each variant
10204       if( v == VariantXiangqi ) width = 9,  height = 10;
10205       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10206       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10207       if( v == VariantCapablanca || v == VariantCapaRandom ||
10208           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10209                                 width = 10;
10210       if( v == VariantCourier ) width = 12;
10211       if( v == VariantSuper )                            holdings = 8;
10212       if( v == VariantGreat )   width = 10,              holdings = 8;
10213       if( v == VariantSChess )                           holdings = 7;
10214       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10215       if( v == VariantChuChess) width = 10, height = 10;
10216       if( v == VariantChu )     width = 12, height = 12;
10217       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10218              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10219              holdingsSize >= 0 && holdingsSize != holdings;
10220 }
10221
10222 char variantError[MSG_SIZ];
10223
10224 char *
10225 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10226 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10227       char *p, *variant = VariantName(v);
10228       static char b[MSG_SIZ];
10229       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10230            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10231                                                holdingsSize, variant); // cook up sized variant name
10232            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10233            if(StrStr(list, b) == NULL) {
10234                // specific sized variant not known, check if general sizing allowed
10235                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10236                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10237                             boardWidth, boardHeight, holdingsSize, engine);
10238                    return NULL;
10239                }
10240                /* [HGM] here we really should compare with the maximum supported board size */
10241            }
10242       } else snprintf(b, MSG_SIZ,"%s", variant);
10243       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10244       p = StrStr(list, b);
10245       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10246       if(p == NULL) {
10247           // occurs not at all in list, or only as sub-string
10248           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10249           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10250               int l = strlen(variantError);
10251               char *q;
10252               while(p != list && p[-1] != ',') p--;
10253               q = strchr(p, ',');
10254               if(q) *q = NULLCHAR;
10255               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10256               if(q) *q= ',';
10257           }
10258           return NULL;
10259       }
10260       return b;
10261 }
10262
10263 void
10264 InitChessProgram (ChessProgramState *cps, int setup)
10265 /* setup needed to setup FRC opening position */
10266 {
10267     char buf[MSG_SIZ], *b;
10268     if (appData.noChessProgram) return;
10269     hintRequested = FALSE;
10270     bookRequested = FALSE;
10271
10272     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10273     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10274     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10275     if(cps->memSize) { /* [HGM] memory */
10276       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10277         SendToProgram(buf, cps);
10278     }
10279     SendEgtPath(cps); /* [HGM] EGT */
10280     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10281       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10282         SendToProgram(buf, cps);
10283     }
10284
10285     SendToProgram(cps->initString, cps);
10286     if (gameInfo.variant != VariantNormal &&
10287         gameInfo.variant != VariantLoadable
10288         /* [HGM] also send variant if board size non-standard */
10289         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10290
10291       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10292                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10293       if (b == NULL) {
10294         DisplayFatalError(variantError, 0, 1);
10295         return;
10296       }
10297
10298       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10299       SendToProgram(buf, cps);
10300     }
10301     currentlyInitializedVariant = gameInfo.variant;
10302
10303     /* [HGM] send opening position in FRC to first engine */
10304     if(setup) {
10305           SendToProgram("force\n", cps);
10306           SendBoard(cps, 0);
10307           /* engine is now in force mode! Set flag to wake it up after first move. */
10308           setboardSpoiledMachineBlack = 1;
10309     }
10310
10311     if (cps->sendICS) {
10312       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10313       SendToProgram(buf, cps);
10314     }
10315     cps->maybeThinking = FALSE;
10316     cps->offeredDraw = 0;
10317     if (!appData.icsActive) {
10318         SendTimeControl(cps, movesPerSession, timeControl,
10319                         timeIncrement, appData.searchDepth,
10320                         searchTime);
10321     }
10322     if (appData.showThinking
10323         // [HGM] thinking: four options require thinking output to be sent
10324         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10325                                 ) {
10326         SendToProgram("post\n", cps);
10327     }
10328     SendToProgram("hard\n", cps);
10329     if (!appData.ponderNextMove) {
10330         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10331            it without being sure what state we are in first.  "hard"
10332            is not a toggle, so that one is OK.
10333          */
10334         SendToProgram("easy\n", cps);
10335     }
10336     if (cps->usePing) {
10337       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10338       SendToProgram(buf, cps);
10339     }
10340     cps->initDone = TRUE;
10341     ClearEngineOutputPane(cps == &second);
10342 }
10343
10344
10345 void
10346 ResendOptions (ChessProgramState *cps)
10347 { // send the stored value of the options
10348   int i;
10349   char buf[MSG_SIZ];
10350   Option *opt = cps->option;
10351   for(i=0; i<cps->nrOptions; i++, opt++) {
10352       switch(opt->type) {
10353         case Spin:
10354         case Slider:
10355         case CheckBox:
10356             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10357           break;
10358         case ComboBox:
10359           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10360           break;
10361         default:
10362             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10363           break;
10364         case Button:
10365         case SaveButton:
10366           continue;
10367       }
10368       SendToProgram(buf, cps);
10369   }
10370 }
10371
10372 void
10373 StartChessProgram (ChessProgramState *cps)
10374 {
10375     char buf[MSG_SIZ];
10376     int err;
10377
10378     if (appData.noChessProgram) return;
10379     cps->initDone = FALSE;
10380
10381     if (strcmp(cps->host, "localhost") == 0) {
10382         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10383     } else if (*appData.remoteShell == NULLCHAR) {
10384         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10385     } else {
10386         if (*appData.remoteUser == NULLCHAR) {
10387           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10388                     cps->program);
10389         } else {
10390           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10391                     cps->host, appData.remoteUser, cps->program);
10392         }
10393         err = StartChildProcess(buf, "", &cps->pr);
10394     }
10395
10396     if (err != 0) {
10397       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10398         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10399         if(cps != &first) return;
10400         appData.noChessProgram = TRUE;
10401         ThawUI();
10402         SetNCPMode();
10403 //      DisplayFatalError(buf, err, 1);
10404 //      cps->pr = NoProc;
10405 //      cps->isr = NULL;
10406         return;
10407     }
10408
10409     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10410     if (cps->protocolVersion > 1) {
10411       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10412       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10413         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10414         cps->comboCnt = 0;  //                and values of combo boxes
10415       }
10416       SendToProgram(buf, cps);
10417       if(cps->reload) ResendOptions(cps);
10418     } else {
10419       SendToProgram("xboard\n", cps);
10420     }
10421 }
10422
10423 void
10424 TwoMachinesEventIfReady P((void))
10425 {
10426   static int curMess = 0;
10427   if (first.lastPing != first.lastPong) {
10428     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10429     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10430     return;
10431   }
10432   if (second.lastPing != second.lastPong) {
10433     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10434     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10435     return;
10436   }
10437   DisplayMessage("", ""); curMess = 0;
10438   TwoMachinesEvent();
10439 }
10440
10441 char *
10442 MakeName (char *template)
10443 {
10444     time_t clock;
10445     struct tm *tm;
10446     static char buf[MSG_SIZ];
10447     char *p = buf;
10448     int i;
10449
10450     clock = time((time_t *)NULL);
10451     tm = localtime(&clock);
10452
10453     while(*p++ = *template++) if(p[-1] == '%') {
10454         switch(*template++) {
10455           case 0:   *p = 0; return buf;
10456           case 'Y': i = tm->tm_year+1900; break;
10457           case 'y': i = tm->tm_year-100; break;
10458           case 'M': i = tm->tm_mon+1; break;
10459           case 'd': i = tm->tm_mday; break;
10460           case 'h': i = tm->tm_hour; break;
10461           case 'm': i = tm->tm_min; break;
10462           case 's': i = tm->tm_sec; break;
10463           default:  i = 0;
10464         }
10465         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10466     }
10467     return buf;
10468 }
10469
10470 int
10471 CountPlayers (char *p)
10472 {
10473     int n = 0;
10474     while(p = strchr(p, '\n')) p++, n++; // count participants
10475     return n;
10476 }
10477
10478 FILE *
10479 WriteTourneyFile (char *results, FILE *f)
10480 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10481     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10482     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10483         // create a file with tournament description
10484         fprintf(f, "-participants {%s}\n", appData.participants);
10485         fprintf(f, "-seedBase %d\n", appData.seedBase);
10486         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10487         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10488         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10489         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10490         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10491         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10492         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10493         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10494         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10495         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10496         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10497         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10498         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10499         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10500         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10501         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10502         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10503         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10504         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10505         fprintf(f, "-smpCores %d\n", appData.smpCores);
10506         if(searchTime > 0)
10507                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10508         else {
10509                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10510                 fprintf(f, "-tc %s\n", appData.timeControl);
10511                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10512         }
10513         fprintf(f, "-results \"%s\"\n", results);
10514     }
10515     return f;
10516 }
10517
10518 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10519
10520 void
10521 Substitute (char *participants, int expunge)
10522 {
10523     int i, changed, changes=0, nPlayers=0;
10524     char *p, *q, *r, buf[MSG_SIZ];
10525     if(participants == NULL) return;
10526     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10527     r = p = participants; q = appData.participants;
10528     while(*p && *p == *q) {
10529         if(*p == '\n') r = p+1, nPlayers++;
10530         p++; q++;
10531     }
10532     if(*p) { // difference
10533         while(*p && *p++ != '\n');
10534         while(*q && *q++ != '\n');
10535       changed = nPlayers;
10536         changes = 1 + (strcmp(p, q) != 0);
10537     }
10538     if(changes == 1) { // a single engine mnemonic was changed
10539         q = r; while(*q) nPlayers += (*q++ == '\n');
10540         p = buf; while(*r && (*p = *r++) != '\n') p++;
10541         *p = NULLCHAR;
10542         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10543         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10544         if(mnemonic[i]) { // The substitute is valid
10545             FILE *f;
10546             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10547                 flock(fileno(f), LOCK_EX);
10548                 ParseArgsFromFile(f);
10549                 fseek(f, 0, SEEK_SET);
10550                 FREE(appData.participants); appData.participants = participants;
10551                 if(expunge) { // erase results of replaced engine
10552                     int len = strlen(appData.results), w, b, dummy;
10553                     for(i=0; i<len; i++) {
10554                         Pairing(i, nPlayers, &w, &b, &dummy);
10555                         if((w == changed || b == changed) && appData.results[i] == '*') {
10556                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10557                             fclose(f);
10558                             return;
10559                         }
10560                     }
10561                     for(i=0; i<len; i++) {
10562                         Pairing(i, nPlayers, &w, &b, &dummy);
10563                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10564                     }
10565                 }
10566                 WriteTourneyFile(appData.results, f);
10567                 fclose(f); // release lock
10568                 return;
10569             }
10570         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10571     }
10572     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10573     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10574     free(participants);
10575     return;
10576 }
10577
10578 int
10579 CheckPlayers (char *participants)
10580 {
10581         int i;
10582         char buf[MSG_SIZ], *p;
10583         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10584         while(p = strchr(participants, '\n')) {
10585             *p = NULLCHAR;
10586             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10587             if(!mnemonic[i]) {
10588                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10589                 *p = '\n';
10590                 DisplayError(buf, 0);
10591                 return 1;
10592             }
10593             *p = '\n';
10594             participants = p + 1;
10595         }
10596         return 0;
10597 }
10598
10599 int
10600 CreateTourney (char *name)
10601 {
10602         FILE *f;
10603         if(matchMode && strcmp(name, appData.tourneyFile)) {
10604              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10605         }
10606         if(name[0] == NULLCHAR) {
10607             if(appData.participants[0])
10608                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10609             return 0;
10610         }
10611         f = fopen(name, "r");
10612         if(f) { // file exists
10613             ASSIGN(appData.tourneyFile, name);
10614             ParseArgsFromFile(f); // parse it
10615         } else {
10616             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10617             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10618                 DisplayError(_("Not enough participants"), 0);
10619                 return 0;
10620             }
10621             if(CheckPlayers(appData.participants)) return 0;
10622             ASSIGN(appData.tourneyFile, name);
10623             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10624             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10625         }
10626         fclose(f);
10627         appData.noChessProgram = FALSE;
10628         appData.clockMode = TRUE;
10629         SetGNUMode();
10630         return 1;
10631 }
10632
10633 int
10634 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10635 {
10636     char buf[MSG_SIZ], *p, *q;
10637     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10638     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10639     skip = !all && group[0]; // if group requested, we start in skip mode
10640     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10641         p = names; q = buf; header = 0;
10642         while(*p && *p != '\n') *q++ = *p++;
10643         *q = 0;
10644         if(*p == '\n') p++;
10645         if(buf[0] == '#') {
10646             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10647             depth++; // we must be entering a new group
10648             if(all) continue; // suppress printing group headers when complete list requested
10649             header = 1;
10650             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10651         }
10652         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10653         if(engineList[i]) free(engineList[i]);
10654         engineList[i] = strdup(buf);
10655         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10656         if(engineMnemonic[i]) free(engineMnemonic[i]);
10657         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10658             strcat(buf, " (");
10659             sscanf(q + 8, "%s", buf + strlen(buf));
10660             strcat(buf, ")");
10661         }
10662         engineMnemonic[i] = strdup(buf);
10663         i++;
10664     }
10665     engineList[i] = engineMnemonic[i] = NULL;
10666     return i;
10667 }
10668
10669 // following implemented as macro to avoid type limitations
10670 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10671
10672 void
10673 SwapEngines (int n)
10674 {   // swap settings for first engine and other engine (so far only some selected options)
10675     int h;
10676     char *p;
10677     if(n == 0) return;
10678     SWAP(directory, p)
10679     SWAP(chessProgram, p)
10680     SWAP(isUCI, h)
10681     SWAP(hasOwnBookUCI, h)
10682     SWAP(protocolVersion, h)
10683     SWAP(reuse, h)
10684     SWAP(scoreIsAbsolute, h)
10685     SWAP(timeOdds, h)
10686     SWAP(logo, p)
10687     SWAP(pgnName, p)
10688     SWAP(pvSAN, h)
10689     SWAP(engOptions, p)
10690     SWAP(engInitString, p)
10691     SWAP(computerString, p)
10692     SWAP(features, p)
10693     SWAP(fenOverride, p)
10694     SWAP(NPS, h)
10695     SWAP(accumulateTC, h)
10696     SWAP(host, p)
10697 }
10698
10699 int
10700 GetEngineLine (char *s, int n)
10701 {
10702     int i;
10703     char buf[MSG_SIZ];
10704     extern char *icsNames;
10705     if(!s || !*s) return 0;
10706     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10707     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10708     if(!mnemonic[i]) return 0;
10709     if(n == 11) return 1; // just testing if there was a match
10710     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10711     if(n == 1) SwapEngines(n);
10712     ParseArgsFromString(buf);
10713     if(n == 1) SwapEngines(n);
10714     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10715         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10716         ParseArgsFromString(buf);
10717     }
10718     return 1;
10719 }
10720
10721 int
10722 SetPlayer (int player, char *p)
10723 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10724     int i;
10725     char buf[MSG_SIZ], *engineName;
10726     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10727     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10728     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10729     if(mnemonic[i]) {
10730         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10731         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10732         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10733         ParseArgsFromString(buf);
10734     } else { // no engine with this nickname is installed!
10735         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10736         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10737         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10738         ModeHighlight();
10739         DisplayError(buf, 0);
10740         return 0;
10741     }
10742     free(engineName);
10743     return i;
10744 }
10745
10746 char *recentEngines;
10747
10748 void
10749 RecentEngineEvent (int nr)
10750 {
10751     int n;
10752 //    SwapEngines(1); // bump first to second
10753 //    ReplaceEngine(&second, 1); // and load it there
10754     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10755     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10756     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10757         ReplaceEngine(&first, 0);
10758         FloatToFront(&appData.recentEngineList, command[n]);
10759     }
10760 }
10761
10762 int
10763 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10764 {   // determine players from game number
10765     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10766
10767     if(appData.tourneyType == 0) {
10768         roundsPerCycle = (nPlayers - 1) | 1;
10769         pairingsPerRound = nPlayers / 2;
10770     } else if(appData.tourneyType > 0) {
10771         roundsPerCycle = nPlayers - appData.tourneyType;
10772         pairingsPerRound = appData.tourneyType;
10773     }
10774     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10775     gamesPerCycle = gamesPerRound * roundsPerCycle;
10776     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10777     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10778     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10779     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10780     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10781     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10782
10783     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10784     if(appData.roundSync) *syncInterval = gamesPerRound;
10785
10786     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10787
10788     if(appData.tourneyType == 0) {
10789         if(curPairing == (nPlayers-1)/2 ) {
10790             *whitePlayer = curRound;
10791             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10792         } else {
10793             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10794             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10795             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10796             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10797         }
10798     } else if(appData.tourneyType > 1) {
10799         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10800         *whitePlayer = curRound + appData.tourneyType;
10801     } else if(appData.tourneyType > 0) {
10802         *whitePlayer = curPairing;
10803         *blackPlayer = curRound + appData.tourneyType;
10804     }
10805
10806     // take care of white/black alternation per round.
10807     // For cycles and games this is already taken care of by default, derived from matchGame!
10808     return curRound & 1;
10809 }
10810
10811 int
10812 NextTourneyGame (int nr, int *swapColors)
10813 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10814     char *p, *q;
10815     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10816     FILE *tf;
10817     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10818     tf = fopen(appData.tourneyFile, "r");
10819     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10820     ParseArgsFromFile(tf); fclose(tf);
10821     InitTimeControls(); // TC might be altered from tourney file
10822
10823     nPlayers = CountPlayers(appData.participants); // count participants
10824     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10825     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10826
10827     if(syncInterval) {
10828         p = q = appData.results;
10829         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10830         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10831             DisplayMessage(_("Waiting for other game(s)"),"");
10832             waitingForGame = TRUE;
10833             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10834             return 0;
10835         }
10836         waitingForGame = FALSE;
10837     }
10838
10839     if(appData.tourneyType < 0) {
10840         if(nr>=0 && !pairingReceived) {
10841             char buf[1<<16];
10842             if(pairing.pr == NoProc) {
10843                 if(!appData.pairingEngine[0]) {
10844                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10845                     return 0;
10846                 }
10847                 StartChessProgram(&pairing); // starts the pairing engine
10848             }
10849             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10850             SendToProgram(buf, &pairing);
10851             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10852             SendToProgram(buf, &pairing);
10853             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10854         }
10855         pairingReceived = 0;                              // ... so we continue here
10856         *swapColors = 0;
10857         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10858         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10859         matchGame = 1; roundNr = nr / syncInterval + 1;
10860     }
10861
10862     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10863
10864     // redefine engines, engine dir, etc.
10865     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10866     if(first.pr == NoProc) {
10867       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10868       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10869     }
10870     if(second.pr == NoProc) {
10871       SwapEngines(1);
10872       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10873       SwapEngines(1);         // and make that valid for second engine by swapping
10874       InitEngine(&second, 1);
10875     }
10876     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10877     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10878     return OK;
10879 }
10880
10881 void
10882 NextMatchGame ()
10883 {   // performs game initialization that does not invoke engines, and then tries to start the game
10884     int res, firstWhite, swapColors = 0;
10885     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10886     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
10887         char buf[MSG_SIZ];
10888         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10889         if(strcmp(buf, currentDebugFile)) { // name has changed
10890             FILE *f = fopen(buf, "w");
10891             if(f) { // if opening the new file failed, just keep using the old one
10892                 ASSIGN(currentDebugFile, buf);
10893                 fclose(debugFP);
10894                 debugFP = f;
10895             }
10896             if(appData.serverFileName) {
10897                 if(serverFP) fclose(serverFP);
10898                 serverFP = fopen(appData.serverFileName, "w");
10899                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10900                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10901             }
10902         }
10903     }
10904     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10905     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10906     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10907     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10908     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10909     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10910     Reset(FALSE, first.pr != NoProc);
10911     res = LoadGameOrPosition(matchGame); // setup game
10912     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10913     if(!res) return; // abort when bad game/pos file
10914     TwoMachinesEvent();
10915 }
10916
10917 void
10918 UserAdjudicationEvent (int result)
10919 {
10920     ChessMove gameResult = GameIsDrawn;
10921
10922     if( result > 0 ) {
10923         gameResult = WhiteWins;
10924     }
10925     else if( result < 0 ) {
10926         gameResult = BlackWins;
10927     }
10928
10929     if( gameMode == TwoMachinesPlay ) {
10930         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10931     }
10932 }
10933
10934
10935 // [HGM] save: calculate checksum of game to make games easily identifiable
10936 int
10937 StringCheckSum (char *s)
10938 {
10939         int i = 0;
10940         if(s==NULL) return 0;
10941         while(*s) i = i*259 + *s++;
10942         return i;
10943 }
10944
10945 int
10946 GameCheckSum ()
10947 {
10948         int i, sum=0;
10949         for(i=backwardMostMove; i<forwardMostMove; i++) {
10950                 sum += pvInfoList[i].depth;
10951                 sum += StringCheckSum(parseList[i]);
10952                 sum += StringCheckSum(commentList[i]);
10953                 sum *= 261;
10954         }
10955         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10956         return sum + StringCheckSum(commentList[i]);
10957 } // end of save patch
10958
10959 void
10960 GameEnds (ChessMove result, char *resultDetails, int whosays)
10961 {
10962     GameMode nextGameMode;
10963     int isIcsGame;
10964     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10965
10966     if(endingGame) return; /* [HGM] crash: forbid recursion */
10967     endingGame = 1;
10968     if(twoBoards) { // [HGM] dual: switch back to one board
10969         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10970         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10971     }
10972     if (appData.debugMode) {
10973       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10974               result, resultDetails ? resultDetails : "(null)", whosays);
10975     }
10976
10977     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10978
10979     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10980
10981     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10982         /* If we are playing on ICS, the server decides when the
10983            game is over, but the engine can offer to draw, claim
10984            a draw, or resign.
10985          */
10986 #if ZIPPY
10987         if (appData.zippyPlay && first.initDone) {
10988             if (result == GameIsDrawn) {
10989                 /* In case draw still needs to be claimed */
10990                 SendToICS(ics_prefix);
10991                 SendToICS("draw\n");
10992             } else if (StrCaseStr(resultDetails, "resign")) {
10993                 SendToICS(ics_prefix);
10994                 SendToICS("resign\n");
10995             }
10996         }
10997 #endif
10998         endingGame = 0; /* [HGM] crash */
10999         return;
11000     }
11001
11002     /* If we're loading the game from a file, stop */
11003     if (whosays == GE_FILE) {
11004       (void) StopLoadGameTimer();
11005       gameFileFP = NULL;
11006     }
11007
11008     /* Cancel draw offers */
11009     first.offeredDraw = second.offeredDraw = 0;
11010
11011     /* If this is an ICS game, only ICS can really say it's done;
11012        if not, anyone can. */
11013     isIcsGame = (gameMode == IcsPlayingWhite ||
11014                  gameMode == IcsPlayingBlack ||
11015                  gameMode == IcsObserving    ||
11016                  gameMode == IcsExamining);
11017
11018     if (!isIcsGame || whosays == GE_ICS) {
11019         /* OK -- not an ICS game, or ICS said it was done */
11020         StopClocks();
11021         if (!isIcsGame && !appData.noChessProgram)
11022           SetUserThinkingEnables();
11023
11024         /* [HGM] if a machine claims the game end we verify this claim */
11025         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11026             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11027                 char claimer;
11028                 ChessMove trueResult = (ChessMove) -1;
11029
11030                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11031                                             first.twoMachinesColor[0] :
11032                                             second.twoMachinesColor[0] ;
11033
11034                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11035                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11036                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11037                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11038                 } else
11039                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11040                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11041                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11042                 } else
11043                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11044                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11045                 }
11046
11047                 // now verify win claims, but not in drop games, as we don't understand those yet
11048                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11049                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11050                     (result == WhiteWins && claimer == 'w' ||
11051                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11052                       if (appData.debugMode) {
11053                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11054                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11055                       }
11056                       if(result != trueResult) {
11057                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11058                               result = claimer == 'w' ? BlackWins : WhiteWins;
11059                               resultDetails = buf;
11060                       }
11061                 } else
11062                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11063                     && (forwardMostMove <= backwardMostMove ||
11064                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11065                         (claimer=='b')==(forwardMostMove&1))
11066                                                                                   ) {
11067                       /* [HGM] verify: draws that were not flagged are false claims */
11068                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11069                       result = claimer == 'w' ? BlackWins : WhiteWins;
11070                       resultDetails = buf;
11071                 }
11072                 /* (Claiming a loss is accepted no questions asked!) */
11073             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11074                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11075                 result = GameUnfinished;
11076                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11077             }
11078             /* [HGM] bare: don't allow bare King to win */
11079             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11080                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11081                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11082                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11083                && result != GameIsDrawn)
11084             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11085                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11086                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11087                         if(p >= 0 && p <= (int)WhiteKing) k++;
11088                 }
11089                 if (appData.debugMode) {
11090                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11091                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11092                 }
11093                 if(k <= 1) {
11094                         result = GameIsDrawn;
11095                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11096                         resultDetails = buf;
11097                 }
11098             }
11099         }
11100
11101
11102         if(serverMoves != NULL && !loadFlag) { char c = '=';
11103             if(result==WhiteWins) c = '+';
11104             if(result==BlackWins) c = '-';
11105             if(resultDetails != NULL)
11106                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11107         }
11108         if (resultDetails != NULL) {
11109             gameInfo.result = result;
11110             gameInfo.resultDetails = StrSave(resultDetails);
11111
11112             /* display last move only if game was not loaded from file */
11113             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11114                 DisplayMove(currentMove - 1);
11115
11116             if (forwardMostMove != 0) {
11117                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11118                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11119                                                                 ) {
11120                     if (*appData.saveGameFile != NULLCHAR) {
11121                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11122                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11123                         else
11124                         SaveGameToFile(appData.saveGameFile, TRUE);
11125                     } else if (appData.autoSaveGames) {
11126                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11127                     }
11128                     if (*appData.savePositionFile != NULLCHAR) {
11129                         SavePositionToFile(appData.savePositionFile);
11130                     }
11131                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11132                 }
11133             }
11134
11135             /* Tell program how game ended in case it is learning */
11136             /* [HGM] Moved this to after saving the PGN, just in case */
11137             /* engine died and we got here through time loss. In that */
11138             /* case we will get a fatal error writing the pipe, which */
11139             /* would otherwise lose us the PGN.                       */
11140             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11141             /* output during GameEnds should never be fatal anymore   */
11142             if (gameMode == MachinePlaysWhite ||
11143                 gameMode == MachinePlaysBlack ||
11144                 gameMode == TwoMachinesPlay ||
11145                 gameMode == IcsPlayingWhite ||
11146                 gameMode == IcsPlayingBlack ||
11147                 gameMode == BeginningOfGame) {
11148                 char buf[MSG_SIZ];
11149                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11150                         resultDetails);
11151                 if (first.pr != NoProc) {
11152                     SendToProgram(buf, &first);
11153                 }
11154                 if (second.pr != NoProc &&
11155                     gameMode == TwoMachinesPlay) {
11156                     SendToProgram(buf, &second);
11157                 }
11158             }
11159         }
11160
11161         if (appData.icsActive) {
11162             if (appData.quietPlay &&
11163                 (gameMode == IcsPlayingWhite ||
11164                  gameMode == IcsPlayingBlack)) {
11165                 SendToICS(ics_prefix);
11166                 SendToICS("set shout 1\n");
11167             }
11168             nextGameMode = IcsIdle;
11169             ics_user_moved = FALSE;
11170             /* clean up premove.  It's ugly when the game has ended and the
11171              * premove highlights are still on the board.
11172              */
11173             if (gotPremove) {
11174               gotPremove = FALSE;
11175               ClearPremoveHighlights();
11176               DrawPosition(FALSE, boards[currentMove]);
11177             }
11178             if (whosays == GE_ICS) {
11179                 switch (result) {
11180                 case WhiteWins:
11181                     if (gameMode == IcsPlayingWhite)
11182                         PlayIcsWinSound();
11183                     else if(gameMode == IcsPlayingBlack)
11184                         PlayIcsLossSound();
11185                     break;
11186                 case BlackWins:
11187                     if (gameMode == IcsPlayingBlack)
11188                         PlayIcsWinSound();
11189                     else if(gameMode == IcsPlayingWhite)
11190                         PlayIcsLossSound();
11191                     break;
11192                 case GameIsDrawn:
11193                     PlayIcsDrawSound();
11194                     break;
11195                 default:
11196                     PlayIcsUnfinishedSound();
11197                 }
11198             }
11199             if(appData.quitNext) { ExitEvent(0); return; }
11200         } else if (gameMode == EditGame ||
11201                    gameMode == PlayFromGameFile ||
11202                    gameMode == AnalyzeMode ||
11203                    gameMode == AnalyzeFile) {
11204             nextGameMode = gameMode;
11205         } else {
11206             nextGameMode = EndOfGame;
11207         }
11208         pausing = FALSE;
11209         ModeHighlight();
11210     } else {
11211         nextGameMode = gameMode;
11212     }
11213
11214     if (appData.noChessProgram) {
11215         gameMode = nextGameMode;
11216         ModeHighlight();
11217         endingGame = 0; /* [HGM] crash */
11218         return;
11219     }
11220
11221     if (first.reuse) {
11222         /* Put first chess program into idle state */
11223         if (first.pr != NoProc &&
11224             (gameMode == MachinePlaysWhite ||
11225              gameMode == MachinePlaysBlack ||
11226              gameMode == TwoMachinesPlay ||
11227              gameMode == IcsPlayingWhite ||
11228              gameMode == IcsPlayingBlack ||
11229              gameMode == BeginningOfGame)) {
11230             SendToProgram("force\n", &first);
11231             if (first.usePing) {
11232               char buf[MSG_SIZ];
11233               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11234               SendToProgram(buf, &first);
11235             }
11236         }
11237     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11238         /* Kill off first chess program */
11239         if (first.isr != NULL)
11240           RemoveInputSource(first.isr);
11241         first.isr = NULL;
11242
11243         if (first.pr != NoProc) {
11244             ExitAnalyzeMode();
11245             DoSleep( appData.delayBeforeQuit );
11246             SendToProgram("quit\n", &first);
11247             DoSleep( appData.delayAfterQuit );
11248             DestroyChildProcess(first.pr, first.useSigterm);
11249             first.reload = TRUE;
11250         }
11251         first.pr = NoProc;
11252     }
11253     if (second.reuse) {
11254         /* Put second chess program into idle state */
11255         if (second.pr != NoProc &&
11256             gameMode == TwoMachinesPlay) {
11257             SendToProgram("force\n", &second);
11258             if (second.usePing) {
11259               char buf[MSG_SIZ];
11260               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11261               SendToProgram(buf, &second);
11262             }
11263         }
11264     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11265         /* Kill off second chess program */
11266         if (second.isr != NULL)
11267           RemoveInputSource(second.isr);
11268         second.isr = NULL;
11269
11270         if (second.pr != NoProc) {
11271             DoSleep( appData.delayBeforeQuit );
11272             SendToProgram("quit\n", &second);
11273             DoSleep( appData.delayAfterQuit );
11274             DestroyChildProcess(second.pr, second.useSigterm);
11275             second.reload = TRUE;
11276         }
11277         second.pr = NoProc;
11278     }
11279
11280     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11281         char resChar = '=';
11282         switch (result) {
11283         case WhiteWins:
11284           resChar = '+';
11285           if (first.twoMachinesColor[0] == 'w') {
11286             first.matchWins++;
11287           } else {
11288             second.matchWins++;
11289           }
11290           break;
11291         case BlackWins:
11292           resChar = '-';
11293           if (first.twoMachinesColor[0] == 'b') {
11294             first.matchWins++;
11295           } else {
11296             second.matchWins++;
11297           }
11298           break;
11299         case GameUnfinished:
11300           resChar = ' ';
11301         default:
11302           break;
11303         }
11304
11305         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11306         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11307             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11308             ReserveGame(nextGame, resChar); // sets nextGame
11309             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11310             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11311         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11312
11313         if (nextGame <= appData.matchGames && !abortMatch) {
11314             gameMode = nextGameMode;
11315             matchGame = nextGame; // this will be overruled in tourney mode!
11316             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11317             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11318             endingGame = 0; /* [HGM] crash */
11319             return;
11320         } else {
11321             gameMode = nextGameMode;
11322             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11323                      first.tidy, second.tidy,
11324                      first.matchWins, second.matchWins,
11325                      appData.matchGames - (first.matchWins + second.matchWins));
11326             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11327             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11328             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11329             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11330                 first.twoMachinesColor = "black\n";
11331                 second.twoMachinesColor = "white\n";
11332             } else {
11333                 first.twoMachinesColor = "white\n";
11334                 second.twoMachinesColor = "black\n";
11335             }
11336         }
11337     }
11338     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11339         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11340       ExitAnalyzeMode();
11341     gameMode = nextGameMode;
11342     ModeHighlight();
11343     endingGame = 0;  /* [HGM] crash */
11344     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11345         if(matchMode == TRUE) { // match through command line: exit with or without popup
11346             if(ranking) {
11347                 ToNrEvent(forwardMostMove);
11348                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11349                 else ExitEvent(0);
11350             } else DisplayFatalError(buf, 0, 0);
11351         } else { // match through menu; just stop, with or without popup
11352             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11353             ModeHighlight();
11354             if(ranking){
11355                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11356             } else DisplayNote(buf);
11357       }
11358       if(ranking) free(ranking);
11359     }
11360 }
11361
11362 /* Assumes program was just initialized (initString sent).
11363    Leaves program in force mode. */
11364 void
11365 FeedMovesToProgram (ChessProgramState *cps, int upto)
11366 {
11367     int i;
11368
11369     if (appData.debugMode)
11370       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11371               startedFromSetupPosition ? "position and " : "",
11372               backwardMostMove, upto, cps->which);
11373     if(currentlyInitializedVariant != gameInfo.variant) {
11374       char buf[MSG_SIZ];
11375         // [HGM] variantswitch: make engine aware of new variant
11376         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11377                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11378                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11379         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11380         SendToProgram(buf, cps);
11381         currentlyInitializedVariant = gameInfo.variant;
11382     }
11383     SendToProgram("force\n", cps);
11384     if (startedFromSetupPosition) {
11385         SendBoard(cps, backwardMostMove);
11386     if (appData.debugMode) {
11387         fprintf(debugFP, "feedMoves\n");
11388     }
11389     }
11390     for (i = backwardMostMove; i < upto; i++) {
11391         SendMoveToProgram(i, cps);
11392     }
11393 }
11394
11395
11396 int
11397 ResurrectChessProgram ()
11398 {
11399      /* The chess program may have exited.
11400         If so, restart it and feed it all the moves made so far. */
11401     static int doInit = 0;
11402
11403     if (appData.noChessProgram) return 1;
11404
11405     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11406         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11407         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11408         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11409     } else {
11410         if (first.pr != NoProc) return 1;
11411         StartChessProgram(&first);
11412     }
11413     InitChessProgram(&first, FALSE);
11414     FeedMovesToProgram(&first, currentMove);
11415
11416     if (!first.sendTime) {
11417         /* can't tell gnuchess what its clock should read,
11418            so we bow to its notion. */
11419         ResetClocks();
11420         timeRemaining[0][currentMove] = whiteTimeRemaining;
11421         timeRemaining[1][currentMove] = blackTimeRemaining;
11422     }
11423
11424     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11425                 appData.icsEngineAnalyze) && first.analysisSupport) {
11426       SendToProgram("analyze\n", &first);
11427       first.analyzing = TRUE;
11428     }
11429     return 1;
11430 }
11431
11432 /*
11433  * Button procedures
11434  */
11435 void
11436 Reset (int redraw, int init)
11437 {
11438     int i;
11439
11440     if (appData.debugMode) {
11441         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11442                 redraw, init, gameMode);
11443     }
11444     CleanupTail(); // [HGM] vari: delete any stored variations
11445     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11446     pausing = pauseExamInvalid = FALSE;
11447     startedFromSetupPosition = blackPlaysFirst = FALSE;
11448     firstMove = TRUE;
11449     whiteFlag = blackFlag = FALSE;
11450     userOfferedDraw = FALSE;
11451     hintRequested = bookRequested = FALSE;
11452     first.maybeThinking = FALSE;
11453     second.maybeThinking = FALSE;
11454     first.bookSuspend = FALSE; // [HGM] book
11455     second.bookSuspend = FALSE;
11456     thinkOutput[0] = NULLCHAR;
11457     lastHint[0] = NULLCHAR;
11458     ClearGameInfo(&gameInfo);
11459     gameInfo.variant = StringToVariant(appData.variant);
11460     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11461     ics_user_moved = ics_clock_paused = FALSE;
11462     ics_getting_history = H_FALSE;
11463     ics_gamenum = -1;
11464     white_holding[0] = black_holding[0] = NULLCHAR;
11465     ClearProgramStats();
11466     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11467
11468     ResetFrontEnd();
11469     ClearHighlights();
11470     flipView = appData.flipView;
11471     ClearPremoveHighlights();
11472     gotPremove = FALSE;
11473     alarmSounded = FALSE;
11474     killX = killY = -1; // [HGM] lion
11475
11476     GameEnds(EndOfFile, NULL, GE_PLAYER);
11477     if(appData.serverMovesName != NULL) {
11478         /* [HGM] prepare to make moves file for broadcasting */
11479         clock_t t = clock();
11480         if(serverMoves != NULL) fclose(serverMoves);
11481         serverMoves = fopen(appData.serverMovesName, "r");
11482         if(serverMoves != NULL) {
11483             fclose(serverMoves);
11484             /* delay 15 sec before overwriting, so all clients can see end */
11485             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11486         }
11487         serverMoves = fopen(appData.serverMovesName, "w");
11488     }
11489
11490     ExitAnalyzeMode();
11491     gameMode = BeginningOfGame;
11492     ModeHighlight();
11493     if(appData.icsActive) gameInfo.variant = VariantNormal;
11494     currentMove = forwardMostMove = backwardMostMove = 0;
11495     MarkTargetSquares(1);
11496     InitPosition(redraw);
11497     for (i = 0; i < MAX_MOVES; i++) {
11498         if (commentList[i] != NULL) {
11499             free(commentList[i]);
11500             commentList[i] = NULL;
11501         }
11502     }
11503     ResetClocks();
11504     timeRemaining[0][0] = whiteTimeRemaining;
11505     timeRemaining[1][0] = blackTimeRemaining;
11506
11507     if (first.pr == NoProc) {
11508         StartChessProgram(&first);
11509     }
11510     if (init) {
11511             InitChessProgram(&first, startedFromSetupPosition);
11512     }
11513     DisplayTitle("");
11514     DisplayMessage("", "");
11515     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11516     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11517     ClearMap();        // [HGM] exclude: invalidate map
11518 }
11519
11520 void
11521 AutoPlayGameLoop ()
11522 {
11523     for (;;) {
11524         if (!AutoPlayOneMove())
11525           return;
11526         if (matchMode || appData.timeDelay == 0)
11527           continue;
11528         if (appData.timeDelay < 0)
11529           return;
11530         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11531         break;
11532     }
11533 }
11534
11535 void
11536 AnalyzeNextGame()
11537 {
11538     ReloadGame(1); // next game
11539 }
11540
11541 int
11542 AutoPlayOneMove ()
11543 {
11544     int fromX, fromY, toX, toY;
11545
11546     if (appData.debugMode) {
11547       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11548     }
11549
11550     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11551       return FALSE;
11552
11553     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11554       pvInfoList[currentMove].depth = programStats.depth;
11555       pvInfoList[currentMove].score = programStats.score;
11556       pvInfoList[currentMove].time  = 0;
11557       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11558       else { // append analysis of final position as comment
11559         char buf[MSG_SIZ];
11560         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11561         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11562       }
11563       programStats.depth = 0;
11564     }
11565
11566     if (currentMove >= forwardMostMove) {
11567       if(gameMode == AnalyzeFile) {
11568           if(appData.loadGameIndex == -1) {
11569             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11570           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11571           } else {
11572           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11573         }
11574       }
11575 //      gameMode = EndOfGame;
11576 //      ModeHighlight();
11577
11578       /* [AS] Clear current move marker at the end of a game */
11579       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11580
11581       return FALSE;
11582     }
11583
11584     toX = moveList[currentMove][2] - AAA;
11585     toY = moveList[currentMove][3] - ONE;
11586
11587     if (moveList[currentMove][1] == '@') {
11588         if (appData.highlightLastMove) {
11589             SetHighlights(-1, -1, toX, toY);
11590         }
11591     } else {
11592         fromX = moveList[currentMove][0] - AAA;
11593         fromY = moveList[currentMove][1] - ONE;
11594
11595         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11596
11597         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11598
11599         if (appData.highlightLastMove) {
11600             SetHighlights(fromX, fromY, toX, toY);
11601         }
11602     }
11603     DisplayMove(currentMove);
11604     SendMoveToProgram(currentMove++, &first);
11605     DisplayBothClocks();
11606     DrawPosition(FALSE, boards[currentMove]);
11607     // [HGM] PV info: always display, routine tests if empty
11608     DisplayComment(currentMove - 1, commentList[currentMove]);
11609     return TRUE;
11610 }
11611
11612
11613 int
11614 LoadGameOneMove (ChessMove readAhead)
11615 {
11616     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11617     char promoChar = NULLCHAR;
11618     ChessMove moveType;
11619     char move[MSG_SIZ];
11620     char *p, *q;
11621
11622     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11623         gameMode != AnalyzeMode && gameMode != Training) {
11624         gameFileFP = NULL;
11625         return FALSE;
11626     }
11627
11628     yyboardindex = forwardMostMove;
11629     if (readAhead != EndOfFile) {
11630       moveType = readAhead;
11631     } else {
11632       if (gameFileFP == NULL)
11633           return FALSE;
11634       moveType = (ChessMove) Myylex();
11635     }
11636
11637     done = FALSE;
11638     switch (moveType) {
11639       case Comment:
11640         if (appData.debugMode)
11641           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11642         p = yy_text;
11643
11644         /* append the comment but don't display it */
11645         AppendComment(currentMove, p, FALSE);
11646         return TRUE;
11647
11648       case WhiteCapturesEnPassant:
11649       case BlackCapturesEnPassant:
11650       case WhitePromotion:
11651       case BlackPromotion:
11652       case WhiteNonPromotion:
11653       case BlackNonPromotion:
11654       case NormalMove:
11655       case FirstLeg:
11656       case WhiteKingSideCastle:
11657       case WhiteQueenSideCastle:
11658       case BlackKingSideCastle:
11659       case BlackQueenSideCastle:
11660       case WhiteKingSideCastleWild:
11661       case WhiteQueenSideCastleWild:
11662       case BlackKingSideCastleWild:
11663       case BlackQueenSideCastleWild:
11664       /* PUSH Fabien */
11665       case WhiteHSideCastleFR:
11666       case WhiteASideCastleFR:
11667       case BlackHSideCastleFR:
11668       case BlackASideCastleFR:
11669       /* POP Fabien */
11670         if (appData.debugMode)
11671           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11672         fromX = currentMoveString[0] - AAA;
11673         fromY = currentMoveString[1] - ONE;
11674         toX = currentMoveString[2] - AAA;
11675         toY = currentMoveString[3] - ONE;
11676         promoChar = currentMoveString[4];
11677         if(promoChar == ';') promoChar = NULLCHAR;
11678         break;
11679
11680       case WhiteDrop:
11681       case BlackDrop:
11682         if (appData.debugMode)
11683           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11684         fromX = moveType == WhiteDrop ?
11685           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11686         (int) CharToPiece(ToLower(currentMoveString[0]));
11687         fromY = DROP_RANK;
11688         toX = currentMoveString[2] - AAA;
11689         toY = currentMoveString[3] - ONE;
11690         break;
11691
11692       case WhiteWins:
11693       case BlackWins:
11694       case GameIsDrawn:
11695       case GameUnfinished:
11696         if (appData.debugMode)
11697           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11698         p = strchr(yy_text, '{');
11699         if (p == NULL) p = strchr(yy_text, '(');
11700         if (p == NULL) {
11701             p = yy_text;
11702             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11703         } else {
11704             q = strchr(p, *p == '{' ? '}' : ')');
11705             if (q != NULL) *q = NULLCHAR;
11706             p++;
11707         }
11708         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11709         GameEnds(moveType, p, GE_FILE);
11710         done = TRUE;
11711         if (cmailMsgLoaded) {
11712             ClearHighlights();
11713             flipView = WhiteOnMove(currentMove);
11714             if (moveType == GameUnfinished) flipView = !flipView;
11715             if (appData.debugMode)
11716               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11717         }
11718         break;
11719
11720       case EndOfFile:
11721         if (appData.debugMode)
11722           fprintf(debugFP, "Parser hit end of file\n");
11723         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11724           case MT_NONE:
11725           case MT_CHECK:
11726             break;
11727           case MT_CHECKMATE:
11728           case MT_STAINMATE:
11729             if (WhiteOnMove(currentMove)) {
11730                 GameEnds(BlackWins, "Black mates", GE_FILE);
11731             } else {
11732                 GameEnds(WhiteWins, "White mates", GE_FILE);
11733             }
11734             break;
11735           case MT_STALEMATE:
11736             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11737             break;
11738         }
11739         done = TRUE;
11740         break;
11741
11742       case MoveNumberOne:
11743         if (lastLoadGameStart == GNUChessGame) {
11744             /* GNUChessGames have numbers, but they aren't move numbers */
11745             if (appData.debugMode)
11746               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11747                       yy_text, (int) moveType);
11748             return LoadGameOneMove(EndOfFile); /* tail recursion */
11749         }
11750         /* else fall thru */
11751
11752       case XBoardGame:
11753       case GNUChessGame:
11754       case PGNTag:
11755         /* Reached start of next game in file */
11756         if (appData.debugMode)
11757           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11758         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11759           case MT_NONE:
11760           case MT_CHECK:
11761             break;
11762           case MT_CHECKMATE:
11763           case MT_STAINMATE:
11764             if (WhiteOnMove(currentMove)) {
11765                 GameEnds(BlackWins, "Black mates", GE_FILE);
11766             } else {
11767                 GameEnds(WhiteWins, "White mates", GE_FILE);
11768             }
11769             break;
11770           case MT_STALEMATE:
11771             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11772             break;
11773         }
11774         done = TRUE;
11775         break;
11776
11777       case PositionDiagram:     /* should not happen; ignore */
11778       case ElapsedTime:         /* ignore */
11779       case NAG:                 /* ignore */
11780         if (appData.debugMode)
11781           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11782                   yy_text, (int) moveType);
11783         return LoadGameOneMove(EndOfFile); /* tail recursion */
11784
11785       case IllegalMove:
11786         if (appData.testLegality) {
11787             if (appData.debugMode)
11788               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11789             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11790                     (forwardMostMove / 2) + 1,
11791                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11792             DisplayError(move, 0);
11793             done = TRUE;
11794         } else {
11795             if (appData.debugMode)
11796               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11797                       yy_text, currentMoveString);
11798             fromX = currentMoveString[0] - AAA;
11799             fromY = currentMoveString[1] - ONE;
11800             toX = currentMoveString[2] - AAA;
11801             toY = currentMoveString[3] - ONE;
11802             promoChar = currentMoveString[4];
11803         }
11804         break;
11805
11806       case AmbiguousMove:
11807         if (appData.debugMode)
11808           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11809         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11810                 (forwardMostMove / 2) + 1,
11811                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11812         DisplayError(move, 0);
11813         done = TRUE;
11814         break;
11815
11816       default:
11817       case ImpossibleMove:
11818         if (appData.debugMode)
11819           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11820         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11821                 (forwardMostMove / 2) + 1,
11822                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11823         DisplayError(move, 0);
11824         done = TRUE;
11825         break;
11826     }
11827
11828     if (done) {
11829         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11830             DrawPosition(FALSE, boards[currentMove]);
11831             DisplayBothClocks();
11832             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11833               DisplayComment(currentMove - 1, commentList[currentMove]);
11834         }
11835         (void) StopLoadGameTimer();
11836         gameFileFP = NULL;
11837         cmailOldMove = forwardMostMove;
11838         return FALSE;
11839     } else {
11840         /* currentMoveString is set as a side-effect of yylex */
11841
11842         thinkOutput[0] = NULLCHAR;
11843         MakeMove(fromX, fromY, toX, toY, promoChar);
11844         killX = killY = -1; // [HGM] lion: used up
11845         currentMove = forwardMostMove;
11846         return TRUE;
11847     }
11848 }
11849
11850 /* Load the nth game from the given file */
11851 int
11852 LoadGameFromFile (char *filename, int n, char *title, int useList)
11853 {
11854     FILE *f;
11855     char buf[MSG_SIZ];
11856
11857     if (strcmp(filename, "-") == 0) {
11858         f = stdin;
11859         title = "stdin";
11860     } else {
11861         f = fopen(filename, "rb");
11862         if (f == NULL) {
11863           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11864             DisplayError(buf, errno);
11865             return FALSE;
11866         }
11867     }
11868     if (fseek(f, 0, 0) == -1) {
11869         /* f is not seekable; probably a pipe */
11870         useList = FALSE;
11871     }
11872     if (useList && n == 0) {
11873         int error = GameListBuild(f);
11874         if (error) {
11875             DisplayError(_("Cannot build game list"), error);
11876         } else if (!ListEmpty(&gameList) &&
11877                    ((ListGame *) gameList.tailPred)->number > 1) {
11878             GameListPopUp(f, title);
11879             return TRUE;
11880         }
11881         GameListDestroy();
11882         n = 1;
11883     }
11884     if (n == 0) n = 1;
11885     return LoadGame(f, n, title, FALSE);
11886 }
11887
11888
11889 void
11890 MakeRegisteredMove ()
11891 {
11892     int fromX, fromY, toX, toY;
11893     char promoChar;
11894     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11895         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11896           case CMAIL_MOVE:
11897           case CMAIL_DRAW:
11898             if (appData.debugMode)
11899               fprintf(debugFP, "Restoring %s for game %d\n",
11900                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11901
11902             thinkOutput[0] = NULLCHAR;
11903             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11904             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11905             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11906             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11907             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11908             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11909             MakeMove(fromX, fromY, toX, toY, promoChar);
11910             ShowMove(fromX, fromY, toX, toY);
11911
11912             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11913               case MT_NONE:
11914               case MT_CHECK:
11915                 break;
11916
11917               case MT_CHECKMATE:
11918               case MT_STAINMATE:
11919                 if (WhiteOnMove(currentMove)) {
11920                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11921                 } else {
11922                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11923                 }
11924                 break;
11925
11926               case MT_STALEMATE:
11927                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11928                 break;
11929             }
11930
11931             break;
11932
11933           case CMAIL_RESIGN:
11934             if (WhiteOnMove(currentMove)) {
11935                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11936             } else {
11937                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11938             }
11939             break;
11940
11941           case CMAIL_ACCEPT:
11942             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11943             break;
11944
11945           default:
11946             break;
11947         }
11948     }
11949
11950     return;
11951 }
11952
11953 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11954 int
11955 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11956 {
11957     int retVal;
11958
11959     if (gameNumber > nCmailGames) {
11960         DisplayError(_("No more games in this message"), 0);
11961         return FALSE;
11962     }
11963     if (f == lastLoadGameFP) {
11964         int offset = gameNumber - lastLoadGameNumber;
11965         if (offset == 0) {
11966             cmailMsg[0] = NULLCHAR;
11967             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11968                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11969                 nCmailMovesRegistered--;
11970             }
11971             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11972             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11973                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11974             }
11975         } else {
11976             if (! RegisterMove()) return FALSE;
11977         }
11978     }
11979
11980     retVal = LoadGame(f, gameNumber, title, useList);
11981
11982     /* Make move registered during previous look at this game, if any */
11983     MakeRegisteredMove();
11984
11985     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11986         commentList[currentMove]
11987           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11988         DisplayComment(currentMove - 1, commentList[currentMove]);
11989     }
11990
11991     return retVal;
11992 }
11993
11994 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11995 int
11996 ReloadGame (int offset)
11997 {
11998     int gameNumber = lastLoadGameNumber + offset;
11999     if (lastLoadGameFP == NULL) {
12000         DisplayError(_("No game has been loaded yet"), 0);
12001         return FALSE;
12002     }
12003     if (gameNumber <= 0) {
12004         DisplayError(_("Can't back up any further"), 0);
12005         return FALSE;
12006     }
12007     if (cmailMsgLoaded) {
12008         return CmailLoadGame(lastLoadGameFP, gameNumber,
12009                              lastLoadGameTitle, lastLoadGameUseList);
12010     } else {
12011         return LoadGame(lastLoadGameFP, gameNumber,
12012                         lastLoadGameTitle, lastLoadGameUseList);
12013     }
12014 }
12015
12016 int keys[EmptySquare+1];
12017
12018 int
12019 PositionMatches (Board b1, Board b2)
12020 {
12021     int r, f, sum=0;
12022     switch(appData.searchMode) {
12023         case 1: return CompareWithRights(b1, b2);
12024         case 2:
12025             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12026                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12027             }
12028             return TRUE;
12029         case 3:
12030             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12031               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12032                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12033             }
12034             return sum==0;
12035         case 4:
12036             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12037                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12038             }
12039             return sum==0;
12040     }
12041     return TRUE;
12042 }
12043
12044 #define Q_PROMO  4
12045 #define Q_EP     3
12046 #define Q_BCASTL 2
12047 #define Q_WCASTL 1
12048
12049 int pieceList[256], quickBoard[256];
12050 ChessSquare pieceType[256] = { EmptySquare };
12051 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12052 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12053 int soughtTotal, turn;
12054 Boolean epOK, flipSearch;
12055
12056 typedef struct {
12057     unsigned char piece, to;
12058 } Move;
12059
12060 #define DSIZE (250000)
12061
12062 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12063 Move *moveDatabase = initialSpace;
12064 unsigned int movePtr, dataSize = DSIZE;
12065
12066 int
12067 MakePieceList (Board board, int *counts)
12068 {
12069     int r, f, n=Q_PROMO, total=0;
12070     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12071     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12072         int sq = f + (r<<4);
12073         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12074             quickBoard[sq] = ++n;
12075             pieceList[n] = sq;
12076             pieceType[n] = board[r][f];
12077             counts[board[r][f]]++;
12078             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12079             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12080             total++;
12081         }
12082     }
12083     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12084     return total;
12085 }
12086
12087 void
12088 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12089 {
12090     int sq = fromX + (fromY<<4);
12091     int piece = quickBoard[sq];
12092     quickBoard[sq] = 0;
12093     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12094     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12095         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12096         moveDatabase[movePtr++].piece = Q_WCASTL;
12097         quickBoard[sq] = piece;
12098         piece = quickBoard[from]; quickBoard[from] = 0;
12099         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12100     } else
12101     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12102         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12103         moveDatabase[movePtr++].piece = Q_BCASTL;
12104         quickBoard[sq] = piece;
12105         piece = quickBoard[from]; quickBoard[from] = 0;
12106         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12107     } else
12108     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12109         quickBoard[(fromY<<4)+toX] = 0;
12110         moveDatabase[movePtr].piece = Q_EP;
12111         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12112         moveDatabase[movePtr].to = sq;
12113     } else
12114     if(promoPiece != pieceType[piece]) {
12115         moveDatabase[movePtr++].piece = Q_PROMO;
12116         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12117     }
12118     moveDatabase[movePtr].piece = piece;
12119     quickBoard[sq] = piece;
12120     movePtr++;
12121 }
12122
12123 int
12124 PackGame (Board board)
12125 {
12126     Move *newSpace = NULL;
12127     moveDatabase[movePtr].piece = 0; // terminate previous game
12128     if(movePtr > dataSize) {
12129         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12130         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12131         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12132         if(newSpace) {
12133             int i;
12134             Move *p = moveDatabase, *q = newSpace;
12135             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12136             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12137             moveDatabase = newSpace;
12138         } else { // calloc failed, we must be out of memory. Too bad...
12139             dataSize = 0; // prevent calloc events for all subsequent games
12140             return 0;     // and signal this one isn't cached
12141         }
12142     }
12143     movePtr++;
12144     MakePieceList(board, counts);
12145     return movePtr;
12146 }
12147
12148 int
12149 QuickCompare (Board board, int *minCounts, int *maxCounts)
12150 {   // compare according to search mode
12151     int r, f;
12152     switch(appData.searchMode)
12153     {
12154       case 1: // exact position match
12155         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12156         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12157             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12158         }
12159         break;
12160       case 2: // can have extra material on empty squares
12161         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162             if(board[r][f] == EmptySquare) continue;
12163             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12164         }
12165         break;
12166       case 3: // material with exact Pawn structure
12167         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12168             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12169             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12170         } // fall through to material comparison
12171       case 4: // exact material
12172         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12173         break;
12174       case 6: // material range with given imbalance
12175         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12176         // fall through to range comparison
12177       case 5: // material range
12178         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12179     }
12180     return TRUE;
12181 }
12182
12183 int
12184 QuickScan (Board board, Move *move)
12185 {   // reconstruct game,and compare all positions in it
12186     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12187     do {
12188         int piece = move->piece;
12189         int to = move->to, from = pieceList[piece];
12190         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12191           if(!piece) return -1;
12192           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12193             piece = (++move)->piece;
12194             from = pieceList[piece];
12195             counts[pieceType[piece]]--;
12196             pieceType[piece] = (ChessSquare) move->to;
12197             counts[move->to]++;
12198           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12199             counts[pieceType[quickBoard[to]]]--;
12200             quickBoard[to] = 0; total--;
12201             move++;
12202             continue;
12203           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12204             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12205             from  = pieceList[piece]; // so this must be King
12206             quickBoard[from] = 0;
12207             pieceList[piece] = to;
12208             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12209             quickBoard[from] = 0; // rook
12210             quickBoard[to] = piece;
12211             to = move->to; piece = move->piece;
12212             goto aftercastle;
12213           }
12214         }
12215         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12216         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12217         quickBoard[from] = 0;
12218       aftercastle:
12219         quickBoard[to] = piece;
12220         pieceList[piece] = to;
12221         cnt++; turn ^= 3;
12222         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12223            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12224            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12225                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12226           ) {
12227             static int lastCounts[EmptySquare+1];
12228             int i;
12229             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12230             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12231         } else stretch = 0;
12232         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12233         move++;
12234     } while(1);
12235 }
12236
12237 void
12238 InitSearch ()
12239 {
12240     int r, f;
12241     flipSearch = FALSE;
12242     CopyBoard(soughtBoard, boards[currentMove]);
12243     soughtTotal = MakePieceList(soughtBoard, maxSought);
12244     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12245     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12246     CopyBoard(reverseBoard, boards[currentMove]);
12247     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12248         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12249         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12250         reverseBoard[r][f] = piece;
12251     }
12252     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12253     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12254     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12255                  || (boards[currentMove][CASTLING][2] == NoRights ||
12256                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12257                  && (boards[currentMove][CASTLING][5] == NoRights ||
12258                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12259       ) {
12260         flipSearch = TRUE;
12261         CopyBoard(flipBoard, soughtBoard);
12262         CopyBoard(rotateBoard, reverseBoard);
12263         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12264             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12265             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12266         }
12267     }
12268     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12269     if(appData.searchMode >= 5) {
12270         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12271         MakePieceList(soughtBoard, minSought);
12272         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12273     }
12274     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12275         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12276 }
12277
12278 GameInfo dummyInfo;
12279 static int creatingBook;
12280
12281 int
12282 GameContainsPosition (FILE *f, ListGame *lg)
12283 {
12284     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12285     int fromX, fromY, toX, toY;
12286     char promoChar;
12287     static int initDone=FALSE;
12288
12289     // weed out games based on numerical tag comparison
12290     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12291     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12292     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12293     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12294     if(!initDone) {
12295         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12296         initDone = TRUE;
12297     }
12298     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12299     else CopyBoard(boards[scratch], initialPosition); // default start position
12300     if(lg->moves) {
12301         turn = btm + 1;
12302         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12303         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12304     }
12305     if(btm) plyNr++;
12306     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12307     fseek(f, lg->offset, 0);
12308     yynewfile(f);
12309     while(1) {
12310         yyboardindex = scratch;
12311         quickFlag = plyNr+1;
12312         next = Myylex();
12313         quickFlag = 0;
12314         switch(next) {
12315             case PGNTag:
12316                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12317             default:
12318                 continue;
12319
12320             case XBoardGame:
12321             case GNUChessGame:
12322                 if(plyNr) return -1; // after we have seen moves, this is for new game
12323               continue;
12324
12325             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12326             case ImpossibleMove:
12327             case WhiteWins: // game ends here with these four
12328             case BlackWins:
12329             case GameIsDrawn:
12330             case GameUnfinished:
12331                 return -1;
12332
12333             case IllegalMove:
12334                 if(appData.testLegality) return -1;
12335             case WhiteCapturesEnPassant:
12336             case BlackCapturesEnPassant:
12337             case WhitePromotion:
12338             case BlackPromotion:
12339             case WhiteNonPromotion:
12340             case BlackNonPromotion:
12341             case NormalMove:
12342             case FirstLeg:
12343             case WhiteKingSideCastle:
12344             case WhiteQueenSideCastle:
12345             case BlackKingSideCastle:
12346             case BlackQueenSideCastle:
12347             case WhiteKingSideCastleWild:
12348             case WhiteQueenSideCastleWild:
12349             case BlackKingSideCastleWild:
12350             case BlackQueenSideCastleWild:
12351             case WhiteHSideCastleFR:
12352             case WhiteASideCastleFR:
12353             case BlackHSideCastleFR:
12354             case BlackASideCastleFR:
12355                 fromX = currentMoveString[0] - AAA;
12356                 fromY = currentMoveString[1] - ONE;
12357                 toX = currentMoveString[2] - AAA;
12358                 toY = currentMoveString[3] - ONE;
12359                 promoChar = currentMoveString[4];
12360                 break;
12361             case WhiteDrop:
12362             case BlackDrop:
12363                 fromX = next == WhiteDrop ?
12364                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12365                   (int) CharToPiece(ToLower(currentMoveString[0]));
12366                 fromY = DROP_RANK;
12367                 toX = currentMoveString[2] - AAA;
12368                 toY = currentMoveString[3] - ONE;
12369                 promoChar = 0;
12370                 break;
12371         }
12372         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12373         plyNr++;
12374         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12375         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12376         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12377         if(appData.findMirror) {
12378             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12379             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12380         }
12381     }
12382 }
12383
12384 /* Load the nth game from open file f */
12385 int
12386 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12387 {
12388     ChessMove cm;
12389     char buf[MSG_SIZ];
12390     int gn = gameNumber;
12391     ListGame *lg = NULL;
12392     int numPGNTags = 0;
12393     int err, pos = -1;
12394     GameMode oldGameMode;
12395     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12396
12397     if (appData.debugMode)
12398         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12399
12400     if (gameMode == Training )
12401         SetTrainingModeOff();
12402
12403     oldGameMode = gameMode;
12404     if (gameMode != BeginningOfGame) {
12405       Reset(FALSE, TRUE);
12406     }
12407     killX = killY = -1; // [HGM] lion: in case we did not Reset
12408
12409     gameFileFP = f;
12410     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12411         fclose(lastLoadGameFP);
12412     }
12413
12414     if (useList) {
12415         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12416
12417         if (lg) {
12418             fseek(f, lg->offset, 0);
12419             GameListHighlight(gameNumber);
12420             pos = lg->position;
12421             gn = 1;
12422         }
12423         else {
12424             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12425               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12426             else
12427             DisplayError(_("Game number out of range"), 0);
12428             return FALSE;
12429         }
12430     } else {
12431         GameListDestroy();
12432         if (fseek(f, 0, 0) == -1) {
12433             if (f == lastLoadGameFP ?
12434                 gameNumber == lastLoadGameNumber + 1 :
12435                 gameNumber == 1) {
12436                 gn = 1;
12437             } else {
12438                 DisplayError(_("Can't seek on game file"), 0);
12439                 return FALSE;
12440             }
12441         }
12442     }
12443     lastLoadGameFP = f;
12444     lastLoadGameNumber = gameNumber;
12445     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12446     lastLoadGameUseList = useList;
12447
12448     yynewfile(f);
12449
12450     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12451       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12452                 lg->gameInfo.black);
12453             DisplayTitle(buf);
12454     } else if (*title != NULLCHAR) {
12455         if (gameNumber > 1) {
12456           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12457             DisplayTitle(buf);
12458         } else {
12459             DisplayTitle(title);
12460         }
12461     }
12462
12463     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12464         gameMode = PlayFromGameFile;
12465         ModeHighlight();
12466     }
12467
12468     currentMove = forwardMostMove = backwardMostMove = 0;
12469     CopyBoard(boards[0], initialPosition);
12470     StopClocks();
12471
12472     /*
12473      * Skip the first gn-1 games in the file.
12474      * Also skip over anything that precedes an identifiable
12475      * start of game marker, to avoid being confused by
12476      * garbage at the start of the file.  Currently
12477      * recognized start of game markers are the move number "1",
12478      * the pattern "gnuchess .* game", the pattern
12479      * "^[#;%] [^ ]* game file", and a PGN tag block.
12480      * A game that starts with one of the latter two patterns
12481      * will also have a move number 1, possibly
12482      * following a position diagram.
12483      * 5-4-02: Let's try being more lenient and allowing a game to
12484      * start with an unnumbered move.  Does that break anything?
12485      */
12486     cm = lastLoadGameStart = EndOfFile;
12487     while (gn > 0) {
12488         yyboardindex = forwardMostMove;
12489         cm = (ChessMove) Myylex();
12490         switch (cm) {
12491           case EndOfFile:
12492             if (cmailMsgLoaded) {
12493                 nCmailGames = CMAIL_MAX_GAMES - gn;
12494             } else {
12495                 Reset(TRUE, TRUE);
12496                 DisplayError(_("Game not found in file"), 0);
12497             }
12498             return FALSE;
12499
12500           case GNUChessGame:
12501           case XBoardGame:
12502             gn--;
12503             lastLoadGameStart = cm;
12504             break;
12505
12506           case MoveNumberOne:
12507             switch (lastLoadGameStart) {
12508               case GNUChessGame:
12509               case XBoardGame:
12510               case PGNTag:
12511                 break;
12512               case MoveNumberOne:
12513               case EndOfFile:
12514                 gn--;           /* count this game */
12515                 lastLoadGameStart = cm;
12516                 break;
12517               default:
12518                 /* impossible */
12519                 break;
12520             }
12521             break;
12522
12523           case PGNTag:
12524             switch (lastLoadGameStart) {
12525               case GNUChessGame:
12526               case PGNTag:
12527               case MoveNumberOne:
12528               case EndOfFile:
12529                 gn--;           /* count this game */
12530                 lastLoadGameStart = cm;
12531                 break;
12532               case XBoardGame:
12533                 lastLoadGameStart = cm; /* game counted already */
12534                 break;
12535               default:
12536                 /* impossible */
12537                 break;
12538             }
12539             if (gn > 0) {
12540                 do {
12541                     yyboardindex = forwardMostMove;
12542                     cm = (ChessMove) Myylex();
12543                 } while (cm == PGNTag || cm == Comment);
12544             }
12545             break;
12546
12547           case WhiteWins:
12548           case BlackWins:
12549           case GameIsDrawn:
12550             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12551                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12552                     != CMAIL_OLD_RESULT) {
12553                     nCmailResults ++ ;
12554                     cmailResult[  CMAIL_MAX_GAMES
12555                                 - gn - 1] = CMAIL_OLD_RESULT;
12556                 }
12557             }
12558             break;
12559
12560           case NormalMove:
12561           case FirstLeg:
12562             /* Only a NormalMove can be at the start of a game
12563              * without a position diagram. */
12564             if (lastLoadGameStart == EndOfFile ) {
12565               gn--;
12566               lastLoadGameStart = MoveNumberOne;
12567             }
12568             break;
12569
12570           default:
12571             break;
12572         }
12573     }
12574
12575     if (appData.debugMode)
12576       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12577
12578     if (cm == XBoardGame) {
12579         /* Skip any header junk before position diagram and/or move 1 */
12580         for (;;) {
12581             yyboardindex = forwardMostMove;
12582             cm = (ChessMove) Myylex();
12583
12584             if (cm == EndOfFile ||
12585                 cm == GNUChessGame || cm == XBoardGame) {
12586                 /* Empty game; pretend end-of-file and handle later */
12587                 cm = EndOfFile;
12588                 break;
12589             }
12590
12591             if (cm == MoveNumberOne || cm == PositionDiagram ||
12592                 cm == PGNTag || cm == Comment)
12593               break;
12594         }
12595     } else if (cm == GNUChessGame) {
12596         if (gameInfo.event != NULL) {
12597             free(gameInfo.event);
12598         }
12599         gameInfo.event = StrSave(yy_text);
12600     }
12601
12602     startedFromSetupPosition = FALSE;
12603     while (cm == PGNTag) {
12604         if (appData.debugMode)
12605           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12606         err = ParsePGNTag(yy_text, &gameInfo);
12607         if (!err) numPGNTags++;
12608
12609         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12610         if(gameInfo.variant != oldVariant) {
12611             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12612             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12613             InitPosition(TRUE);
12614             oldVariant = gameInfo.variant;
12615             if (appData.debugMode)
12616               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12617         }
12618
12619
12620         if (gameInfo.fen != NULL) {
12621           Board initial_position;
12622           startedFromSetupPosition = TRUE;
12623           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12624             Reset(TRUE, TRUE);
12625             DisplayError(_("Bad FEN position in file"), 0);
12626             return FALSE;
12627           }
12628           CopyBoard(boards[0], initial_position);
12629           if (blackPlaysFirst) {
12630             currentMove = forwardMostMove = backwardMostMove = 1;
12631             CopyBoard(boards[1], initial_position);
12632             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12633             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12634             timeRemaining[0][1] = whiteTimeRemaining;
12635             timeRemaining[1][1] = blackTimeRemaining;
12636             if (commentList[0] != NULL) {
12637               commentList[1] = commentList[0];
12638               commentList[0] = NULL;
12639             }
12640           } else {
12641             currentMove = forwardMostMove = backwardMostMove = 0;
12642           }
12643           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12644           {   int i;
12645               initialRulePlies = FENrulePlies;
12646               for( i=0; i< nrCastlingRights; i++ )
12647                   initialRights[i] = initial_position[CASTLING][i];
12648           }
12649           yyboardindex = forwardMostMove;
12650           free(gameInfo.fen);
12651           gameInfo.fen = NULL;
12652         }
12653
12654         yyboardindex = forwardMostMove;
12655         cm = (ChessMove) Myylex();
12656
12657         /* Handle comments interspersed among the tags */
12658         while (cm == Comment) {
12659             char *p;
12660             if (appData.debugMode)
12661               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12662             p = yy_text;
12663             AppendComment(currentMove, p, FALSE);
12664             yyboardindex = forwardMostMove;
12665             cm = (ChessMove) Myylex();
12666         }
12667     }
12668
12669     /* don't rely on existence of Event tag since if game was
12670      * pasted from clipboard the Event tag may not exist
12671      */
12672     if (numPGNTags > 0){
12673         char *tags;
12674         if (gameInfo.variant == VariantNormal) {
12675           VariantClass v = StringToVariant(gameInfo.event);
12676           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12677           if(v < VariantShogi) gameInfo.variant = v;
12678         }
12679         if (!matchMode) {
12680           if( appData.autoDisplayTags ) {
12681             tags = PGNTags(&gameInfo);
12682             TagsPopUp(tags, CmailMsg());
12683             free(tags);
12684           }
12685         }
12686     } else {
12687         /* Make something up, but don't display it now */
12688         SetGameInfo();
12689         TagsPopDown();
12690     }
12691
12692     if (cm == PositionDiagram) {
12693         int i, j;
12694         char *p;
12695         Board initial_position;
12696
12697         if (appData.debugMode)
12698           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12699
12700         if (!startedFromSetupPosition) {
12701             p = yy_text;
12702             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12703               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12704                 switch (*p) {
12705                   case '{':
12706                   case '[':
12707                   case '-':
12708                   case ' ':
12709                   case '\t':
12710                   case '\n':
12711                   case '\r':
12712                     break;
12713                   default:
12714                     initial_position[i][j++] = CharToPiece(*p);
12715                     break;
12716                 }
12717             while (*p == ' ' || *p == '\t' ||
12718                    *p == '\n' || *p == '\r') p++;
12719
12720             if (strncmp(p, "black", strlen("black"))==0)
12721               blackPlaysFirst = TRUE;
12722             else
12723               blackPlaysFirst = FALSE;
12724             startedFromSetupPosition = TRUE;
12725
12726             CopyBoard(boards[0], initial_position);
12727             if (blackPlaysFirst) {
12728                 currentMove = forwardMostMove = backwardMostMove = 1;
12729                 CopyBoard(boards[1], initial_position);
12730                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12731                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12732                 timeRemaining[0][1] = whiteTimeRemaining;
12733                 timeRemaining[1][1] = blackTimeRemaining;
12734                 if (commentList[0] != NULL) {
12735                     commentList[1] = commentList[0];
12736                     commentList[0] = NULL;
12737                 }
12738             } else {
12739                 currentMove = forwardMostMove = backwardMostMove = 0;
12740             }
12741         }
12742         yyboardindex = forwardMostMove;
12743         cm = (ChessMove) Myylex();
12744     }
12745
12746   if(!creatingBook) {
12747     if (first.pr == NoProc) {
12748         StartChessProgram(&first);
12749     }
12750     InitChessProgram(&first, FALSE);
12751     SendToProgram("force\n", &first);
12752     if (startedFromSetupPosition) {
12753         SendBoard(&first, forwardMostMove);
12754     if (appData.debugMode) {
12755         fprintf(debugFP, "Load Game\n");
12756     }
12757         DisplayBothClocks();
12758     }
12759   }
12760
12761     /* [HGM] server: flag to write setup moves in broadcast file as one */
12762     loadFlag = appData.suppressLoadMoves;
12763
12764     while (cm == Comment) {
12765         char *p;
12766         if (appData.debugMode)
12767           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12768         p = yy_text;
12769         AppendComment(currentMove, p, FALSE);
12770         yyboardindex = forwardMostMove;
12771         cm = (ChessMove) Myylex();
12772     }
12773
12774     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12775         cm == WhiteWins || cm == BlackWins ||
12776         cm == GameIsDrawn || cm == GameUnfinished) {
12777         DisplayMessage("", _("No moves in game"));
12778         if (cmailMsgLoaded) {
12779             if (appData.debugMode)
12780               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12781             ClearHighlights();
12782             flipView = FALSE;
12783         }
12784         DrawPosition(FALSE, boards[currentMove]);
12785         DisplayBothClocks();
12786         gameMode = EditGame;
12787         ModeHighlight();
12788         gameFileFP = NULL;
12789         cmailOldMove = 0;
12790         return TRUE;
12791     }
12792
12793     // [HGM] PV info: routine tests if comment empty
12794     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12795         DisplayComment(currentMove - 1, commentList[currentMove]);
12796     }
12797     if (!matchMode && appData.timeDelay != 0)
12798       DrawPosition(FALSE, boards[currentMove]);
12799
12800     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12801       programStats.ok_to_send = 1;
12802     }
12803
12804     /* if the first token after the PGN tags is a move
12805      * and not move number 1, retrieve it from the parser
12806      */
12807     if (cm != MoveNumberOne)
12808         LoadGameOneMove(cm);
12809
12810     /* load the remaining moves from the file */
12811     while (LoadGameOneMove(EndOfFile)) {
12812       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12813       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12814     }
12815
12816     /* rewind to the start of the game */
12817     currentMove = backwardMostMove;
12818
12819     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12820
12821     if (oldGameMode == AnalyzeFile) {
12822       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12823       AnalyzeFileEvent();
12824     } else
12825     if (oldGameMode == AnalyzeMode) {
12826       AnalyzeFileEvent();
12827     }
12828
12829     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12830         long int w, b; // [HGM] adjourn: restore saved clock times
12831         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12832         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12833             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12834             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12835         }
12836     }
12837
12838     if(creatingBook) return TRUE;
12839     if (!matchMode && pos > 0) {
12840         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12841     } else
12842     if (matchMode || appData.timeDelay == 0) {
12843       ToEndEvent();
12844     } else if (appData.timeDelay > 0) {
12845       AutoPlayGameLoop();
12846     }
12847
12848     if (appData.debugMode)
12849         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12850
12851     loadFlag = 0; /* [HGM] true game starts */
12852     return TRUE;
12853 }
12854
12855 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12856 int
12857 ReloadPosition (int offset)
12858 {
12859     int positionNumber = lastLoadPositionNumber + offset;
12860     if (lastLoadPositionFP == NULL) {
12861         DisplayError(_("No position has been loaded yet"), 0);
12862         return FALSE;
12863     }
12864     if (positionNumber <= 0) {
12865         DisplayError(_("Can't back up any further"), 0);
12866         return FALSE;
12867     }
12868     return LoadPosition(lastLoadPositionFP, positionNumber,
12869                         lastLoadPositionTitle);
12870 }
12871
12872 /* Load the nth position from the given file */
12873 int
12874 LoadPositionFromFile (char *filename, int n, char *title)
12875 {
12876     FILE *f;
12877     char buf[MSG_SIZ];
12878
12879     if (strcmp(filename, "-") == 0) {
12880         return LoadPosition(stdin, n, "stdin");
12881     } else {
12882         f = fopen(filename, "rb");
12883         if (f == NULL) {
12884             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12885             DisplayError(buf, errno);
12886             return FALSE;
12887         } else {
12888             return LoadPosition(f, n, title);
12889         }
12890     }
12891 }
12892
12893 /* Load the nth position from the given open file, and close it */
12894 int
12895 LoadPosition (FILE *f, int positionNumber, char *title)
12896 {
12897     char *p, line[MSG_SIZ];
12898     Board initial_position;
12899     int i, j, fenMode, pn;
12900
12901     if (gameMode == Training )
12902         SetTrainingModeOff();
12903
12904     if (gameMode != BeginningOfGame) {
12905         Reset(FALSE, TRUE);
12906     }
12907     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12908         fclose(lastLoadPositionFP);
12909     }
12910     if (positionNumber == 0) positionNumber = 1;
12911     lastLoadPositionFP = f;
12912     lastLoadPositionNumber = positionNumber;
12913     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12914     if (first.pr == NoProc && !appData.noChessProgram) {
12915       StartChessProgram(&first);
12916       InitChessProgram(&first, FALSE);
12917     }
12918     pn = positionNumber;
12919     if (positionNumber < 0) {
12920         /* Negative position number means to seek to that byte offset */
12921         if (fseek(f, -positionNumber, 0) == -1) {
12922             DisplayError(_("Can't seek on position file"), 0);
12923             return FALSE;
12924         };
12925         pn = 1;
12926     } else {
12927         if (fseek(f, 0, 0) == -1) {
12928             if (f == lastLoadPositionFP ?
12929                 positionNumber == lastLoadPositionNumber + 1 :
12930                 positionNumber == 1) {
12931                 pn = 1;
12932             } else {
12933                 DisplayError(_("Can't seek on position file"), 0);
12934                 return FALSE;
12935             }
12936         }
12937     }
12938     /* See if this file is FEN or old-style xboard */
12939     if (fgets(line, MSG_SIZ, f) == NULL) {
12940         DisplayError(_("Position not found in file"), 0);
12941         return FALSE;
12942     }
12943     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12944     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12945
12946     if (pn >= 2) {
12947         if (fenMode || line[0] == '#') pn--;
12948         while (pn > 0) {
12949             /* skip positions before number pn */
12950             if (fgets(line, MSG_SIZ, f) == NULL) {
12951                 Reset(TRUE, TRUE);
12952                 DisplayError(_("Position not found in file"), 0);
12953                 return FALSE;
12954             }
12955             if (fenMode || line[0] == '#') pn--;
12956         }
12957     }
12958
12959     if (fenMode) {
12960         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12961             DisplayError(_("Bad FEN position in file"), 0);
12962             return FALSE;
12963         }
12964     } else {
12965         (void) fgets(line, MSG_SIZ, f);
12966         (void) fgets(line, MSG_SIZ, f);
12967
12968         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12969             (void) fgets(line, MSG_SIZ, f);
12970             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12971                 if (*p == ' ')
12972                   continue;
12973                 initial_position[i][j++] = CharToPiece(*p);
12974             }
12975         }
12976
12977         blackPlaysFirst = FALSE;
12978         if (!feof(f)) {
12979             (void) fgets(line, MSG_SIZ, f);
12980             if (strncmp(line, "black", strlen("black"))==0)
12981               blackPlaysFirst = TRUE;
12982         }
12983     }
12984     startedFromSetupPosition = TRUE;
12985
12986     CopyBoard(boards[0], initial_position);
12987     if (blackPlaysFirst) {
12988         currentMove = forwardMostMove = backwardMostMove = 1;
12989         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12990         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12991         CopyBoard(boards[1], initial_position);
12992         DisplayMessage("", _("Black to play"));
12993     } else {
12994         currentMove = forwardMostMove = backwardMostMove = 0;
12995         DisplayMessage("", _("White to play"));
12996     }
12997     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12998     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12999         SendToProgram("force\n", &first);
13000         SendBoard(&first, forwardMostMove);
13001     }
13002     if (appData.debugMode) {
13003 int i, j;
13004   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13005   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13006         fprintf(debugFP, "Load Position\n");
13007     }
13008
13009     if (positionNumber > 1) {
13010       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13011         DisplayTitle(line);
13012     } else {
13013         DisplayTitle(title);
13014     }
13015     gameMode = EditGame;
13016     ModeHighlight();
13017     ResetClocks();
13018     timeRemaining[0][1] = whiteTimeRemaining;
13019     timeRemaining[1][1] = blackTimeRemaining;
13020     DrawPosition(FALSE, boards[currentMove]);
13021
13022     return TRUE;
13023 }
13024
13025
13026 void
13027 CopyPlayerNameIntoFileName (char **dest, char *src)
13028 {
13029     while (*src != NULLCHAR && *src != ',') {
13030         if (*src == ' ') {
13031             *(*dest)++ = '_';
13032             src++;
13033         } else {
13034             *(*dest)++ = *src++;
13035         }
13036     }
13037 }
13038
13039 char *
13040 DefaultFileName (char *ext)
13041 {
13042     static char def[MSG_SIZ];
13043     char *p;
13044
13045     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13046         p = def;
13047         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13048         *p++ = '-';
13049         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13050         *p++ = '.';
13051         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13052     } else {
13053         def[0] = NULLCHAR;
13054     }
13055     return def;
13056 }
13057
13058 /* Save the current game to the given file */
13059 int
13060 SaveGameToFile (char *filename, int append)
13061 {
13062     FILE *f;
13063     char buf[MSG_SIZ];
13064     int result, i, t,tot=0;
13065
13066     if (strcmp(filename, "-") == 0) {
13067         return SaveGame(stdout, 0, NULL);
13068     } else {
13069         for(i=0; i<10; i++) { // upto 10 tries
13070              f = fopen(filename, append ? "a" : "w");
13071              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13072              if(f || errno != 13) break;
13073              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13074              tot += t;
13075         }
13076         if (f == NULL) {
13077             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13078             DisplayError(buf, errno);
13079             return FALSE;
13080         } else {
13081             safeStrCpy(buf, lastMsg, MSG_SIZ);
13082             DisplayMessage(_("Waiting for access to save file"), "");
13083             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13084             DisplayMessage(_("Saving game"), "");
13085             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13086             result = SaveGame(f, 0, NULL);
13087             DisplayMessage(buf, "");
13088             return result;
13089         }
13090     }
13091 }
13092
13093 char *
13094 SavePart (char *str)
13095 {
13096     static char buf[MSG_SIZ];
13097     char *p;
13098
13099     p = strchr(str, ' ');
13100     if (p == NULL) return str;
13101     strncpy(buf, str, p - str);
13102     buf[p - str] = NULLCHAR;
13103     return buf;
13104 }
13105
13106 #define PGN_MAX_LINE 75
13107
13108 #define PGN_SIDE_WHITE  0
13109 #define PGN_SIDE_BLACK  1
13110
13111 static int
13112 FindFirstMoveOutOfBook (int side)
13113 {
13114     int result = -1;
13115
13116     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13117         int index = backwardMostMove;
13118         int has_book_hit = 0;
13119
13120         if( (index % 2) != side ) {
13121             index++;
13122         }
13123
13124         while( index < forwardMostMove ) {
13125             /* Check to see if engine is in book */
13126             int depth = pvInfoList[index].depth;
13127             int score = pvInfoList[index].score;
13128             int in_book = 0;
13129
13130             if( depth <= 2 ) {
13131                 in_book = 1;
13132             }
13133             else if( score == 0 && depth == 63 ) {
13134                 in_book = 1; /* Zappa */
13135             }
13136             else if( score == 2 && depth == 99 ) {
13137                 in_book = 1; /* Abrok */
13138             }
13139
13140             has_book_hit += in_book;
13141
13142             if( ! in_book ) {
13143                 result = index;
13144
13145                 break;
13146             }
13147
13148             index += 2;
13149         }
13150     }
13151
13152     return result;
13153 }
13154
13155 void
13156 GetOutOfBookInfo (char * buf)
13157 {
13158     int oob[2];
13159     int i;
13160     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13161
13162     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13163     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13164
13165     *buf = '\0';
13166
13167     if( oob[0] >= 0 || oob[1] >= 0 ) {
13168         for( i=0; i<2; i++ ) {
13169             int idx = oob[i];
13170
13171             if( idx >= 0 ) {
13172                 if( i > 0 && oob[0] >= 0 ) {
13173                     strcat( buf, "   " );
13174                 }
13175
13176                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13177                 sprintf( buf+strlen(buf), "%s%.2f",
13178                     pvInfoList[idx].score >= 0 ? "+" : "",
13179                     pvInfoList[idx].score / 100.0 );
13180             }
13181         }
13182     }
13183 }
13184
13185 /* Save game in PGN style and close the file */
13186 int
13187 SaveGamePGN (FILE *f)
13188 {
13189     int i, offset, linelen, newblock;
13190 //    char *movetext;
13191     char numtext[32];
13192     int movelen, numlen, blank;
13193     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13194
13195     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13196
13197     PrintPGNTags(f, &gameInfo);
13198
13199     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13200
13201     if (backwardMostMove > 0 || startedFromSetupPosition) {
13202         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13203         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13204         fprintf(f, "\n{--------------\n");
13205         PrintPosition(f, backwardMostMove);
13206         fprintf(f, "--------------}\n");
13207         free(fen);
13208     }
13209     else {
13210         /* [AS] Out of book annotation */
13211         if( appData.saveOutOfBookInfo ) {
13212             char buf[64];
13213
13214             GetOutOfBookInfo( buf );
13215
13216             if( buf[0] != '\0' ) {
13217                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13218             }
13219         }
13220
13221         fprintf(f, "\n");
13222     }
13223
13224     i = backwardMostMove;
13225     linelen = 0;
13226     newblock = TRUE;
13227
13228     while (i < forwardMostMove) {
13229         /* Print comments preceding this move */
13230         if (commentList[i] != NULL) {
13231             if (linelen > 0) fprintf(f, "\n");
13232             fprintf(f, "%s", commentList[i]);
13233             linelen = 0;
13234             newblock = TRUE;
13235         }
13236
13237         /* Format move number */
13238         if ((i % 2) == 0)
13239           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13240         else
13241           if (newblock)
13242             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13243           else
13244             numtext[0] = NULLCHAR;
13245
13246         numlen = strlen(numtext);
13247         newblock = FALSE;
13248
13249         /* Print move number */
13250         blank = linelen > 0 && numlen > 0;
13251         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13252             fprintf(f, "\n");
13253             linelen = 0;
13254             blank = 0;
13255         }
13256         if (blank) {
13257             fprintf(f, " ");
13258             linelen++;
13259         }
13260         fprintf(f, "%s", numtext);
13261         linelen += numlen;
13262
13263         /* Get move */
13264         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13265         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13266
13267         /* Print move */
13268         blank = linelen > 0 && movelen > 0;
13269         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13270             fprintf(f, "\n");
13271             linelen = 0;
13272             blank = 0;
13273         }
13274         if (blank) {
13275             fprintf(f, " ");
13276             linelen++;
13277         }
13278         fprintf(f, "%s", move_buffer);
13279         linelen += movelen;
13280
13281         /* [AS] Add PV info if present */
13282         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13283             /* [HGM] add time */
13284             char buf[MSG_SIZ]; int seconds;
13285
13286             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13287
13288             if( seconds <= 0)
13289               buf[0] = 0;
13290             else
13291               if( seconds < 30 )
13292                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13293               else
13294                 {
13295                   seconds = (seconds + 4)/10; // round to full seconds
13296                   if( seconds < 60 )
13297                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13298                   else
13299                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13300                 }
13301
13302             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13303                       pvInfoList[i].score >= 0 ? "+" : "",
13304                       pvInfoList[i].score / 100.0,
13305                       pvInfoList[i].depth,
13306                       buf );
13307
13308             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13309
13310             /* Print score/depth */
13311             blank = linelen > 0 && movelen > 0;
13312             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13313                 fprintf(f, "\n");
13314                 linelen = 0;
13315                 blank = 0;
13316             }
13317             if (blank) {
13318                 fprintf(f, " ");
13319                 linelen++;
13320             }
13321             fprintf(f, "%s", move_buffer);
13322             linelen += movelen;
13323         }
13324
13325         i++;
13326     }
13327
13328     /* Start a new line */
13329     if (linelen > 0) fprintf(f, "\n");
13330
13331     /* Print comments after last move */
13332     if (commentList[i] != NULL) {
13333         fprintf(f, "%s\n", commentList[i]);
13334     }
13335
13336     /* Print result */
13337     if (gameInfo.resultDetails != NULL &&
13338         gameInfo.resultDetails[0] != NULLCHAR) {
13339         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13340         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13341            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13342             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13343         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13344     } else {
13345         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13346     }
13347
13348     fclose(f);
13349     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13350     return TRUE;
13351 }
13352
13353 /* Save game in old style and close the file */
13354 int
13355 SaveGameOldStyle (FILE *f)
13356 {
13357     int i, offset;
13358     time_t tm;
13359
13360     tm = time((time_t *) NULL);
13361
13362     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13363     PrintOpponents(f);
13364
13365     if (backwardMostMove > 0 || startedFromSetupPosition) {
13366         fprintf(f, "\n[--------------\n");
13367         PrintPosition(f, backwardMostMove);
13368         fprintf(f, "--------------]\n");
13369     } else {
13370         fprintf(f, "\n");
13371     }
13372
13373     i = backwardMostMove;
13374     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13375
13376     while (i < forwardMostMove) {
13377         if (commentList[i] != NULL) {
13378             fprintf(f, "[%s]\n", commentList[i]);
13379         }
13380
13381         if ((i % 2) == 1) {
13382             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13383             i++;
13384         } else {
13385             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13386             i++;
13387             if (commentList[i] != NULL) {
13388                 fprintf(f, "\n");
13389                 continue;
13390             }
13391             if (i >= forwardMostMove) {
13392                 fprintf(f, "\n");
13393                 break;
13394             }
13395             fprintf(f, "%s\n", parseList[i]);
13396             i++;
13397         }
13398     }
13399
13400     if (commentList[i] != NULL) {
13401         fprintf(f, "[%s]\n", commentList[i]);
13402     }
13403
13404     /* This isn't really the old style, but it's close enough */
13405     if (gameInfo.resultDetails != NULL &&
13406         gameInfo.resultDetails[0] != NULLCHAR) {
13407         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13408                 gameInfo.resultDetails);
13409     } else {
13410         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13411     }
13412
13413     fclose(f);
13414     return TRUE;
13415 }
13416
13417 /* Save the current game to open file f and close the file */
13418 int
13419 SaveGame (FILE *f, int dummy, char *dummy2)
13420 {
13421     if (gameMode == EditPosition) EditPositionDone(TRUE);
13422     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13423     if (appData.oldSaveStyle)
13424       return SaveGameOldStyle(f);
13425     else
13426       return SaveGamePGN(f);
13427 }
13428
13429 /* Save the current position to the given file */
13430 int
13431 SavePositionToFile (char *filename)
13432 {
13433     FILE *f;
13434     char buf[MSG_SIZ];
13435
13436     if (strcmp(filename, "-") == 0) {
13437         return SavePosition(stdout, 0, NULL);
13438     } else {
13439         f = fopen(filename, "a");
13440         if (f == NULL) {
13441             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13442             DisplayError(buf, errno);
13443             return FALSE;
13444         } else {
13445             safeStrCpy(buf, lastMsg, MSG_SIZ);
13446             DisplayMessage(_("Waiting for access to save file"), "");
13447             flock(fileno(f), LOCK_EX); // [HGM] lock
13448             DisplayMessage(_("Saving position"), "");
13449             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13450             SavePosition(f, 0, NULL);
13451             DisplayMessage(buf, "");
13452             return TRUE;
13453         }
13454     }
13455 }
13456
13457 /* Save the current position to the given open file and close the file */
13458 int
13459 SavePosition (FILE *f, int dummy, char *dummy2)
13460 {
13461     time_t tm;
13462     char *fen;
13463
13464     if (gameMode == EditPosition) EditPositionDone(TRUE);
13465     if (appData.oldSaveStyle) {
13466         tm = time((time_t *) NULL);
13467
13468         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13469         PrintOpponents(f);
13470         fprintf(f, "[--------------\n");
13471         PrintPosition(f, currentMove);
13472         fprintf(f, "--------------]\n");
13473     } else {
13474         fen = PositionToFEN(currentMove, NULL, 1);
13475         fprintf(f, "%s\n", fen);
13476         free(fen);
13477     }
13478     fclose(f);
13479     return TRUE;
13480 }
13481
13482 void
13483 ReloadCmailMsgEvent (int unregister)
13484 {
13485 #if !WIN32
13486     static char *inFilename = NULL;
13487     static char *outFilename;
13488     int i;
13489     struct stat inbuf, outbuf;
13490     int status;
13491
13492     /* Any registered moves are unregistered if unregister is set, */
13493     /* i.e. invoked by the signal handler */
13494     if (unregister) {
13495         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13496             cmailMoveRegistered[i] = FALSE;
13497             if (cmailCommentList[i] != NULL) {
13498                 free(cmailCommentList[i]);
13499                 cmailCommentList[i] = NULL;
13500             }
13501         }
13502         nCmailMovesRegistered = 0;
13503     }
13504
13505     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13506         cmailResult[i] = CMAIL_NOT_RESULT;
13507     }
13508     nCmailResults = 0;
13509
13510     if (inFilename == NULL) {
13511         /* Because the filenames are static they only get malloced once  */
13512         /* and they never get freed                                      */
13513         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13514         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13515
13516         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13517         sprintf(outFilename, "%s.out", appData.cmailGameName);
13518     }
13519
13520     status = stat(outFilename, &outbuf);
13521     if (status < 0) {
13522         cmailMailedMove = FALSE;
13523     } else {
13524         status = stat(inFilename, &inbuf);
13525         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13526     }
13527
13528     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13529        counts the games, notes how each one terminated, etc.
13530
13531        It would be nice to remove this kludge and instead gather all
13532        the information while building the game list.  (And to keep it
13533        in the game list nodes instead of having a bunch of fixed-size
13534        parallel arrays.)  Note this will require getting each game's
13535        termination from the PGN tags, as the game list builder does
13536        not process the game moves.  --mann
13537        */
13538     cmailMsgLoaded = TRUE;
13539     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13540
13541     /* Load first game in the file or popup game menu */
13542     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13543
13544 #endif /* !WIN32 */
13545     return;
13546 }
13547
13548 int
13549 RegisterMove ()
13550 {
13551     FILE *f;
13552     char string[MSG_SIZ];
13553
13554     if (   cmailMailedMove
13555         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13556         return TRUE;            /* Allow free viewing  */
13557     }
13558
13559     /* Unregister move to ensure that we don't leave RegisterMove        */
13560     /* with the move registered when the conditions for registering no   */
13561     /* longer hold                                                       */
13562     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13563         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13564         nCmailMovesRegistered --;
13565
13566         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13567           {
13568               free(cmailCommentList[lastLoadGameNumber - 1]);
13569               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13570           }
13571     }
13572
13573     if (cmailOldMove == -1) {
13574         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13575         return FALSE;
13576     }
13577
13578     if (currentMove > cmailOldMove + 1) {
13579         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13580         return FALSE;
13581     }
13582
13583     if (currentMove < cmailOldMove) {
13584         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13585         return FALSE;
13586     }
13587
13588     if (forwardMostMove > currentMove) {
13589         /* Silently truncate extra moves */
13590         TruncateGame();
13591     }
13592
13593     if (   (currentMove == cmailOldMove + 1)
13594         || (   (currentMove == cmailOldMove)
13595             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13596                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13597         if (gameInfo.result != GameUnfinished) {
13598             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13599         }
13600
13601         if (commentList[currentMove] != NULL) {
13602             cmailCommentList[lastLoadGameNumber - 1]
13603               = StrSave(commentList[currentMove]);
13604         }
13605         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13606
13607         if (appData.debugMode)
13608           fprintf(debugFP, "Saving %s for game %d\n",
13609                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13610
13611         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13612
13613         f = fopen(string, "w");
13614         if (appData.oldSaveStyle) {
13615             SaveGameOldStyle(f); /* also closes the file */
13616
13617             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13618             f = fopen(string, "w");
13619             SavePosition(f, 0, NULL); /* also closes the file */
13620         } else {
13621             fprintf(f, "{--------------\n");
13622             PrintPosition(f, currentMove);
13623             fprintf(f, "--------------}\n\n");
13624
13625             SaveGame(f, 0, NULL); /* also closes the file*/
13626         }
13627
13628         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13629         nCmailMovesRegistered ++;
13630     } else if (nCmailGames == 1) {
13631         DisplayError(_("You have not made a move yet"), 0);
13632         return FALSE;
13633     }
13634
13635     return TRUE;
13636 }
13637
13638 void
13639 MailMoveEvent ()
13640 {
13641 #if !WIN32
13642     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13643     FILE *commandOutput;
13644     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13645     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13646     int nBuffers;
13647     int i;
13648     int archived;
13649     char *arcDir;
13650
13651     if (! cmailMsgLoaded) {
13652         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13653         return;
13654     }
13655
13656     if (nCmailGames == nCmailResults) {
13657         DisplayError(_("No unfinished games"), 0);
13658         return;
13659     }
13660
13661 #if CMAIL_PROHIBIT_REMAIL
13662     if (cmailMailedMove) {
13663       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);
13664         DisplayError(msg, 0);
13665         return;
13666     }
13667 #endif
13668
13669     if (! (cmailMailedMove || RegisterMove())) return;
13670
13671     if (   cmailMailedMove
13672         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13673       snprintf(string, MSG_SIZ, partCommandString,
13674                appData.debugMode ? " -v" : "", appData.cmailGameName);
13675         commandOutput = popen(string, "r");
13676
13677         if (commandOutput == NULL) {
13678             DisplayError(_("Failed to invoke cmail"), 0);
13679         } else {
13680             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13681                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13682             }
13683             if (nBuffers > 1) {
13684                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13685                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13686                 nBytes = MSG_SIZ - 1;
13687             } else {
13688                 (void) memcpy(msg, buffer, nBytes);
13689             }
13690             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13691
13692             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13693                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13694
13695                 archived = TRUE;
13696                 for (i = 0; i < nCmailGames; i ++) {
13697                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13698                         archived = FALSE;
13699                     }
13700                 }
13701                 if (   archived
13702                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13703                         != NULL)) {
13704                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13705                            arcDir,
13706                            appData.cmailGameName,
13707                            gameInfo.date);
13708                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13709                     cmailMsgLoaded = FALSE;
13710                 }
13711             }
13712
13713             DisplayInformation(msg);
13714             pclose(commandOutput);
13715         }
13716     } else {
13717         if ((*cmailMsg) != '\0') {
13718             DisplayInformation(cmailMsg);
13719         }
13720     }
13721
13722     return;
13723 #endif /* !WIN32 */
13724 }
13725
13726 char *
13727 CmailMsg ()
13728 {
13729 #if WIN32
13730     return NULL;
13731 #else
13732     int  prependComma = 0;
13733     char number[5];
13734     char string[MSG_SIZ];       /* Space for game-list */
13735     int  i;
13736
13737     if (!cmailMsgLoaded) return "";
13738
13739     if (cmailMailedMove) {
13740       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13741     } else {
13742         /* Create a list of games left */
13743       snprintf(string, MSG_SIZ, "[");
13744         for (i = 0; i < nCmailGames; i ++) {
13745             if (! (   cmailMoveRegistered[i]
13746                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13747                 if (prependComma) {
13748                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13749                 } else {
13750                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13751                     prependComma = 1;
13752                 }
13753
13754                 strcat(string, number);
13755             }
13756         }
13757         strcat(string, "]");
13758
13759         if (nCmailMovesRegistered + nCmailResults == 0) {
13760             switch (nCmailGames) {
13761               case 1:
13762                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13763                 break;
13764
13765               case 2:
13766                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13767                 break;
13768
13769               default:
13770                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13771                          nCmailGames);
13772                 break;
13773             }
13774         } else {
13775             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13776               case 1:
13777                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13778                          string);
13779                 break;
13780
13781               case 0:
13782                 if (nCmailResults == nCmailGames) {
13783                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13784                 } else {
13785                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13786                 }
13787                 break;
13788
13789               default:
13790                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13791                          string);
13792             }
13793         }
13794     }
13795     return cmailMsg;
13796 #endif /* WIN32 */
13797 }
13798
13799 void
13800 ResetGameEvent ()
13801 {
13802     if (gameMode == Training)
13803       SetTrainingModeOff();
13804
13805     Reset(TRUE, TRUE);
13806     cmailMsgLoaded = FALSE;
13807     if (appData.icsActive) {
13808       SendToICS(ics_prefix);
13809       SendToICS("refresh\n");
13810     }
13811 }
13812
13813 void
13814 ExitEvent (int status)
13815 {
13816     exiting++;
13817     if (exiting > 2) {
13818       /* Give up on clean exit */
13819       exit(status);
13820     }
13821     if (exiting > 1) {
13822       /* Keep trying for clean exit */
13823       return;
13824     }
13825
13826     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13827
13828     if (telnetISR != NULL) {
13829       RemoveInputSource(telnetISR);
13830     }
13831     if (icsPR != NoProc) {
13832       DestroyChildProcess(icsPR, TRUE);
13833     }
13834
13835     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13836     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13837
13838     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13839     /* make sure this other one finishes before killing it!                  */
13840     if(endingGame) { int count = 0;
13841         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13842         while(endingGame && count++ < 10) DoSleep(1);
13843         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13844     }
13845
13846     /* Kill off chess programs */
13847     if (first.pr != NoProc) {
13848         ExitAnalyzeMode();
13849
13850         DoSleep( appData.delayBeforeQuit );
13851         SendToProgram("quit\n", &first);
13852         DoSleep( appData.delayAfterQuit );
13853         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13854     }
13855     if (second.pr != NoProc) {
13856         DoSleep( appData.delayBeforeQuit );
13857         SendToProgram("quit\n", &second);
13858         DoSleep( appData.delayAfterQuit );
13859         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13860     }
13861     if (first.isr != NULL) {
13862         RemoveInputSource(first.isr);
13863     }
13864     if (second.isr != NULL) {
13865         RemoveInputSource(second.isr);
13866     }
13867
13868     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13869     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13870
13871     ShutDownFrontEnd();
13872     exit(status);
13873 }
13874
13875 void
13876 PauseEngine (ChessProgramState *cps)
13877 {
13878     SendToProgram("pause\n", cps);
13879     cps->pause = 2;
13880 }
13881
13882 void
13883 UnPauseEngine (ChessProgramState *cps)
13884 {
13885     SendToProgram("resume\n", cps);
13886     cps->pause = 1;
13887 }
13888
13889 void
13890 PauseEvent ()
13891 {
13892     if (appData.debugMode)
13893         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13894     if (pausing) {
13895         pausing = FALSE;
13896         ModeHighlight();
13897         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13898             StartClocks();
13899             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13900                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13901                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13902             }
13903             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13904             HandleMachineMove(stashedInputMove, stalledEngine);
13905             stalledEngine = NULL;
13906             return;
13907         }
13908         if (gameMode == MachinePlaysWhite ||
13909             gameMode == TwoMachinesPlay   ||
13910             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13911             if(first.pause)  UnPauseEngine(&first);
13912             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13913             if(second.pause) UnPauseEngine(&second);
13914             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13915             StartClocks();
13916         } else {
13917             DisplayBothClocks();
13918         }
13919         if (gameMode == PlayFromGameFile) {
13920             if (appData.timeDelay >= 0)
13921                 AutoPlayGameLoop();
13922         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13923             Reset(FALSE, TRUE);
13924             SendToICS(ics_prefix);
13925             SendToICS("refresh\n");
13926         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13927             ForwardInner(forwardMostMove);
13928         }
13929         pauseExamInvalid = FALSE;
13930     } else {
13931         switch (gameMode) {
13932           default:
13933             return;
13934           case IcsExamining:
13935             pauseExamForwardMostMove = forwardMostMove;
13936             pauseExamInvalid = FALSE;
13937             /* fall through */
13938           case IcsObserving:
13939           case IcsPlayingWhite:
13940           case IcsPlayingBlack:
13941             pausing = TRUE;
13942             ModeHighlight();
13943             return;
13944           case PlayFromGameFile:
13945             (void) StopLoadGameTimer();
13946             pausing = TRUE;
13947             ModeHighlight();
13948             break;
13949           case BeginningOfGame:
13950             if (appData.icsActive) return;
13951             /* else fall through */
13952           case MachinePlaysWhite:
13953           case MachinePlaysBlack:
13954           case TwoMachinesPlay:
13955             if (forwardMostMove == 0)
13956               return;           /* don't pause if no one has moved */
13957             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13958                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13959                 if(onMove->pause) {           // thinking engine can be paused
13960                     PauseEngine(onMove);      // do it
13961                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13962                         PauseEngine(onMove->other);
13963                     else
13964                         SendToProgram("easy\n", onMove->other);
13965                     StopClocks();
13966                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13967             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13968                 if(first.pause) {
13969                     PauseEngine(&first);
13970                     StopClocks();
13971                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13972             } else { // human on move, pause pondering by either method
13973                 if(first.pause)
13974                     PauseEngine(&first);
13975                 else if(appData.ponderNextMove)
13976                     SendToProgram("easy\n", &first);
13977                 StopClocks();
13978             }
13979             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13980           case AnalyzeMode:
13981             pausing = TRUE;
13982             ModeHighlight();
13983             break;
13984         }
13985     }
13986 }
13987
13988 void
13989 EditCommentEvent ()
13990 {
13991     char title[MSG_SIZ];
13992
13993     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13994       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13995     } else {
13996       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13997                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13998                parseList[currentMove - 1]);
13999     }
14000
14001     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14002 }
14003
14004
14005 void
14006 EditTagsEvent ()
14007 {
14008     char *tags = PGNTags(&gameInfo);
14009     bookUp = FALSE;
14010     EditTagsPopUp(tags, NULL);
14011     free(tags);
14012 }
14013
14014 void
14015 ToggleSecond ()
14016 {
14017   if(second.analyzing) {
14018     SendToProgram("exit\n", &second);
14019     second.analyzing = FALSE;
14020   } else {
14021     if (second.pr == NoProc) StartChessProgram(&second);
14022     InitChessProgram(&second, FALSE);
14023     FeedMovesToProgram(&second, currentMove);
14024
14025     SendToProgram("analyze\n", &second);
14026     second.analyzing = TRUE;
14027   }
14028 }
14029
14030 /* Toggle ShowThinking */
14031 void
14032 ToggleShowThinking()
14033 {
14034   appData.showThinking = !appData.showThinking;
14035   ShowThinkingEvent();
14036 }
14037
14038 int
14039 AnalyzeModeEvent ()
14040 {
14041     char buf[MSG_SIZ];
14042
14043     if (!first.analysisSupport) {
14044       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14045       DisplayError(buf, 0);
14046       return 0;
14047     }
14048     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14049     if (appData.icsActive) {
14050         if (gameMode != IcsObserving) {
14051           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14052             DisplayError(buf, 0);
14053             /* secure check */
14054             if (appData.icsEngineAnalyze) {
14055                 if (appData.debugMode)
14056                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14057                 ExitAnalyzeMode();
14058                 ModeHighlight();
14059             }
14060             return 0;
14061         }
14062         /* if enable, user wants to disable icsEngineAnalyze */
14063         if (appData.icsEngineAnalyze) {
14064                 ExitAnalyzeMode();
14065                 ModeHighlight();
14066                 return 0;
14067         }
14068         appData.icsEngineAnalyze = TRUE;
14069         if (appData.debugMode)
14070             fprintf(debugFP, "ICS engine analyze starting... \n");
14071     }
14072
14073     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14074     if (appData.noChessProgram || gameMode == AnalyzeMode)
14075       return 0;
14076
14077     if (gameMode != AnalyzeFile) {
14078         if (!appData.icsEngineAnalyze) {
14079                EditGameEvent();
14080                if (gameMode != EditGame) return 0;
14081         }
14082         if (!appData.showThinking) ToggleShowThinking();
14083         ResurrectChessProgram();
14084         SendToProgram("analyze\n", &first);
14085         first.analyzing = TRUE;
14086         /*first.maybeThinking = TRUE;*/
14087         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14088         EngineOutputPopUp();
14089     }
14090     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14091     pausing = FALSE;
14092     ModeHighlight();
14093     SetGameInfo();
14094
14095     StartAnalysisClock();
14096     GetTimeMark(&lastNodeCountTime);
14097     lastNodeCount = 0;
14098     return 1;
14099 }
14100
14101 void
14102 AnalyzeFileEvent ()
14103 {
14104     if (appData.noChessProgram || gameMode == AnalyzeFile)
14105       return;
14106
14107     if (!first.analysisSupport) {
14108       char buf[MSG_SIZ];
14109       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14110       DisplayError(buf, 0);
14111       return;
14112     }
14113
14114     if (gameMode != AnalyzeMode) {
14115         keepInfo = 1; // mere annotating should not alter PGN tags
14116         EditGameEvent();
14117         keepInfo = 0;
14118         if (gameMode != EditGame) return;
14119         if (!appData.showThinking) ToggleShowThinking();
14120         ResurrectChessProgram();
14121         SendToProgram("analyze\n", &first);
14122         first.analyzing = TRUE;
14123         /*first.maybeThinking = TRUE;*/
14124         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14125         EngineOutputPopUp();
14126     }
14127     gameMode = AnalyzeFile;
14128     pausing = FALSE;
14129     ModeHighlight();
14130
14131     StartAnalysisClock();
14132     GetTimeMark(&lastNodeCountTime);
14133     lastNodeCount = 0;
14134     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14135     AnalysisPeriodicEvent(1);
14136 }
14137
14138 void
14139 MachineWhiteEvent ()
14140 {
14141     char buf[MSG_SIZ];
14142     char *bookHit = NULL;
14143
14144     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14145       return;
14146
14147
14148     if (gameMode == PlayFromGameFile ||
14149         gameMode == TwoMachinesPlay  ||
14150         gameMode == Training         ||
14151         gameMode == AnalyzeMode      ||
14152         gameMode == EndOfGame)
14153         EditGameEvent();
14154
14155     if (gameMode == EditPosition)
14156         EditPositionDone(TRUE);
14157
14158     if (!WhiteOnMove(currentMove)) {
14159         DisplayError(_("It is not White's turn"), 0);
14160         return;
14161     }
14162
14163     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14164       ExitAnalyzeMode();
14165
14166     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14167         gameMode == AnalyzeFile)
14168         TruncateGame();
14169
14170     ResurrectChessProgram();    /* in case it isn't running */
14171     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14172         gameMode = MachinePlaysWhite;
14173         ResetClocks();
14174     } else
14175     gameMode = MachinePlaysWhite;
14176     pausing = FALSE;
14177     ModeHighlight();
14178     SetGameInfo();
14179     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14180     DisplayTitle(buf);
14181     if (first.sendName) {
14182       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14183       SendToProgram(buf, &first);
14184     }
14185     if (first.sendTime) {
14186       if (first.useColors) {
14187         SendToProgram("black\n", &first); /*gnu kludge*/
14188       }
14189       SendTimeRemaining(&first, TRUE);
14190     }
14191     if (first.useColors) {
14192       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14193     }
14194     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14195     SetMachineThinkingEnables();
14196     first.maybeThinking = TRUE;
14197     StartClocks();
14198     firstMove = FALSE;
14199
14200     if (appData.autoFlipView && !flipView) {
14201       flipView = !flipView;
14202       DrawPosition(FALSE, NULL);
14203       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14204     }
14205
14206     if(bookHit) { // [HGM] book: simulate book reply
14207         static char bookMove[MSG_SIZ]; // a bit generous?
14208
14209         programStats.nodes = programStats.depth = programStats.time =
14210         programStats.score = programStats.got_only_move = 0;
14211         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14212
14213         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14214         strcat(bookMove, bookHit);
14215         HandleMachineMove(bookMove, &first);
14216     }
14217 }
14218
14219 void
14220 MachineBlackEvent ()
14221 {
14222   char buf[MSG_SIZ];
14223   char *bookHit = NULL;
14224
14225     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14226         return;
14227
14228
14229     if (gameMode == PlayFromGameFile ||
14230         gameMode == TwoMachinesPlay  ||
14231         gameMode == Training         ||
14232         gameMode == AnalyzeMode      ||
14233         gameMode == EndOfGame)
14234         EditGameEvent();
14235
14236     if (gameMode == EditPosition)
14237         EditPositionDone(TRUE);
14238
14239     if (WhiteOnMove(currentMove)) {
14240         DisplayError(_("It is not Black's turn"), 0);
14241         return;
14242     }
14243
14244     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14245       ExitAnalyzeMode();
14246
14247     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14248         gameMode == AnalyzeFile)
14249         TruncateGame();
14250
14251     ResurrectChessProgram();    /* in case it isn't running */
14252     gameMode = MachinePlaysBlack;
14253     pausing = FALSE;
14254     ModeHighlight();
14255     SetGameInfo();
14256     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14257     DisplayTitle(buf);
14258     if (first.sendName) {
14259       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14260       SendToProgram(buf, &first);
14261     }
14262     if (first.sendTime) {
14263       if (first.useColors) {
14264         SendToProgram("white\n", &first); /*gnu kludge*/
14265       }
14266       SendTimeRemaining(&first, FALSE);
14267     }
14268     if (first.useColors) {
14269       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14270     }
14271     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14272     SetMachineThinkingEnables();
14273     first.maybeThinking = TRUE;
14274     StartClocks();
14275
14276     if (appData.autoFlipView && flipView) {
14277       flipView = !flipView;
14278       DrawPosition(FALSE, NULL);
14279       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14280     }
14281     if(bookHit) { // [HGM] book: simulate book reply
14282         static char bookMove[MSG_SIZ]; // a bit generous?
14283
14284         programStats.nodes = programStats.depth = programStats.time =
14285         programStats.score = programStats.got_only_move = 0;
14286         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14287
14288         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14289         strcat(bookMove, bookHit);
14290         HandleMachineMove(bookMove, &first);
14291     }
14292 }
14293
14294
14295 void
14296 DisplayTwoMachinesTitle ()
14297 {
14298     char buf[MSG_SIZ];
14299     if (appData.matchGames > 0) {
14300         if(appData.tourneyFile[0]) {
14301           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14302                    gameInfo.white, _("vs."), gameInfo.black,
14303                    nextGame+1, appData.matchGames+1,
14304                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14305         } else
14306         if (first.twoMachinesColor[0] == 'w') {
14307           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14308                    gameInfo.white, _("vs."),  gameInfo.black,
14309                    first.matchWins, second.matchWins,
14310                    matchGame - 1 - (first.matchWins + second.matchWins));
14311         } else {
14312           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14313                    gameInfo.white, _("vs."), gameInfo.black,
14314                    second.matchWins, first.matchWins,
14315                    matchGame - 1 - (first.matchWins + second.matchWins));
14316         }
14317     } else {
14318       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14319     }
14320     DisplayTitle(buf);
14321 }
14322
14323 void
14324 SettingsMenuIfReady ()
14325 {
14326   if (second.lastPing != second.lastPong) {
14327     DisplayMessage("", _("Waiting for second chess program"));
14328     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14329     return;
14330   }
14331   ThawUI();
14332   DisplayMessage("", "");
14333   SettingsPopUp(&second);
14334 }
14335
14336 int
14337 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14338 {
14339     char buf[MSG_SIZ];
14340     if (cps->pr == NoProc) {
14341         StartChessProgram(cps);
14342         if (cps->protocolVersion == 1) {
14343           retry();
14344           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14345         } else {
14346           /* kludge: allow timeout for initial "feature" command */
14347           if(retry != TwoMachinesEventIfReady) FreezeUI();
14348           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14349           DisplayMessage("", buf);
14350           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14351         }
14352         return 1;
14353     }
14354     return 0;
14355 }
14356
14357 void
14358 TwoMachinesEvent P((void))
14359 {
14360     int i;
14361     char buf[MSG_SIZ];
14362     ChessProgramState *onmove;
14363     char *bookHit = NULL;
14364     static int stalling = 0;
14365     TimeMark now;
14366     long wait;
14367
14368     if (appData.noChessProgram) return;
14369
14370     switch (gameMode) {
14371       case TwoMachinesPlay:
14372         return;
14373       case MachinePlaysWhite:
14374       case MachinePlaysBlack:
14375         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14376             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14377             return;
14378         }
14379         /* fall through */
14380       case BeginningOfGame:
14381       case PlayFromGameFile:
14382       case EndOfGame:
14383         EditGameEvent();
14384         if (gameMode != EditGame) return;
14385         break;
14386       case EditPosition:
14387         EditPositionDone(TRUE);
14388         break;
14389       case AnalyzeMode:
14390       case AnalyzeFile:
14391         ExitAnalyzeMode();
14392         break;
14393       case EditGame:
14394       default:
14395         break;
14396     }
14397
14398 //    forwardMostMove = currentMove;
14399     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14400     startingEngine = TRUE;
14401
14402     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14403
14404     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14405     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14406       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14407       return;
14408     }
14409     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14410
14411     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14412                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14413         startingEngine = FALSE;
14414         DisplayError("second engine does not play this", 0);
14415         return;
14416     }
14417
14418     if(!stalling) {
14419       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14420       SendToProgram("force\n", &second);
14421       stalling = 1;
14422       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14423       return;
14424     }
14425     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14426     if(appData.matchPause>10000 || appData.matchPause<10)
14427                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14428     wait = SubtractTimeMarks(&now, &pauseStart);
14429     if(wait < appData.matchPause) {
14430         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14431         return;
14432     }
14433     // we are now committed to starting the game
14434     stalling = 0;
14435     DisplayMessage("", "");
14436     if (startedFromSetupPosition) {
14437         SendBoard(&second, backwardMostMove);
14438     if (appData.debugMode) {
14439         fprintf(debugFP, "Two Machines\n");
14440     }
14441     }
14442     for (i = backwardMostMove; i < forwardMostMove; i++) {
14443         SendMoveToProgram(i, &second);
14444     }
14445
14446     gameMode = TwoMachinesPlay;
14447     pausing = startingEngine = FALSE;
14448     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14449     SetGameInfo();
14450     DisplayTwoMachinesTitle();
14451     firstMove = TRUE;
14452     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14453         onmove = &first;
14454     } else {
14455         onmove = &second;
14456     }
14457     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14458     SendToProgram(first.computerString, &first);
14459     if (first.sendName) {
14460       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14461       SendToProgram(buf, &first);
14462     }
14463     SendToProgram(second.computerString, &second);
14464     if (second.sendName) {
14465       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14466       SendToProgram(buf, &second);
14467     }
14468
14469     ResetClocks();
14470     if (!first.sendTime || !second.sendTime) {
14471         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14472         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14473     }
14474     if (onmove->sendTime) {
14475       if (onmove->useColors) {
14476         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14477       }
14478       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14479     }
14480     if (onmove->useColors) {
14481       SendToProgram(onmove->twoMachinesColor, onmove);
14482     }
14483     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14484 //    SendToProgram("go\n", onmove);
14485     onmove->maybeThinking = TRUE;
14486     SetMachineThinkingEnables();
14487
14488     StartClocks();
14489
14490     if(bookHit) { // [HGM] book: simulate book reply
14491         static char bookMove[MSG_SIZ]; // a bit generous?
14492
14493         programStats.nodes = programStats.depth = programStats.time =
14494         programStats.score = programStats.got_only_move = 0;
14495         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14496
14497         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14498         strcat(bookMove, bookHit);
14499         savedMessage = bookMove; // args for deferred call
14500         savedState = onmove;
14501         ScheduleDelayedEvent(DeferredBookMove, 1);
14502     }
14503 }
14504
14505 void
14506 TrainingEvent ()
14507 {
14508     if (gameMode == Training) {
14509       SetTrainingModeOff();
14510       gameMode = PlayFromGameFile;
14511       DisplayMessage("", _("Training mode off"));
14512     } else {
14513       gameMode = Training;
14514       animateTraining = appData.animate;
14515
14516       /* make sure we are not already at the end of the game */
14517       if (currentMove < forwardMostMove) {
14518         SetTrainingModeOn();
14519         DisplayMessage("", _("Training mode on"));
14520       } else {
14521         gameMode = PlayFromGameFile;
14522         DisplayError(_("Already at end of game"), 0);
14523       }
14524     }
14525     ModeHighlight();
14526 }
14527
14528 void
14529 IcsClientEvent ()
14530 {
14531     if (!appData.icsActive) return;
14532     switch (gameMode) {
14533       case IcsPlayingWhite:
14534       case IcsPlayingBlack:
14535       case IcsObserving:
14536       case IcsIdle:
14537       case BeginningOfGame:
14538       case IcsExamining:
14539         return;
14540
14541       case EditGame:
14542         break;
14543
14544       case EditPosition:
14545         EditPositionDone(TRUE);
14546         break;
14547
14548       case AnalyzeMode:
14549       case AnalyzeFile:
14550         ExitAnalyzeMode();
14551         break;
14552
14553       default:
14554         EditGameEvent();
14555         break;
14556     }
14557
14558     gameMode = IcsIdle;
14559     ModeHighlight();
14560     return;
14561 }
14562
14563 void
14564 EditGameEvent ()
14565 {
14566     int i;
14567
14568     switch (gameMode) {
14569       case Training:
14570         SetTrainingModeOff();
14571         break;
14572       case MachinePlaysWhite:
14573       case MachinePlaysBlack:
14574       case BeginningOfGame:
14575         SendToProgram("force\n", &first);
14576         SetUserThinkingEnables();
14577         break;
14578       case PlayFromGameFile:
14579         (void) StopLoadGameTimer();
14580         if (gameFileFP != NULL) {
14581             gameFileFP = NULL;
14582         }
14583         break;
14584       case EditPosition:
14585         EditPositionDone(TRUE);
14586         break;
14587       case AnalyzeMode:
14588       case AnalyzeFile:
14589         ExitAnalyzeMode();
14590         SendToProgram("force\n", &first);
14591         break;
14592       case TwoMachinesPlay:
14593         GameEnds(EndOfFile, NULL, GE_PLAYER);
14594         ResurrectChessProgram();
14595         SetUserThinkingEnables();
14596         break;
14597       case EndOfGame:
14598         ResurrectChessProgram();
14599         break;
14600       case IcsPlayingBlack:
14601       case IcsPlayingWhite:
14602         DisplayError(_("Warning: You are still playing a game"), 0);
14603         break;
14604       case IcsObserving:
14605         DisplayError(_("Warning: You are still observing a game"), 0);
14606         break;
14607       case IcsExamining:
14608         DisplayError(_("Warning: You are still examining a game"), 0);
14609         break;
14610       case IcsIdle:
14611         break;
14612       case EditGame:
14613       default:
14614         return;
14615     }
14616
14617     pausing = FALSE;
14618     StopClocks();
14619     first.offeredDraw = second.offeredDraw = 0;
14620
14621     if (gameMode == PlayFromGameFile) {
14622         whiteTimeRemaining = timeRemaining[0][currentMove];
14623         blackTimeRemaining = timeRemaining[1][currentMove];
14624         DisplayTitle("");
14625     }
14626
14627     if (gameMode == MachinePlaysWhite ||
14628         gameMode == MachinePlaysBlack ||
14629         gameMode == TwoMachinesPlay ||
14630         gameMode == EndOfGame) {
14631         i = forwardMostMove;
14632         while (i > currentMove) {
14633             SendToProgram("undo\n", &first);
14634             i--;
14635         }
14636         if(!adjustedClock) {
14637         whiteTimeRemaining = timeRemaining[0][currentMove];
14638         blackTimeRemaining = timeRemaining[1][currentMove];
14639         DisplayBothClocks();
14640         }
14641         if (whiteFlag || blackFlag) {
14642             whiteFlag = blackFlag = 0;
14643         }
14644         DisplayTitle("");
14645     }
14646
14647     gameMode = EditGame;
14648     ModeHighlight();
14649     SetGameInfo();
14650 }
14651
14652
14653 void
14654 EditPositionEvent ()
14655 {
14656     if (gameMode == EditPosition) {
14657         EditGameEvent();
14658         return;
14659     }
14660
14661     EditGameEvent();
14662     if (gameMode != EditGame) return;
14663
14664     gameMode = EditPosition;
14665     ModeHighlight();
14666     SetGameInfo();
14667     if (currentMove > 0)
14668       CopyBoard(boards[0], boards[currentMove]);
14669
14670     blackPlaysFirst = !WhiteOnMove(currentMove);
14671     ResetClocks();
14672     currentMove = forwardMostMove = backwardMostMove = 0;
14673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14674     DisplayMove(-1);
14675     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14676 }
14677
14678 void
14679 ExitAnalyzeMode ()
14680 {
14681     /* [DM] icsEngineAnalyze - possible call from other functions */
14682     if (appData.icsEngineAnalyze) {
14683         appData.icsEngineAnalyze = FALSE;
14684
14685         DisplayMessage("",_("Close ICS engine analyze..."));
14686     }
14687     if (first.analysisSupport && first.analyzing) {
14688       SendToBoth("exit\n");
14689       first.analyzing = second.analyzing = FALSE;
14690     }
14691     thinkOutput[0] = NULLCHAR;
14692 }
14693
14694 void
14695 EditPositionDone (Boolean fakeRights)
14696 {
14697     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14698
14699     startedFromSetupPosition = TRUE;
14700     InitChessProgram(&first, FALSE);
14701     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14702       boards[0][EP_STATUS] = EP_NONE;
14703       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14704       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14705         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14706         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14707       } else boards[0][CASTLING][2] = NoRights;
14708       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14709         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14710         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14711       } else boards[0][CASTLING][5] = NoRights;
14712       if(gameInfo.variant == VariantSChess) {
14713         int i;
14714         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14715           boards[0][VIRGIN][i] = 0;
14716           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14717           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14718         }
14719       }
14720     }
14721     SendToProgram("force\n", &first);
14722     if (blackPlaysFirst) {
14723         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14724         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14725         currentMove = forwardMostMove = backwardMostMove = 1;
14726         CopyBoard(boards[1], boards[0]);
14727     } else {
14728         currentMove = forwardMostMove = backwardMostMove = 0;
14729     }
14730     SendBoard(&first, forwardMostMove);
14731     if (appData.debugMode) {
14732         fprintf(debugFP, "EditPosDone\n");
14733     }
14734     DisplayTitle("");
14735     DisplayMessage("", "");
14736     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14737     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14738     gameMode = EditGame;
14739     ModeHighlight();
14740     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14741     ClearHighlights(); /* [AS] */
14742 }
14743
14744 /* Pause for `ms' milliseconds */
14745 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14746 void
14747 TimeDelay (long ms)
14748 {
14749     TimeMark m1, m2;
14750
14751     GetTimeMark(&m1);
14752     do {
14753         GetTimeMark(&m2);
14754     } while (SubtractTimeMarks(&m2, &m1) < ms);
14755 }
14756
14757 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14758 void
14759 SendMultiLineToICS (char *buf)
14760 {
14761     char temp[MSG_SIZ+1], *p;
14762     int len;
14763
14764     len = strlen(buf);
14765     if (len > MSG_SIZ)
14766       len = MSG_SIZ;
14767
14768     strncpy(temp, buf, len);
14769     temp[len] = 0;
14770
14771     p = temp;
14772     while (*p) {
14773         if (*p == '\n' || *p == '\r')
14774           *p = ' ';
14775         ++p;
14776     }
14777
14778     strcat(temp, "\n");
14779     SendToICS(temp);
14780     SendToPlayer(temp, strlen(temp));
14781 }
14782
14783 void
14784 SetWhiteToPlayEvent ()
14785 {
14786     if (gameMode == EditPosition) {
14787         blackPlaysFirst = FALSE;
14788         DisplayBothClocks();    /* works because currentMove is 0 */
14789     } else if (gameMode == IcsExamining) {
14790         SendToICS(ics_prefix);
14791         SendToICS("tomove white\n");
14792     }
14793 }
14794
14795 void
14796 SetBlackToPlayEvent ()
14797 {
14798     if (gameMode == EditPosition) {
14799         blackPlaysFirst = TRUE;
14800         currentMove = 1;        /* kludge */
14801         DisplayBothClocks();
14802         currentMove = 0;
14803     } else if (gameMode == IcsExamining) {
14804         SendToICS(ics_prefix);
14805         SendToICS("tomove black\n");
14806     }
14807 }
14808
14809 void
14810 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14811 {
14812     char buf[MSG_SIZ];
14813     ChessSquare piece = boards[0][y][x];
14814     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14815     static int lastVariant;
14816
14817     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14818
14819     switch (selection) {
14820       case ClearBoard:
14821         CopyBoard(currentBoard, boards[0]);
14822         CopyBoard(menuBoard, initialPosition);
14823         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14824             SendToICS(ics_prefix);
14825             SendToICS("bsetup clear\n");
14826         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14827             SendToICS(ics_prefix);
14828             SendToICS("clearboard\n");
14829         } else {
14830             int nonEmpty = 0;
14831             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14832                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14833                 for (y = 0; y < BOARD_HEIGHT; y++) {
14834                     if (gameMode == IcsExamining) {
14835                         if (boards[currentMove][y][x] != EmptySquare) {
14836                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14837                                     AAA + x, ONE + y);
14838                             SendToICS(buf);
14839                         }
14840                     } else {
14841                         if(boards[0][y][x] != p) nonEmpty++;
14842                         boards[0][y][x] = p;
14843                     }
14844                 }
14845                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14846             }
14847             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14848                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14849                     ChessSquare p = menuBoard[0][x];
14850                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14851                     p = menuBoard[BOARD_HEIGHT-1][x];
14852                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14853                 }
14854                 DisplayMessage("Clicking clock again restores position", "");
14855                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14856                 if(!nonEmpty) { // asked to clear an empty board
14857                     CopyBoard(boards[0], menuBoard);
14858                 } else
14859                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14860                     CopyBoard(boards[0], initialPosition);
14861                 } else
14862                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14863                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14864                     CopyBoard(boards[0], erasedBoard);
14865                 } else
14866                     CopyBoard(erasedBoard, currentBoard);
14867
14868             }
14869         }
14870         if (gameMode == EditPosition) {
14871             DrawPosition(FALSE, boards[0]);
14872         }
14873         break;
14874
14875       case WhitePlay:
14876         SetWhiteToPlayEvent();
14877         break;
14878
14879       case BlackPlay:
14880         SetBlackToPlayEvent();
14881         break;
14882
14883       case EmptySquare:
14884         if (gameMode == IcsExamining) {
14885             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14886             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14887             SendToICS(buf);
14888         } else {
14889             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14890                 if(x == BOARD_LEFT-2) {
14891                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14892                     boards[0][y][1] = 0;
14893                 } else
14894                 if(x == BOARD_RGHT+1) {
14895                     if(y >= gameInfo.holdingsSize) break;
14896                     boards[0][y][BOARD_WIDTH-2] = 0;
14897                 } else break;
14898             }
14899             boards[0][y][x] = EmptySquare;
14900             DrawPosition(FALSE, boards[0]);
14901         }
14902         break;
14903
14904       case PromotePiece:
14905         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14906            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14907             selection = (ChessSquare) (PROMOTED piece);
14908         } else if(piece == EmptySquare) selection = WhiteSilver;
14909         else selection = (ChessSquare)((int)piece - 1);
14910         goto defaultlabel;
14911
14912       case DemotePiece:
14913         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14914            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14915             selection = (ChessSquare) (DEMOTED piece);
14916         } else if(piece == EmptySquare) selection = BlackSilver;
14917         else selection = (ChessSquare)((int)piece + 1);
14918         goto defaultlabel;
14919
14920       case WhiteQueen:
14921       case BlackQueen:
14922         if(gameInfo.variant == VariantShatranj ||
14923            gameInfo.variant == VariantXiangqi  ||
14924            gameInfo.variant == VariantCourier  ||
14925            gameInfo.variant == VariantASEAN    ||
14926            gameInfo.variant == VariantMakruk     )
14927             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14928         goto defaultlabel;
14929
14930       case WhiteKing:
14931       case BlackKing:
14932         if(gameInfo.variant == VariantXiangqi)
14933             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14934         if(gameInfo.variant == VariantKnightmate)
14935             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14936       default:
14937         defaultlabel:
14938         if (gameMode == IcsExamining) {
14939             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14940             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14941                      PieceToChar(selection), AAA + x, ONE + y);
14942             SendToICS(buf);
14943         } else {
14944             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14945                 int n;
14946                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14947                     n = PieceToNumber(selection - BlackPawn);
14948                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14949                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14950                     boards[0][BOARD_HEIGHT-1-n][1]++;
14951                 } else
14952                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14953                     n = PieceToNumber(selection);
14954                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14955                     boards[0][n][BOARD_WIDTH-1] = selection;
14956                     boards[0][n][BOARD_WIDTH-2]++;
14957                 }
14958             } else
14959             boards[0][y][x] = selection;
14960             DrawPosition(TRUE, boards[0]);
14961             ClearHighlights();
14962             fromX = fromY = -1;
14963         }
14964         break;
14965     }
14966 }
14967
14968
14969 void
14970 DropMenuEvent (ChessSquare selection, int x, int y)
14971 {
14972     ChessMove moveType;
14973
14974     switch (gameMode) {
14975       case IcsPlayingWhite:
14976       case MachinePlaysBlack:
14977         if (!WhiteOnMove(currentMove)) {
14978             DisplayMoveError(_("It is Black's turn"));
14979             return;
14980         }
14981         moveType = WhiteDrop;
14982         break;
14983       case IcsPlayingBlack:
14984       case MachinePlaysWhite:
14985         if (WhiteOnMove(currentMove)) {
14986             DisplayMoveError(_("It is White's turn"));
14987             return;
14988         }
14989         moveType = BlackDrop;
14990         break;
14991       case EditGame:
14992         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14993         break;
14994       default:
14995         return;
14996     }
14997
14998     if (moveType == BlackDrop && selection < BlackPawn) {
14999       selection = (ChessSquare) ((int) selection
15000                                  + (int) BlackPawn - (int) WhitePawn);
15001     }
15002     if (boards[currentMove][y][x] != EmptySquare) {
15003         DisplayMoveError(_("That square is occupied"));
15004         return;
15005     }
15006
15007     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15008 }
15009
15010 void
15011 AcceptEvent ()
15012 {
15013     /* Accept a pending offer of any kind from opponent */
15014
15015     if (appData.icsActive) {
15016         SendToICS(ics_prefix);
15017         SendToICS("accept\n");
15018     } else if (cmailMsgLoaded) {
15019         if (currentMove == cmailOldMove &&
15020             commentList[cmailOldMove] != NULL &&
15021             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15022                    "Black offers a draw" : "White offers a draw")) {
15023             TruncateGame();
15024             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15025             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15026         } else {
15027             DisplayError(_("There is no pending offer on this move"), 0);
15028             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15029         }
15030     } else {
15031         /* Not used for offers from chess program */
15032     }
15033 }
15034
15035 void
15036 DeclineEvent ()
15037 {
15038     /* Decline a pending offer of any kind from opponent */
15039
15040     if (appData.icsActive) {
15041         SendToICS(ics_prefix);
15042         SendToICS("decline\n");
15043     } else if (cmailMsgLoaded) {
15044         if (currentMove == cmailOldMove &&
15045             commentList[cmailOldMove] != NULL &&
15046             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15047                    "Black offers a draw" : "White offers a draw")) {
15048 #ifdef NOTDEF
15049             AppendComment(cmailOldMove, "Draw declined", TRUE);
15050             DisplayComment(cmailOldMove - 1, "Draw declined");
15051 #endif /*NOTDEF*/
15052         } else {
15053             DisplayError(_("There is no pending offer on this move"), 0);
15054         }
15055     } else {
15056         /* Not used for offers from chess program */
15057     }
15058 }
15059
15060 void
15061 RematchEvent ()
15062 {
15063     /* Issue ICS rematch command */
15064     if (appData.icsActive) {
15065         SendToICS(ics_prefix);
15066         SendToICS("rematch\n");
15067     }
15068 }
15069
15070 void
15071 CallFlagEvent ()
15072 {
15073     /* Call your opponent's flag (claim a win on time) */
15074     if (appData.icsActive) {
15075         SendToICS(ics_prefix);
15076         SendToICS("flag\n");
15077     } else {
15078         switch (gameMode) {
15079           default:
15080             return;
15081           case MachinePlaysWhite:
15082             if (whiteFlag) {
15083                 if (blackFlag)
15084                   GameEnds(GameIsDrawn, "Both players ran out of time",
15085                            GE_PLAYER);
15086                 else
15087                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15088             } else {
15089                 DisplayError(_("Your opponent is not out of time"), 0);
15090             }
15091             break;
15092           case MachinePlaysBlack:
15093             if (blackFlag) {
15094                 if (whiteFlag)
15095                   GameEnds(GameIsDrawn, "Both players ran out of time",
15096                            GE_PLAYER);
15097                 else
15098                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15099             } else {
15100                 DisplayError(_("Your opponent is not out of time"), 0);
15101             }
15102             break;
15103         }
15104     }
15105 }
15106
15107 void
15108 ClockClick (int which)
15109 {       // [HGM] code moved to back-end from winboard.c
15110         if(which) { // black clock
15111           if (gameMode == EditPosition || gameMode == IcsExamining) {
15112             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15113             SetBlackToPlayEvent();
15114           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15115           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15116           } else if (shiftKey) {
15117             AdjustClock(which, -1);
15118           } else if (gameMode == IcsPlayingWhite ||
15119                      gameMode == MachinePlaysBlack) {
15120             CallFlagEvent();
15121           }
15122         } else { // white clock
15123           if (gameMode == EditPosition || gameMode == IcsExamining) {
15124             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15125             SetWhiteToPlayEvent();
15126           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15127           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15128           } else if (shiftKey) {
15129             AdjustClock(which, -1);
15130           } else if (gameMode == IcsPlayingBlack ||
15131                    gameMode == MachinePlaysWhite) {
15132             CallFlagEvent();
15133           }
15134         }
15135 }
15136
15137 void
15138 DrawEvent ()
15139 {
15140     /* Offer draw or accept pending draw offer from opponent */
15141
15142     if (appData.icsActive) {
15143         /* Note: tournament rules require draw offers to be
15144            made after you make your move but before you punch
15145            your clock.  Currently ICS doesn't let you do that;
15146            instead, you immediately punch your clock after making
15147            a move, but you can offer a draw at any time. */
15148
15149         SendToICS(ics_prefix);
15150         SendToICS("draw\n");
15151         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15152     } else if (cmailMsgLoaded) {
15153         if (currentMove == cmailOldMove &&
15154             commentList[cmailOldMove] != NULL &&
15155             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15156                    "Black offers a draw" : "White offers a draw")) {
15157             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15158             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15159         } else if (currentMove == cmailOldMove + 1) {
15160             char *offer = WhiteOnMove(cmailOldMove) ?
15161               "White offers a draw" : "Black offers a draw";
15162             AppendComment(currentMove, offer, TRUE);
15163             DisplayComment(currentMove - 1, offer);
15164             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15165         } else {
15166             DisplayError(_("You must make your move before offering a draw"), 0);
15167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15168         }
15169     } else if (first.offeredDraw) {
15170         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15171     } else {
15172         if (first.sendDrawOffers) {
15173             SendToProgram("draw\n", &first);
15174             userOfferedDraw = TRUE;
15175         }
15176     }
15177 }
15178
15179 void
15180 AdjournEvent ()
15181 {
15182     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15183
15184     if (appData.icsActive) {
15185         SendToICS(ics_prefix);
15186         SendToICS("adjourn\n");
15187     } else {
15188         /* Currently GNU Chess doesn't offer or accept Adjourns */
15189     }
15190 }
15191
15192
15193 void
15194 AbortEvent ()
15195 {
15196     /* Offer Abort or accept pending Abort offer from opponent */
15197
15198     if (appData.icsActive) {
15199         SendToICS(ics_prefix);
15200         SendToICS("abort\n");
15201     } else {
15202         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15203     }
15204 }
15205
15206 void
15207 ResignEvent ()
15208 {
15209     /* Resign.  You can do this even if it's not your turn. */
15210
15211     if (appData.icsActive) {
15212         SendToICS(ics_prefix);
15213         SendToICS("resign\n");
15214     } else {
15215         switch (gameMode) {
15216           case MachinePlaysWhite:
15217             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15218             break;
15219           case MachinePlaysBlack:
15220             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15221             break;
15222           case EditGame:
15223             if (cmailMsgLoaded) {
15224                 TruncateGame();
15225                 if (WhiteOnMove(cmailOldMove)) {
15226                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15227                 } else {
15228                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15229                 }
15230                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15231             }
15232             break;
15233           default:
15234             break;
15235         }
15236     }
15237 }
15238
15239
15240 void
15241 StopObservingEvent ()
15242 {
15243     /* Stop observing current games */
15244     SendToICS(ics_prefix);
15245     SendToICS("unobserve\n");
15246 }
15247
15248 void
15249 StopExaminingEvent ()
15250 {
15251     /* Stop observing current game */
15252     SendToICS(ics_prefix);
15253     SendToICS("unexamine\n");
15254 }
15255
15256 void
15257 ForwardInner (int target)
15258 {
15259     int limit; int oldSeekGraphUp = seekGraphUp;
15260
15261     if (appData.debugMode)
15262         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15263                 target, currentMove, forwardMostMove);
15264
15265     if (gameMode == EditPosition)
15266       return;
15267
15268     seekGraphUp = FALSE;
15269     MarkTargetSquares(1);
15270
15271     if (gameMode == PlayFromGameFile && !pausing)
15272       PauseEvent();
15273
15274     if (gameMode == IcsExamining && pausing)
15275       limit = pauseExamForwardMostMove;
15276     else
15277       limit = forwardMostMove;
15278
15279     if (target > limit) target = limit;
15280
15281     if (target > 0 && moveList[target - 1][0]) {
15282         int fromX, fromY, toX, toY;
15283         toX = moveList[target - 1][2] - AAA;
15284         toY = moveList[target - 1][3] - ONE;
15285         if (moveList[target - 1][1] == '@') {
15286             if (appData.highlightLastMove) {
15287                 SetHighlights(-1, -1, toX, toY);
15288             }
15289         } else {
15290             fromX = moveList[target - 1][0] - AAA;
15291             fromY = moveList[target - 1][1] - ONE;
15292             if (target == currentMove + 1) {
15293                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15294             }
15295             if (appData.highlightLastMove) {
15296                 SetHighlights(fromX, fromY, toX, toY);
15297             }
15298         }
15299     }
15300     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15301         gameMode == Training || gameMode == PlayFromGameFile ||
15302         gameMode == AnalyzeFile) {
15303         while (currentMove < target) {
15304             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15305             SendMoveToProgram(currentMove++, &first);
15306         }
15307     } else {
15308         currentMove = target;
15309     }
15310
15311     if (gameMode == EditGame || gameMode == EndOfGame) {
15312         whiteTimeRemaining = timeRemaining[0][currentMove];
15313         blackTimeRemaining = timeRemaining[1][currentMove];
15314     }
15315     DisplayBothClocks();
15316     DisplayMove(currentMove - 1);
15317     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15318     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15319     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15320         DisplayComment(currentMove - 1, commentList[currentMove]);
15321     }
15322     ClearMap(); // [HGM] exclude: invalidate map
15323 }
15324
15325
15326 void
15327 ForwardEvent ()
15328 {
15329     if (gameMode == IcsExamining && !pausing) {
15330         SendToICS(ics_prefix);
15331         SendToICS("forward\n");
15332     } else {
15333         ForwardInner(currentMove + 1);
15334     }
15335 }
15336
15337 void
15338 ToEndEvent ()
15339 {
15340     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15341         /* to optimze, we temporarily turn off analysis mode while we feed
15342          * the remaining moves to the engine. Otherwise we get analysis output
15343          * after each move.
15344          */
15345         if (first.analysisSupport) {
15346           SendToProgram("exit\nforce\n", &first);
15347           first.analyzing = FALSE;
15348         }
15349     }
15350
15351     if (gameMode == IcsExamining && !pausing) {
15352         SendToICS(ics_prefix);
15353         SendToICS("forward 999999\n");
15354     } else {
15355         ForwardInner(forwardMostMove);
15356     }
15357
15358     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15359         /* we have fed all the moves, so reactivate analysis mode */
15360         SendToProgram("analyze\n", &first);
15361         first.analyzing = TRUE;
15362         /*first.maybeThinking = TRUE;*/
15363         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15364     }
15365 }
15366
15367 void
15368 BackwardInner (int target)
15369 {
15370     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15371
15372     if (appData.debugMode)
15373         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15374                 target, currentMove, forwardMostMove);
15375
15376     if (gameMode == EditPosition) return;
15377     seekGraphUp = FALSE;
15378     MarkTargetSquares(1);
15379     if (currentMove <= backwardMostMove) {
15380         ClearHighlights();
15381         DrawPosition(full_redraw, boards[currentMove]);
15382         return;
15383     }
15384     if (gameMode == PlayFromGameFile && !pausing)
15385       PauseEvent();
15386
15387     if (moveList[target][0]) {
15388         int fromX, fromY, toX, toY;
15389         toX = moveList[target][2] - AAA;
15390         toY = moveList[target][3] - ONE;
15391         if (moveList[target][1] == '@') {
15392             if (appData.highlightLastMove) {
15393                 SetHighlights(-1, -1, toX, toY);
15394             }
15395         } else {
15396             fromX = moveList[target][0] - AAA;
15397             fromY = moveList[target][1] - ONE;
15398             if (target == currentMove - 1) {
15399                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15400             }
15401             if (appData.highlightLastMove) {
15402                 SetHighlights(fromX, fromY, toX, toY);
15403             }
15404         }
15405     }
15406     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15407         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15408         while (currentMove > target) {
15409             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15410                 // null move cannot be undone. Reload program with move history before it.
15411                 int i;
15412                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15413                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15414                 }
15415                 SendBoard(&first, i);
15416               if(second.analyzing) SendBoard(&second, i);
15417                 for(currentMove=i; currentMove<target; currentMove++) {
15418                     SendMoveToProgram(currentMove, &first);
15419                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15420                 }
15421                 break;
15422             }
15423             SendToBoth("undo\n");
15424             currentMove--;
15425         }
15426     } else {
15427         currentMove = target;
15428     }
15429
15430     if (gameMode == EditGame || gameMode == EndOfGame) {
15431         whiteTimeRemaining = timeRemaining[0][currentMove];
15432         blackTimeRemaining = timeRemaining[1][currentMove];
15433     }
15434     DisplayBothClocks();
15435     DisplayMove(currentMove - 1);
15436     DrawPosition(full_redraw, boards[currentMove]);
15437     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15438     // [HGM] PV info: routine tests if comment empty
15439     DisplayComment(currentMove - 1, commentList[currentMove]);
15440     ClearMap(); // [HGM] exclude: invalidate map
15441 }
15442
15443 void
15444 BackwardEvent ()
15445 {
15446     if (gameMode == IcsExamining && !pausing) {
15447         SendToICS(ics_prefix);
15448         SendToICS("backward\n");
15449     } else {
15450         BackwardInner(currentMove - 1);
15451     }
15452 }
15453
15454 void
15455 ToStartEvent ()
15456 {
15457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15458         /* to optimize, we temporarily turn off analysis mode while we undo
15459          * all the moves. Otherwise we get analysis output after each undo.
15460          */
15461         if (first.analysisSupport) {
15462           SendToProgram("exit\nforce\n", &first);
15463           first.analyzing = FALSE;
15464         }
15465     }
15466
15467     if (gameMode == IcsExamining && !pausing) {
15468         SendToICS(ics_prefix);
15469         SendToICS("backward 999999\n");
15470     } else {
15471         BackwardInner(backwardMostMove);
15472     }
15473
15474     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15475         /* we have fed all the moves, so reactivate analysis mode */
15476         SendToProgram("analyze\n", &first);
15477         first.analyzing = TRUE;
15478         /*first.maybeThinking = TRUE;*/
15479         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15480     }
15481 }
15482
15483 void
15484 ToNrEvent (int to)
15485 {
15486   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15487   if (to >= forwardMostMove) to = forwardMostMove;
15488   if (to <= backwardMostMove) to = backwardMostMove;
15489   if (to < currentMove) {
15490     BackwardInner(to);
15491   } else {
15492     ForwardInner(to);
15493   }
15494 }
15495
15496 void
15497 RevertEvent (Boolean annotate)
15498 {
15499     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15500         return;
15501     }
15502     if (gameMode != IcsExamining) {
15503         DisplayError(_("You are not examining a game"), 0);
15504         return;
15505     }
15506     if (pausing) {
15507         DisplayError(_("You can't revert while pausing"), 0);
15508         return;
15509     }
15510     SendToICS(ics_prefix);
15511     SendToICS("revert\n");
15512 }
15513
15514 void
15515 RetractMoveEvent ()
15516 {
15517     switch (gameMode) {
15518       case MachinePlaysWhite:
15519       case MachinePlaysBlack:
15520         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15521             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15522             return;
15523         }
15524         if (forwardMostMove < 2) return;
15525         currentMove = forwardMostMove = forwardMostMove - 2;
15526         whiteTimeRemaining = timeRemaining[0][currentMove];
15527         blackTimeRemaining = timeRemaining[1][currentMove];
15528         DisplayBothClocks();
15529         DisplayMove(currentMove - 1);
15530         ClearHighlights();/*!! could figure this out*/
15531         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15532         SendToProgram("remove\n", &first);
15533         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15534         break;
15535
15536       case BeginningOfGame:
15537       default:
15538         break;
15539
15540       case IcsPlayingWhite:
15541       case IcsPlayingBlack:
15542         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15543             SendToICS(ics_prefix);
15544             SendToICS("takeback 2\n");
15545         } else {
15546             SendToICS(ics_prefix);
15547             SendToICS("takeback 1\n");
15548         }
15549         break;
15550     }
15551 }
15552
15553 void
15554 MoveNowEvent ()
15555 {
15556     ChessProgramState *cps;
15557
15558     switch (gameMode) {
15559       case MachinePlaysWhite:
15560         if (!WhiteOnMove(forwardMostMove)) {
15561             DisplayError(_("It is your turn"), 0);
15562             return;
15563         }
15564         cps = &first;
15565         break;
15566       case MachinePlaysBlack:
15567         if (WhiteOnMove(forwardMostMove)) {
15568             DisplayError(_("It is your turn"), 0);
15569             return;
15570         }
15571         cps = &first;
15572         break;
15573       case TwoMachinesPlay:
15574         if (WhiteOnMove(forwardMostMove) ==
15575             (first.twoMachinesColor[0] == 'w')) {
15576             cps = &first;
15577         } else {
15578             cps = &second;
15579         }
15580         break;
15581       case BeginningOfGame:
15582       default:
15583         return;
15584     }
15585     SendToProgram("?\n", cps);
15586 }
15587
15588 void
15589 TruncateGameEvent ()
15590 {
15591     EditGameEvent();
15592     if (gameMode != EditGame) return;
15593     TruncateGame();
15594 }
15595
15596 void
15597 TruncateGame ()
15598 {
15599     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15600     if (forwardMostMove > currentMove) {
15601         if (gameInfo.resultDetails != NULL) {
15602             free(gameInfo.resultDetails);
15603             gameInfo.resultDetails = NULL;
15604             gameInfo.result = GameUnfinished;
15605         }
15606         forwardMostMove = currentMove;
15607         HistorySet(parseList, backwardMostMove, forwardMostMove,
15608                    currentMove-1);
15609     }
15610 }
15611
15612 void
15613 HintEvent ()
15614 {
15615     if (appData.noChessProgram) return;
15616     switch (gameMode) {
15617       case MachinePlaysWhite:
15618         if (WhiteOnMove(forwardMostMove)) {
15619             DisplayError(_("Wait until your turn."), 0);
15620             return;
15621         }
15622         break;
15623       case BeginningOfGame:
15624       case MachinePlaysBlack:
15625         if (!WhiteOnMove(forwardMostMove)) {
15626             DisplayError(_("Wait until your turn."), 0);
15627             return;
15628         }
15629         break;
15630       default:
15631         DisplayError(_("No hint available"), 0);
15632         return;
15633     }
15634     SendToProgram("hint\n", &first);
15635     hintRequested = TRUE;
15636 }
15637
15638 void
15639 CreateBookEvent ()
15640 {
15641     ListGame * lg = (ListGame *) gameList.head;
15642     FILE *f, *g;
15643     int nItem;
15644     static int secondTime = FALSE;
15645
15646     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15647         DisplayError(_("Game list not loaded or empty"), 0);
15648         return;
15649     }
15650
15651     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15652         fclose(g);
15653         secondTime++;
15654         DisplayNote(_("Book file exists! Try again for overwrite."));
15655         return;
15656     }
15657
15658     creatingBook = TRUE;
15659     secondTime = FALSE;
15660
15661     /* Get list size */
15662     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15663         LoadGame(f, nItem, "", TRUE);
15664         AddGameToBook(TRUE);
15665         lg = (ListGame *) lg->node.succ;
15666     }
15667
15668     creatingBook = FALSE;
15669     FlushBook();
15670 }
15671
15672 void
15673 BookEvent ()
15674 {
15675     if (appData.noChessProgram) return;
15676     switch (gameMode) {
15677       case MachinePlaysWhite:
15678         if (WhiteOnMove(forwardMostMove)) {
15679             DisplayError(_("Wait until your turn."), 0);
15680             return;
15681         }
15682         break;
15683       case BeginningOfGame:
15684       case MachinePlaysBlack:
15685         if (!WhiteOnMove(forwardMostMove)) {
15686             DisplayError(_("Wait until your turn."), 0);
15687             return;
15688         }
15689         break;
15690       case EditPosition:
15691         EditPositionDone(TRUE);
15692         break;
15693       case TwoMachinesPlay:
15694         return;
15695       default:
15696         break;
15697     }
15698     SendToProgram("bk\n", &first);
15699     bookOutput[0] = NULLCHAR;
15700     bookRequested = TRUE;
15701 }
15702
15703 void
15704 AboutGameEvent ()
15705 {
15706     char *tags = PGNTags(&gameInfo);
15707     TagsPopUp(tags, CmailMsg());
15708     free(tags);
15709 }
15710
15711 /* end button procedures */
15712
15713 void
15714 PrintPosition (FILE *fp, int move)
15715 {
15716     int i, j;
15717
15718     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15719         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15720             char c = PieceToChar(boards[move][i][j]);
15721             fputc(c == 'x' ? '.' : c, fp);
15722             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15723         }
15724     }
15725     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15726       fprintf(fp, "white to play\n");
15727     else
15728       fprintf(fp, "black to play\n");
15729 }
15730
15731 void
15732 PrintOpponents (FILE *fp)
15733 {
15734     if (gameInfo.white != NULL) {
15735         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15736     } else {
15737         fprintf(fp, "\n");
15738     }
15739 }
15740
15741 /* Find last component of program's own name, using some heuristics */
15742 void
15743 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15744 {
15745     char *p, *q, c;
15746     int local = (strcmp(host, "localhost") == 0);
15747     while (!local && (p = strchr(prog, ';')) != NULL) {
15748         p++;
15749         while (*p == ' ') p++;
15750         prog = p;
15751     }
15752     if (*prog == '"' || *prog == '\'') {
15753         q = strchr(prog + 1, *prog);
15754     } else {
15755         q = strchr(prog, ' ');
15756     }
15757     if (q == NULL) q = prog + strlen(prog);
15758     p = q;
15759     while (p >= prog && *p != '/' && *p != '\\') p--;
15760     p++;
15761     if(p == prog && *p == '"') p++;
15762     c = *q; *q = 0;
15763     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15764     memcpy(buf, p, q - p);
15765     buf[q - p] = NULLCHAR;
15766     if (!local) {
15767         strcat(buf, "@");
15768         strcat(buf, host);
15769     }
15770 }
15771
15772 char *
15773 TimeControlTagValue ()
15774 {
15775     char buf[MSG_SIZ];
15776     if (!appData.clockMode) {
15777       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15778     } else if (movesPerSession > 0) {
15779       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15780     } else if (timeIncrement == 0) {
15781       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15782     } else {
15783       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15784     }
15785     return StrSave(buf);
15786 }
15787
15788 void
15789 SetGameInfo ()
15790 {
15791     /* This routine is used only for certain modes */
15792     VariantClass v = gameInfo.variant;
15793     ChessMove r = GameUnfinished;
15794     char *p = NULL;
15795
15796     if(keepInfo) return;
15797
15798     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15799         r = gameInfo.result;
15800         p = gameInfo.resultDetails;
15801         gameInfo.resultDetails = NULL;
15802     }
15803     ClearGameInfo(&gameInfo);
15804     gameInfo.variant = v;
15805
15806     switch (gameMode) {
15807       case MachinePlaysWhite:
15808         gameInfo.event = StrSave( appData.pgnEventHeader );
15809         gameInfo.site = StrSave(HostName());
15810         gameInfo.date = PGNDate();
15811         gameInfo.round = StrSave("-");
15812         gameInfo.white = StrSave(first.tidy);
15813         gameInfo.black = StrSave(UserName());
15814         gameInfo.timeControl = TimeControlTagValue();
15815         break;
15816
15817       case MachinePlaysBlack:
15818         gameInfo.event = StrSave( appData.pgnEventHeader );
15819         gameInfo.site = StrSave(HostName());
15820         gameInfo.date = PGNDate();
15821         gameInfo.round = StrSave("-");
15822         gameInfo.white = StrSave(UserName());
15823         gameInfo.black = StrSave(first.tidy);
15824         gameInfo.timeControl = TimeControlTagValue();
15825         break;
15826
15827       case TwoMachinesPlay:
15828         gameInfo.event = StrSave( appData.pgnEventHeader );
15829         gameInfo.site = StrSave(HostName());
15830         gameInfo.date = PGNDate();
15831         if (roundNr > 0) {
15832             char buf[MSG_SIZ];
15833             snprintf(buf, MSG_SIZ, "%d", roundNr);
15834             gameInfo.round = StrSave(buf);
15835         } else {
15836             gameInfo.round = StrSave("-");
15837         }
15838         if (first.twoMachinesColor[0] == 'w') {
15839             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15840             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15841         } else {
15842             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15843             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15844         }
15845         gameInfo.timeControl = TimeControlTagValue();
15846         break;
15847
15848       case EditGame:
15849         gameInfo.event = StrSave("Edited game");
15850         gameInfo.site = StrSave(HostName());
15851         gameInfo.date = PGNDate();
15852         gameInfo.round = StrSave("-");
15853         gameInfo.white = StrSave("-");
15854         gameInfo.black = StrSave("-");
15855         gameInfo.result = r;
15856         gameInfo.resultDetails = p;
15857         break;
15858
15859       case EditPosition:
15860         gameInfo.event = StrSave("Edited position");
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       case IcsPlayingWhite:
15869       case IcsPlayingBlack:
15870       case IcsObserving:
15871       case IcsExamining:
15872         break;
15873
15874       case PlayFromGameFile:
15875         gameInfo.event = StrSave("Game from non-PGN file");
15876         gameInfo.site = StrSave(HostName());
15877         gameInfo.date = PGNDate();
15878         gameInfo.round = StrSave("-");
15879         gameInfo.white = StrSave("?");
15880         gameInfo.black = StrSave("?");
15881         break;
15882
15883       default:
15884         break;
15885     }
15886 }
15887
15888 void
15889 ReplaceComment (int index, char *text)
15890 {
15891     int len;
15892     char *p;
15893     float score;
15894
15895     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15896        pvInfoList[index-1].depth == len &&
15897        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15898        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15899     while (*text == '\n') text++;
15900     len = strlen(text);
15901     while (len > 0 && text[len - 1] == '\n') len--;
15902
15903     if (commentList[index] != NULL)
15904       free(commentList[index]);
15905
15906     if (len == 0) {
15907         commentList[index] = NULL;
15908         return;
15909     }
15910   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15911       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15912       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15913     commentList[index] = (char *) malloc(len + 2);
15914     strncpy(commentList[index], text, len);
15915     commentList[index][len] = '\n';
15916     commentList[index][len + 1] = NULLCHAR;
15917   } else {
15918     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15919     char *p;
15920     commentList[index] = (char *) malloc(len + 7);
15921     safeStrCpy(commentList[index], "{\n", 3);
15922     safeStrCpy(commentList[index]+2, text, len+1);
15923     commentList[index][len+2] = NULLCHAR;
15924     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15925     strcat(commentList[index], "\n}\n");
15926   }
15927 }
15928
15929 void
15930 CrushCRs (char *text)
15931 {
15932   char *p = text;
15933   char *q = text;
15934   char ch;
15935
15936   do {
15937     ch = *p++;
15938     if (ch == '\r') continue;
15939     *q++ = ch;
15940   } while (ch != '\0');
15941 }
15942
15943 void
15944 AppendComment (int index, char *text, Boolean addBraces)
15945 /* addBraces  tells if we should add {} */
15946 {
15947     int oldlen, len;
15948     char *old;
15949
15950 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15951     if(addBraces == 3) addBraces = 0; else // force appending literally
15952     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15953
15954     CrushCRs(text);
15955     while (*text == '\n') text++;
15956     len = strlen(text);
15957     while (len > 0 && text[len - 1] == '\n') len--;
15958     text[len] = NULLCHAR;
15959
15960     if (len == 0) return;
15961
15962     if (commentList[index] != NULL) {
15963       Boolean addClosingBrace = addBraces;
15964         old = commentList[index];
15965         oldlen = strlen(old);
15966         while(commentList[index][oldlen-1] ==  '\n')
15967           commentList[index][--oldlen] = NULLCHAR;
15968         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15969         safeStrCpy(commentList[index], old, oldlen + len + 6);
15970         free(old);
15971         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15972         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15973           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15974           while (*text == '\n') { text++; len--; }
15975           commentList[index][--oldlen] = NULLCHAR;
15976       }
15977         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15978         else          strcat(commentList[index], "\n");
15979         strcat(commentList[index], text);
15980         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15981         else          strcat(commentList[index], "\n");
15982     } else {
15983         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15984         if(addBraces)
15985           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15986         else commentList[index][0] = NULLCHAR;
15987         strcat(commentList[index], text);
15988         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15989         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15990     }
15991 }
15992
15993 static char *
15994 FindStr (char * text, char * sub_text)
15995 {
15996     char * result = strstr( text, sub_text );
15997
15998     if( result != NULL ) {
15999         result += strlen( sub_text );
16000     }
16001
16002     return result;
16003 }
16004
16005 /* [AS] Try to extract PV info from PGN comment */
16006 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16007 char *
16008 GetInfoFromComment (int index, char * text)
16009 {
16010     char * sep = text, *p;
16011
16012     if( text != NULL && index > 0 ) {
16013         int score = 0;
16014         int depth = 0;
16015         int time = -1, sec = 0, deci;
16016         char * s_eval = FindStr( text, "[%eval " );
16017         char * s_emt = FindStr( text, "[%emt " );
16018 #if 0
16019         if( s_eval != NULL || s_emt != NULL ) {
16020 #else
16021         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16022 #endif
16023             /* New style */
16024             char delim;
16025
16026             if( s_eval != NULL ) {
16027                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16028                     return text;
16029                 }
16030
16031                 if( delim != ']' ) {
16032                     return text;
16033                 }
16034             }
16035
16036             if( s_emt != NULL ) {
16037             }
16038                 return text;
16039         }
16040         else {
16041             /* We expect something like: [+|-]nnn.nn/dd */
16042             int score_lo = 0;
16043
16044             if(*text != '{') return text; // [HGM] braces: must be normal comment
16045
16046             sep = strchr( text, '/' );
16047             if( sep == NULL || sep < (text+4) ) {
16048                 return text;
16049             }
16050
16051             p = text;
16052             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16053             if(p[1] == '(') { // comment starts with PV
16054                p = strchr(p, ')'); // locate end of PV
16055                if(p == NULL || sep < p+5) return text;
16056                // at this point we have something like "{(.*) +0.23/6 ..."
16057                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16058                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16059                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16060             }
16061             time = -1; sec = -1; deci = -1;
16062             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16063                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16064                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16065                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16066                 return text;
16067             }
16068
16069             if( score_lo < 0 || score_lo >= 100 ) {
16070                 return text;
16071             }
16072
16073             if(sec >= 0) time = 600*time + 10*sec; else
16074             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16075
16076             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16077
16078             /* [HGM] PV time: now locate end of PV info */
16079             while( *++sep >= '0' && *sep <= '9'); // strip depth
16080             if(time >= 0)
16081             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16082             if(sec >= 0)
16083             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16084             if(deci >= 0)
16085             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16086             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16087         }
16088
16089         if( depth <= 0 ) {
16090             return text;
16091         }
16092
16093         if( time < 0 ) {
16094             time = -1;
16095         }
16096
16097         pvInfoList[index-1].depth = depth;
16098         pvInfoList[index-1].score = score;
16099         pvInfoList[index-1].time  = 10*time; // centi-sec
16100         if(*sep == '}') *sep = 0; else *--sep = '{';
16101         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16102     }
16103     return sep;
16104 }
16105
16106 void
16107 SendToProgram (char *message, ChessProgramState *cps)
16108 {
16109     int count, outCount, error;
16110     char buf[MSG_SIZ];
16111
16112     if (cps->pr == NoProc) return;
16113     Attention(cps);
16114
16115     if (appData.debugMode) {
16116         TimeMark now;
16117         GetTimeMark(&now);
16118         fprintf(debugFP, "%ld >%-6s: %s",
16119                 SubtractTimeMarks(&now, &programStartTime),
16120                 cps->which, message);
16121         if(serverFP)
16122             fprintf(serverFP, "%ld >%-6s: %s",
16123                 SubtractTimeMarks(&now, &programStartTime),
16124                 cps->which, message), fflush(serverFP);
16125     }
16126
16127     count = strlen(message);
16128     outCount = OutputToProcess(cps->pr, message, count, &error);
16129     if (outCount < count && !exiting
16130                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16131       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16132       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16133         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16134             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16135                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16136                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16137                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16138             } else {
16139                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16140                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16141                 gameInfo.result = res;
16142             }
16143             gameInfo.resultDetails = StrSave(buf);
16144         }
16145         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16146         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16147     }
16148 }
16149
16150 void
16151 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16152 {
16153     char *end_str;
16154     char buf[MSG_SIZ];
16155     ChessProgramState *cps = (ChessProgramState *)closure;
16156
16157     if (isr != cps->isr) return; /* Killed intentionally */
16158     if (count <= 0) {
16159         if (count == 0) {
16160             RemoveInputSource(cps->isr);
16161             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16162                     _(cps->which), cps->program);
16163             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16164             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16165                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16166                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16167                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16168                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16169                 } else {
16170                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16171                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16172                     gameInfo.result = res;
16173                 }
16174                 gameInfo.resultDetails = StrSave(buf);
16175             }
16176             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16177             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16178         } else {
16179             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16180                     _(cps->which), cps->program);
16181             RemoveInputSource(cps->isr);
16182
16183             /* [AS] Program is misbehaving badly... kill it */
16184             if( count == -2 ) {
16185                 DestroyChildProcess( cps->pr, 9 );
16186                 cps->pr = NoProc;
16187             }
16188
16189             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16190         }
16191         return;
16192     }
16193
16194     if ((end_str = strchr(message, '\r')) != NULL)
16195       *end_str = NULLCHAR;
16196     if ((end_str = strchr(message, '\n')) != NULL)
16197       *end_str = NULLCHAR;
16198
16199     if (appData.debugMode) {
16200         TimeMark now; int print = 1;
16201         char *quote = ""; char c; int i;
16202
16203         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16204                 char start = message[0];
16205                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16206                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16207                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16208                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16209                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16210                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16211                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16212                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16213                    sscanf(message, "hint: %c", &c)!=1 &&
16214                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16215                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16216                     print = (appData.engineComments >= 2);
16217                 }
16218                 message[0] = start; // restore original message
16219         }
16220         if(print) {
16221                 GetTimeMark(&now);
16222                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16223                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16224                         quote,
16225                         message);
16226                 if(serverFP)
16227                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16228                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16229                         quote,
16230                         message), fflush(serverFP);
16231         }
16232     }
16233
16234     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16235     if (appData.icsEngineAnalyze) {
16236         if (strstr(message, "whisper") != NULL ||
16237              strstr(message, "kibitz") != NULL ||
16238             strstr(message, "tellics") != NULL) return;
16239     }
16240
16241     HandleMachineMove(message, cps);
16242 }
16243
16244
16245 void
16246 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16247 {
16248     char buf[MSG_SIZ];
16249     int seconds;
16250
16251     if( timeControl_2 > 0 ) {
16252         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16253             tc = timeControl_2;
16254         }
16255     }
16256     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16257     inc /= cps->timeOdds;
16258     st  /= cps->timeOdds;
16259
16260     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16261
16262     if (st > 0) {
16263       /* Set exact time per move, normally using st command */
16264       if (cps->stKludge) {
16265         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16266         seconds = st % 60;
16267         if (seconds == 0) {
16268           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16269         } else {
16270           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16271         }
16272       } else {
16273         snprintf(buf, MSG_SIZ, "st %d\n", st);
16274       }
16275     } else {
16276       /* Set conventional or incremental time control, using level command */
16277       if (seconds == 0) {
16278         /* Note old gnuchess bug -- minutes:seconds used to not work.
16279            Fixed in later versions, but still avoid :seconds
16280            when seconds is 0. */
16281         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16282       } else {
16283         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16284                  seconds, inc/1000.);
16285       }
16286     }
16287     SendToProgram(buf, cps);
16288
16289     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16290     /* Orthogonally, limit search to given depth */
16291     if (sd > 0) {
16292       if (cps->sdKludge) {
16293         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16294       } else {
16295         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16296       }
16297       SendToProgram(buf, cps);
16298     }
16299
16300     if(cps->nps >= 0) { /* [HGM] nps */
16301         if(cps->supportsNPS == FALSE)
16302           cps->nps = -1; // don't use if engine explicitly says not supported!
16303         else {
16304           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16305           SendToProgram(buf, cps);
16306         }
16307     }
16308 }
16309
16310 ChessProgramState *
16311 WhitePlayer ()
16312 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16313 {
16314     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16315        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16316         return &second;
16317     return &first;
16318 }
16319
16320 void
16321 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16322 {
16323     char message[MSG_SIZ];
16324     long time, otime;
16325
16326     /* Note: this routine must be called when the clocks are stopped
16327        or when they have *just* been set or switched; otherwise
16328        it will be off by the time since the current tick started.
16329     */
16330     if (machineWhite) {
16331         time = whiteTimeRemaining / 10;
16332         otime = blackTimeRemaining / 10;
16333     } else {
16334         time = blackTimeRemaining / 10;
16335         otime = whiteTimeRemaining / 10;
16336     }
16337     /* [HGM] translate opponent's time by time-odds factor */
16338     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16339
16340     if (time <= 0) time = 1;
16341     if (otime <= 0) otime = 1;
16342
16343     snprintf(message, MSG_SIZ, "time %ld\n", time);
16344     SendToProgram(message, cps);
16345
16346     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16347     SendToProgram(message, cps);
16348 }
16349
16350 char *
16351 EngineDefinedVariant (ChessProgramState *cps, int n)
16352 {   // return name of n-th unknown variant that engine supports
16353     static char buf[MSG_SIZ];
16354     char *p, *s = cps->variants;
16355     if(!s) return NULL;
16356     do { // parse string from variants feature
16357       VariantClass v;
16358         p = strchr(s, ',');
16359         if(p) *p = NULLCHAR;
16360       v = StringToVariant(s);
16361       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16362         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16363             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16364         }
16365         if(p) *p++ = ',';
16366         if(n < 0) return buf;
16367     } while(s = p);
16368     return NULL;
16369 }
16370
16371 int
16372 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16373 {
16374   char buf[MSG_SIZ];
16375   int len = strlen(name);
16376   int val;
16377
16378   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16379     (*p) += len + 1;
16380     sscanf(*p, "%d", &val);
16381     *loc = (val != 0);
16382     while (**p && **p != ' ')
16383       (*p)++;
16384     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16385     SendToProgram(buf, cps);
16386     return TRUE;
16387   }
16388   return FALSE;
16389 }
16390
16391 int
16392 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16393 {
16394   char buf[MSG_SIZ];
16395   int len = strlen(name);
16396   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16397     (*p) += len + 1;
16398     sscanf(*p, "%d", loc);
16399     while (**p && **p != ' ') (*p)++;
16400     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16401     SendToProgram(buf, cps);
16402     return TRUE;
16403   }
16404   return FALSE;
16405 }
16406
16407 int
16408 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16409 {
16410   char buf[MSG_SIZ];
16411   int len = strlen(name);
16412   if (strncmp((*p), name, len) == 0
16413       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16414     (*p) += len + 2;
16415     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16416     sscanf(*p, "%[^\"]", *loc);
16417     while (**p && **p != '\"') (*p)++;
16418     if (**p == '\"') (*p)++;
16419     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16420     SendToProgram(buf, cps);
16421     return TRUE;
16422   }
16423   return FALSE;
16424 }
16425
16426 int
16427 ParseOption (Option *opt, ChessProgramState *cps)
16428 // [HGM] options: process the string that defines an engine option, and determine
16429 // name, type, default value, and allowed value range
16430 {
16431         char *p, *q, buf[MSG_SIZ];
16432         int n, min = (-1)<<31, max = 1<<31, def;
16433
16434         if(p = strstr(opt->name, " -spin ")) {
16435             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16436             if(max < min) max = min; // enforce consistency
16437             if(def < min) def = min;
16438             if(def > max) def = max;
16439             opt->value = def;
16440             opt->min = min;
16441             opt->max = max;
16442             opt->type = Spin;
16443         } else if((p = strstr(opt->name, " -slider "))) {
16444             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16445             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16446             if(max < min) max = min; // enforce consistency
16447             if(def < min) def = min;
16448             if(def > max) def = max;
16449             opt->value = def;
16450             opt->min = min;
16451             opt->max = max;
16452             opt->type = Spin; // Slider;
16453         } else if((p = strstr(opt->name, " -string "))) {
16454             opt->textValue = p+9;
16455             opt->type = TextBox;
16456         } else if((p = strstr(opt->name, " -file "))) {
16457             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16458             opt->textValue = p+7;
16459             opt->type = FileName; // FileName;
16460         } else if((p = strstr(opt->name, " -path "))) {
16461             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16462             opt->textValue = p+7;
16463             opt->type = PathName; // PathName;
16464         } else if(p = strstr(opt->name, " -check ")) {
16465             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16466             opt->value = (def != 0);
16467             opt->type = CheckBox;
16468         } else if(p = strstr(opt->name, " -combo ")) {
16469             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16470             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16471             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16472             opt->value = n = 0;
16473             while(q = StrStr(q, " /// ")) {
16474                 n++; *q = 0;    // count choices, and null-terminate each of them
16475                 q += 5;
16476                 if(*q == '*') { // remember default, which is marked with * prefix
16477                     q++;
16478                     opt->value = n;
16479                 }
16480                 cps->comboList[cps->comboCnt++] = q;
16481             }
16482             cps->comboList[cps->comboCnt++] = NULL;
16483             opt->max = n + 1;
16484             opt->type = ComboBox;
16485         } else if(p = strstr(opt->name, " -button")) {
16486             opt->type = Button;
16487         } else if(p = strstr(opt->name, " -save")) {
16488             opt->type = SaveButton;
16489         } else return FALSE;
16490         *p = 0; // terminate option name
16491         // now look if the command-line options define a setting for this engine option.
16492         if(cps->optionSettings && cps->optionSettings[0])
16493             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16494         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16495           snprintf(buf, MSG_SIZ, "option %s", p);
16496                 if(p = strstr(buf, ",")) *p = 0;
16497                 if(q = strchr(buf, '=')) switch(opt->type) {
16498                     case ComboBox:
16499                         for(n=0; n<opt->max; n++)
16500                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16501                         break;
16502                     case TextBox:
16503                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16504                         break;
16505                     case Spin:
16506                     case CheckBox:
16507                         opt->value = atoi(q+1);
16508                     default:
16509                         break;
16510                 }
16511                 strcat(buf, "\n");
16512                 SendToProgram(buf, cps);
16513         }
16514         return TRUE;
16515 }
16516
16517 void
16518 FeatureDone (ChessProgramState *cps, int val)
16519 {
16520   DelayedEventCallback cb = GetDelayedEvent();
16521   if ((cb == InitBackEnd3 && cps == &first) ||
16522       (cb == SettingsMenuIfReady && cps == &second) ||
16523       (cb == LoadEngine) ||
16524       (cb == TwoMachinesEventIfReady)) {
16525     CancelDelayedEvent();
16526     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16527   }
16528   cps->initDone = val;
16529   if(val) cps->reload = FALSE;
16530 }
16531
16532 /* Parse feature command from engine */
16533 void
16534 ParseFeatures (char *args, ChessProgramState *cps)
16535 {
16536   char *p = args;
16537   char *q = NULL;
16538   int val;
16539   char buf[MSG_SIZ];
16540
16541   for (;;) {
16542     while (*p == ' ') p++;
16543     if (*p == NULLCHAR) return;
16544
16545     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16546     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16547     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16548     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16549     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16550     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16551     if (BoolFeature(&p, "reuse", &val, cps)) {
16552       /* Engine can disable reuse, but can't enable it if user said no */
16553       if (!val) cps->reuse = FALSE;
16554       continue;
16555     }
16556     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16557     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16558       if (gameMode == TwoMachinesPlay) {
16559         DisplayTwoMachinesTitle();
16560       } else {
16561         DisplayTitle("");
16562       }
16563       continue;
16564     }
16565     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16566     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16567     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16568     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16569     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16570     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16571     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16572     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16573     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16574     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16575     if (IntFeature(&p, "done", &val, cps)) {
16576       FeatureDone(cps, val);
16577       continue;
16578     }
16579     /* Added by Tord: */
16580     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16581     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16582     /* End of additions by Tord */
16583
16584     /* [HGM] added features: */
16585     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16586     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16587     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16588     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16589     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16590     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16591     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16592     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16593         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16594         FREE(cps->option[cps->nrOptions].name);
16595         cps->option[cps->nrOptions].name = q; q = NULL;
16596         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16597           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16598             SendToProgram(buf, cps);
16599             continue;
16600         }
16601         if(cps->nrOptions >= MAX_OPTIONS) {
16602             cps->nrOptions--;
16603             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16604             DisplayError(buf, 0);
16605         }
16606         continue;
16607     }
16608     /* End of additions by HGM */
16609
16610     /* unknown feature: complain and skip */
16611     q = p;
16612     while (*q && *q != '=') q++;
16613     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16614     SendToProgram(buf, cps);
16615     p = q;
16616     if (*p == '=') {
16617       p++;
16618       if (*p == '\"') {
16619         p++;
16620         while (*p && *p != '\"') p++;
16621         if (*p == '\"') p++;
16622       } else {
16623         while (*p && *p != ' ') p++;
16624       }
16625     }
16626   }
16627
16628 }
16629
16630 void
16631 PeriodicUpdatesEvent (int newState)
16632 {
16633     if (newState == appData.periodicUpdates)
16634       return;
16635
16636     appData.periodicUpdates=newState;
16637
16638     /* Display type changes, so update it now */
16639 //    DisplayAnalysis();
16640
16641     /* Get the ball rolling again... */
16642     if (newState) {
16643         AnalysisPeriodicEvent(1);
16644         StartAnalysisClock();
16645     }
16646 }
16647
16648 void
16649 PonderNextMoveEvent (int newState)
16650 {
16651     if (newState == appData.ponderNextMove) return;
16652     if (gameMode == EditPosition) EditPositionDone(TRUE);
16653     if (newState) {
16654         SendToProgram("hard\n", &first);
16655         if (gameMode == TwoMachinesPlay) {
16656             SendToProgram("hard\n", &second);
16657         }
16658     } else {
16659         SendToProgram("easy\n", &first);
16660         thinkOutput[0] = NULLCHAR;
16661         if (gameMode == TwoMachinesPlay) {
16662             SendToProgram("easy\n", &second);
16663         }
16664     }
16665     appData.ponderNextMove = newState;
16666 }
16667
16668 void
16669 NewSettingEvent (int option, int *feature, char *command, int value)
16670 {
16671     char buf[MSG_SIZ];
16672
16673     if (gameMode == EditPosition) EditPositionDone(TRUE);
16674     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16675     if(feature == NULL || *feature) SendToProgram(buf, &first);
16676     if (gameMode == TwoMachinesPlay) {
16677         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16678     }
16679 }
16680
16681 void
16682 ShowThinkingEvent ()
16683 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16684 {
16685     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16686     int newState = appData.showThinking
16687         // [HGM] thinking: other features now need thinking output as well
16688         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16689
16690     if (oldState == newState) return;
16691     oldState = newState;
16692     if (gameMode == EditPosition) EditPositionDone(TRUE);
16693     if (oldState) {
16694         SendToProgram("post\n", &first);
16695         if (gameMode == TwoMachinesPlay) {
16696             SendToProgram("post\n", &second);
16697         }
16698     } else {
16699         SendToProgram("nopost\n", &first);
16700         thinkOutput[0] = NULLCHAR;
16701         if (gameMode == TwoMachinesPlay) {
16702             SendToProgram("nopost\n", &second);
16703         }
16704     }
16705 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16706 }
16707
16708 void
16709 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16710 {
16711   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16712   if (pr == NoProc) return;
16713   AskQuestion(title, question, replyPrefix, pr);
16714 }
16715
16716 void
16717 TypeInEvent (char firstChar)
16718 {
16719     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16720         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16721         gameMode == AnalyzeMode || gameMode == EditGame ||
16722         gameMode == EditPosition || gameMode == IcsExamining ||
16723         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16724         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16725                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16726                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16727         gameMode == Training) PopUpMoveDialog(firstChar);
16728 }
16729
16730 void
16731 TypeInDoneEvent (char *move)
16732 {
16733         Board board;
16734         int n, fromX, fromY, toX, toY;
16735         char promoChar;
16736         ChessMove moveType;
16737
16738         // [HGM] FENedit
16739         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16740                 EditPositionPasteFEN(move);
16741                 return;
16742         }
16743         // [HGM] movenum: allow move number to be typed in any mode
16744         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16745           ToNrEvent(2*n-1);
16746           return;
16747         }
16748         // undocumented kludge: allow command-line option to be typed in!
16749         // (potentially fatal, and does not implement the effect of the option.)
16750         // should only be used for options that are values on which future decisions will be made,
16751         // and definitely not on options that would be used during initialization.
16752         if(strstr(move, "!!! -") == move) {
16753             ParseArgsFromString(move+4);
16754             return;
16755         }
16756
16757       if (gameMode != EditGame && currentMove != forwardMostMove &&
16758         gameMode != Training) {
16759         DisplayMoveError(_("Displayed move is not current"));
16760       } else {
16761         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16762           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16763         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16764         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16765           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16766           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16767         } else {
16768           DisplayMoveError(_("Could not parse move"));
16769         }
16770       }
16771 }
16772
16773 void
16774 DisplayMove (int moveNumber)
16775 {
16776     char message[MSG_SIZ];
16777     char res[MSG_SIZ];
16778     char cpThinkOutput[MSG_SIZ];
16779
16780     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16781
16782     if (moveNumber == forwardMostMove - 1 ||
16783         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16784
16785         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16786
16787         if (strchr(cpThinkOutput, '\n')) {
16788             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16789         }
16790     } else {
16791         *cpThinkOutput = NULLCHAR;
16792     }
16793
16794     /* [AS] Hide thinking from human user */
16795     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16796         *cpThinkOutput = NULLCHAR;
16797         if( thinkOutput[0] != NULLCHAR ) {
16798             int i;
16799
16800             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16801                 cpThinkOutput[i] = '.';
16802             }
16803             cpThinkOutput[i] = NULLCHAR;
16804             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16805         }
16806     }
16807
16808     if (moveNumber == forwardMostMove - 1 &&
16809         gameInfo.resultDetails != NULL) {
16810         if (gameInfo.resultDetails[0] == NULLCHAR) {
16811           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16812         } else {
16813           snprintf(res, MSG_SIZ, " {%s} %s",
16814                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16815         }
16816     } else {
16817         res[0] = NULLCHAR;
16818     }
16819
16820     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16821         DisplayMessage(res, cpThinkOutput);
16822     } else {
16823       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16824                 WhiteOnMove(moveNumber) ? " " : ".. ",
16825                 parseList[moveNumber], res);
16826         DisplayMessage(message, cpThinkOutput);
16827     }
16828 }
16829
16830 void
16831 DisplayComment (int moveNumber, char *text)
16832 {
16833     char title[MSG_SIZ];
16834
16835     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16836       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16837     } else {
16838       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16839               WhiteOnMove(moveNumber) ? " " : ".. ",
16840               parseList[moveNumber]);
16841     }
16842     if (text != NULL && (appData.autoDisplayComment || commentUp))
16843         CommentPopUp(title, text);
16844 }
16845
16846 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16847  * might be busy thinking or pondering.  It can be omitted if your
16848  * gnuchess is configured to stop thinking immediately on any user
16849  * input.  However, that gnuchess feature depends on the FIONREAD
16850  * ioctl, which does not work properly on some flavors of Unix.
16851  */
16852 void
16853 Attention (ChessProgramState *cps)
16854 {
16855 #if ATTENTION
16856     if (!cps->useSigint) return;
16857     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16858     switch (gameMode) {
16859       case MachinePlaysWhite:
16860       case MachinePlaysBlack:
16861       case TwoMachinesPlay:
16862       case IcsPlayingWhite:
16863       case IcsPlayingBlack:
16864       case AnalyzeMode:
16865       case AnalyzeFile:
16866         /* Skip if we know it isn't thinking */
16867         if (!cps->maybeThinking) return;
16868         if (appData.debugMode)
16869           fprintf(debugFP, "Interrupting %s\n", cps->which);
16870         InterruptChildProcess(cps->pr);
16871         cps->maybeThinking = FALSE;
16872         break;
16873       default:
16874         break;
16875     }
16876 #endif /*ATTENTION*/
16877 }
16878
16879 int
16880 CheckFlags ()
16881 {
16882     if (whiteTimeRemaining <= 0) {
16883         if (!whiteFlag) {
16884             whiteFlag = TRUE;
16885             if (appData.icsActive) {
16886                 if (appData.autoCallFlag &&
16887                     gameMode == IcsPlayingBlack && !blackFlag) {
16888                   SendToICS(ics_prefix);
16889                   SendToICS("flag\n");
16890                 }
16891             } else {
16892                 if (blackFlag) {
16893                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16894                 } else {
16895                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16896                     if (appData.autoCallFlag) {
16897                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16898                         return TRUE;
16899                     }
16900                 }
16901             }
16902         }
16903     }
16904     if (blackTimeRemaining <= 0) {
16905         if (!blackFlag) {
16906             blackFlag = TRUE;
16907             if (appData.icsActive) {
16908                 if (appData.autoCallFlag &&
16909                     gameMode == IcsPlayingWhite && !whiteFlag) {
16910                   SendToICS(ics_prefix);
16911                   SendToICS("flag\n");
16912                 }
16913             } else {
16914                 if (whiteFlag) {
16915                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16916                 } else {
16917                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16918                     if (appData.autoCallFlag) {
16919                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16920                         return TRUE;
16921                     }
16922                 }
16923             }
16924         }
16925     }
16926     return FALSE;
16927 }
16928
16929 void
16930 CheckTimeControl ()
16931 {
16932     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16933         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16934
16935     /*
16936      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16937      */
16938     if ( !WhiteOnMove(forwardMostMove) ) {
16939         /* White made time control */
16940         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16941         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16942         /* [HGM] time odds: correct new time quota for time odds! */
16943                                             / WhitePlayer()->timeOdds;
16944         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16945     } else {
16946         lastBlack -= blackTimeRemaining;
16947         /* Black made time control */
16948         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16949                                             / WhitePlayer()->other->timeOdds;
16950         lastWhite = whiteTimeRemaining;
16951     }
16952 }
16953
16954 void
16955 DisplayBothClocks ()
16956 {
16957     int wom = gameMode == EditPosition ?
16958       !blackPlaysFirst : WhiteOnMove(currentMove);
16959     DisplayWhiteClock(whiteTimeRemaining, wom);
16960     DisplayBlackClock(blackTimeRemaining, !wom);
16961 }
16962
16963
16964 /* Timekeeping seems to be a portability nightmare.  I think everyone
16965    has ftime(), but I'm really not sure, so I'm including some ifdefs
16966    to use other calls if you don't.  Clocks will be less accurate if
16967    you have neither ftime nor gettimeofday.
16968 */
16969
16970 /* VS 2008 requires the #include outside of the function */
16971 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16972 #include <sys/timeb.h>
16973 #endif
16974
16975 /* Get the current time as a TimeMark */
16976 void
16977 GetTimeMark (TimeMark *tm)
16978 {
16979 #if HAVE_GETTIMEOFDAY
16980
16981     struct timeval timeVal;
16982     struct timezone timeZone;
16983
16984     gettimeofday(&timeVal, &timeZone);
16985     tm->sec = (long) timeVal.tv_sec;
16986     tm->ms = (int) (timeVal.tv_usec / 1000L);
16987
16988 #else /*!HAVE_GETTIMEOFDAY*/
16989 #if HAVE_FTIME
16990
16991 // include <sys/timeb.h> / moved to just above start of function
16992     struct timeb timeB;
16993
16994     ftime(&timeB);
16995     tm->sec = (long) timeB.time;
16996     tm->ms = (int) timeB.millitm;
16997
16998 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16999     tm->sec = (long) time(NULL);
17000     tm->ms = 0;
17001 #endif
17002 #endif
17003 }
17004
17005 /* Return the difference in milliseconds between two
17006    time marks.  We assume the difference will fit in a long!
17007 */
17008 long
17009 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17010 {
17011     return 1000L*(tm2->sec - tm1->sec) +
17012            (long) (tm2->ms - tm1->ms);
17013 }
17014
17015
17016 /*
17017  * Code to manage the game clocks.
17018  *
17019  * In tournament play, black starts the clock and then white makes a move.
17020  * We give the human user a slight advantage if he is playing white---the
17021  * clocks don't run until he makes his first move, so it takes zero time.
17022  * Also, we don't account for network lag, so we could get out of sync
17023  * with GNU Chess's clock -- but then, referees are always right.
17024  */
17025
17026 static TimeMark tickStartTM;
17027 static long intendedTickLength;
17028
17029 long
17030 NextTickLength (long timeRemaining)
17031 {
17032     long nominalTickLength, nextTickLength;
17033
17034     if (timeRemaining > 0L && timeRemaining <= 10000L)
17035       nominalTickLength = 100L;
17036     else
17037       nominalTickLength = 1000L;
17038     nextTickLength = timeRemaining % nominalTickLength;
17039     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17040
17041     return nextTickLength;
17042 }
17043
17044 /* Adjust clock one minute up or down */
17045 void
17046 AdjustClock (Boolean which, int dir)
17047 {
17048     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17049     if(which) blackTimeRemaining += 60000*dir;
17050     else      whiteTimeRemaining += 60000*dir;
17051     DisplayBothClocks();
17052     adjustedClock = TRUE;
17053 }
17054
17055 /* Stop clocks and reset to a fresh time control */
17056 void
17057 ResetClocks ()
17058 {
17059     (void) StopClockTimer();
17060     if (appData.icsActive) {
17061         whiteTimeRemaining = blackTimeRemaining = 0;
17062     } else if (searchTime) {
17063         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17064         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17065     } else { /* [HGM] correct new time quote for time odds */
17066         whiteTC = blackTC = fullTimeControlString;
17067         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17068         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17069     }
17070     if (whiteFlag || blackFlag) {
17071         DisplayTitle("");
17072         whiteFlag = blackFlag = FALSE;
17073     }
17074     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17075     DisplayBothClocks();
17076     adjustedClock = FALSE;
17077 }
17078
17079 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17080
17081 /* Decrement running clock by amount of time that has passed */
17082 void
17083 DecrementClocks ()
17084 {
17085     long timeRemaining;
17086     long lastTickLength, fudge;
17087     TimeMark now;
17088
17089     if (!appData.clockMode) return;
17090     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17091
17092     GetTimeMark(&now);
17093
17094     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17095
17096     /* Fudge if we woke up a little too soon */
17097     fudge = intendedTickLength - lastTickLength;
17098     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17099
17100     if (WhiteOnMove(forwardMostMove)) {
17101         if(whiteNPS >= 0) lastTickLength = 0;
17102         timeRemaining = whiteTimeRemaining -= lastTickLength;
17103         if(timeRemaining < 0 && !appData.icsActive) {
17104             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17105             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17106                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17107                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17108             }
17109         }
17110         DisplayWhiteClock(whiteTimeRemaining - fudge,
17111                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17112     } else {
17113         if(blackNPS >= 0) lastTickLength = 0;
17114         timeRemaining = blackTimeRemaining -= lastTickLength;
17115         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17116             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17117             if(suddenDeath) {
17118                 blackStartMove = forwardMostMove;
17119                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17120             }
17121         }
17122         DisplayBlackClock(blackTimeRemaining - fudge,
17123                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17124     }
17125     if (CheckFlags()) return;
17126
17127     if(twoBoards) { // count down secondary board's clocks as well
17128         activePartnerTime -= lastTickLength;
17129         partnerUp = 1;
17130         if(activePartner == 'W')
17131             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17132         else
17133             DisplayBlackClock(activePartnerTime, TRUE);
17134         partnerUp = 0;
17135     }
17136
17137     tickStartTM = now;
17138     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17139     StartClockTimer(intendedTickLength);
17140
17141     /* if the time remaining has fallen below the alarm threshold, sound the
17142      * alarm. if the alarm has sounded and (due to a takeback or time control
17143      * with increment) the time remaining has increased to a level above the
17144      * threshold, reset the alarm so it can sound again.
17145      */
17146
17147     if (appData.icsActive && appData.icsAlarm) {
17148
17149         /* make sure we are dealing with the user's clock */
17150         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17151                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17152            )) return;
17153
17154         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17155             alarmSounded = FALSE;
17156         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17157             PlayAlarmSound();
17158             alarmSounded = TRUE;
17159         }
17160     }
17161 }
17162
17163
17164 /* A player has just moved, so stop the previously running
17165    clock and (if in clock mode) start the other one.
17166    We redisplay both clocks in case we're in ICS mode, because
17167    ICS gives us an update to both clocks after every move.
17168    Note that this routine is called *after* forwardMostMove
17169    is updated, so the last fractional tick must be subtracted
17170    from the color that is *not* on move now.
17171 */
17172 void
17173 SwitchClocks (int newMoveNr)
17174 {
17175     long lastTickLength;
17176     TimeMark now;
17177     int flagged = FALSE;
17178
17179     GetTimeMark(&now);
17180
17181     if (StopClockTimer() && appData.clockMode) {
17182         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17183         if (!WhiteOnMove(forwardMostMove)) {
17184             if(blackNPS >= 0) lastTickLength = 0;
17185             blackTimeRemaining -= lastTickLength;
17186            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17187 //         if(pvInfoList[forwardMostMove].time == -1)
17188                  pvInfoList[forwardMostMove].time =               // use GUI time
17189                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17190         } else {
17191            if(whiteNPS >= 0) lastTickLength = 0;
17192            whiteTimeRemaining -= lastTickLength;
17193            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17194 //         if(pvInfoList[forwardMostMove].time == -1)
17195                  pvInfoList[forwardMostMove].time =
17196                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17197         }
17198         flagged = CheckFlags();
17199     }
17200     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17201     CheckTimeControl();
17202
17203     if (flagged || !appData.clockMode) return;
17204
17205     switch (gameMode) {
17206       case MachinePlaysBlack:
17207       case MachinePlaysWhite:
17208       case BeginningOfGame:
17209         if (pausing) return;
17210         break;
17211
17212       case EditGame:
17213       case PlayFromGameFile:
17214       case IcsExamining:
17215         return;
17216
17217       default:
17218         break;
17219     }
17220
17221     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17222         if(WhiteOnMove(forwardMostMove))
17223              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17224         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17225     }
17226
17227     tickStartTM = now;
17228     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17229       whiteTimeRemaining : blackTimeRemaining);
17230     StartClockTimer(intendedTickLength);
17231 }
17232
17233
17234 /* Stop both clocks */
17235 void
17236 StopClocks ()
17237 {
17238     long lastTickLength;
17239     TimeMark now;
17240
17241     if (!StopClockTimer()) return;
17242     if (!appData.clockMode) return;
17243
17244     GetTimeMark(&now);
17245
17246     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17247     if (WhiteOnMove(forwardMostMove)) {
17248         if(whiteNPS >= 0) lastTickLength = 0;
17249         whiteTimeRemaining -= lastTickLength;
17250         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17251     } else {
17252         if(blackNPS >= 0) lastTickLength = 0;
17253         blackTimeRemaining -= lastTickLength;
17254         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17255     }
17256     CheckFlags();
17257 }
17258
17259 /* Start clock of player on move.  Time may have been reset, so
17260    if clock is already running, stop and restart it. */
17261 void
17262 StartClocks ()
17263 {
17264     (void) StopClockTimer(); /* in case it was running already */
17265     DisplayBothClocks();
17266     if (CheckFlags()) return;
17267
17268     if (!appData.clockMode) return;
17269     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17270
17271     GetTimeMark(&tickStartTM);
17272     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17273       whiteTimeRemaining : blackTimeRemaining);
17274
17275    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17276     whiteNPS = blackNPS = -1;
17277     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17278        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17279         whiteNPS = first.nps;
17280     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17281        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17282         blackNPS = first.nps;
17283     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17284         whiteNPS = second.nps;
17285     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17286         blackNPS = second.nps;
17287     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17288
17289     StartClockTimer(intendedTickLength);
17290 }
17291
17292 char *
17293 TimeString (long ms)
17294 {
17295     long second, minute, hour, day;
17296     char *sign = "";
17297     static char buf[32];
17298
17299     if (ms > 0 && ms <= 9900) {
17300       /* convert milliseconds to tenths, rounding up */
17301       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17302
17303       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17304       return buf;
17305     }
17306
17307     /* convert milliseconds to seconds, rounding up */
17308     /* use floating point to avoid strangeness of integer division
17309        with negative dividends on many machines */
17310     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17311
17312     if (second < 0) {
17313         sign = "-";
17314         second = -second;
17315     }
17316
17317     day = second / (60 * 60 * 24);
17318     second = second % (60 * 60 * 24);
17319     hour = second / (60 * 60);
17320     second = second % (60 * 60);
17321     minute = second / 60;
17322     second = second % 60;
17323
17324     if (day > 0)
17325       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17326               sign, day, hour, minute, second);
17327     else if (hour > 0)
17328       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17329     else
17330       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17331
17332     return buf;
17333 }
17334
17335
17336 /*
17337  * This is necessary because some C libraries aren't ANSI C compliant yet.
17338  */
17339 char *
17340 StrStr (char *string, char *match)
17341 {
17342     int i, length;
17343
17344     length = strlen(match);
17345
17346     for (i = strlen(string) - length; i >= 0; i--, string++)
17347       if (!strncmp(match, string, length))
17348         return string;
17349
17350     return NULL;
17351 }
17352
17353 char *
17354 StrCaseStr (char *string, char *match)
17355 {
17356     int i, j, length;
17357
17358     length = strlen(match);
17359
17360     for (i = strlen(string) - length; i >= 0; i--, string++) {
17361         for (j = 0; j < length; j++) {
17362             if (ToLower(match[j]) != ToLower(string[j]))
17363               break;
17364         }
17365         if (j == length) return string;
17366     }
17367
17368     return NULL;
17369 }
17370
17371 #ifndef _amigados
17372 int
17373 StrCaseCmp (char *s1, char *s2)
17374 {
17375     char c1, c2;
17376
17377     for (;;) {
17378         c1 = ToLower(*s1++);
17379         c2 = ToLower(*s2++);
17380         if (c1 > c2) return 1;
17381         if (c1 < c2) return -1;
17382         if (c1 == NULLCHAR) return 0;
17383     }
17384 }
17385
17386
17387 int
17388 ToLower (int c)
17389 {
17390     return isupper(c) ? tolower(c) : c;
17391 }
17392
17393
17394 int
17395 ToUpper (int c)
17396 {
17397     return islower(c) ? toupper(c) : c;
17398 }
17399 #endif /* !_amigados    */
17400
17401 char *
17402 StrSave (char *s)
17403 {
17404   char *ret;
17405
17406   if ((ret = (char *) malloc(strlen(s) + 1)))
17407     {
17408       safeStrCpy(ret, s, strlen(s)+1);
17409     }
17410   return ret;
17411 }
17412
17413 char *
17414 StrSavePtr (char *s, char **savePtr)
17415 {
17416     if (*savePtr) {
17417         free(*savePtr);
17418     }
17419     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17420       safeStrCpy(*savePtr, s, strlen(s)+1);
17421     }
17422     return(*savePtr);
17423 }
17424
17425 char *
17426 PGNDate ()
17427 {
17428     time_t clock;
17429     struct tm *tm;
17430     char buf[MSG_SIZ];
17431
17432     clock = time((time_t *)NULL);
17433     tm = localtime(&clock);
17434     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17435             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17436     return StrSave(buf);
17437 }
17438
17439
17440 char *
17441 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17442 {
17443     int i, j, fromX, fromY, toX, toY;
17444     int whiteToPlay;
17445     char buf[MSG_SIZ];
17446     char *p, *q;
17447     int emptycount;
17448     ChessSquare piece;
17449
17450     whiteToPlay = (gameMode == EditPosition) ?
17451       !blackPlaysFirst : (move % 2 == 0);
17452     p = buf;
17453
17454     /* Piece placement data */
17455     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17456         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17457         emptycount = 0;
17458         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17459             if (boards[move][i][j] == EmptySquare) {
17460                 emptycount++;
17461             } else { ChessSquare piece = boards[move][i][j];
17462                 if (emptycount > 0) {
17463                     if(emptycount<10) /* [HGM] can be >= 10 */
17464                         *p++ = '0' + emptycount;
17465                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17466                     emptycount = 0;
17467                 }
17468                 if(PieceToChar(piece) == '+') {
17469                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17470                     *p++ = '+';
17471                     piece = (ChessSquare)(DEMOTED piece);
17472                 }
17473                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17474                 if(p[-1] == '~') {
17475                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17476                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17477                     *p++ = '~';
17478                 }
17479             }
17480         }
17481         if (emptycount > 0) {
17482             if(emptycount<10) /* [HGM] can be >= 10 */
17483                 *p++ = '0' + emptycount;
17484             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17485             emptycount = 0;
17486         }
17487         *p++ = '/';
17488     }
17489     *(p - 1) = ' ';
17490
17491     /* [HGM] print Crazyhouse or Shogi holdings */
17492     if( gameInfo.holdingsWidth ) {
17493         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17494         q = p;
17495         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17496             piece = boards[move][i][BOARD_WIDTH-1];
17497             if( piece != EmptySquare )
17498               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17499                   *p++ = PieceToChar(piece);
17500         }
17501         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17502             piece = boards[move][BOARD_HEIGHT-i-1][0];
17503             if( piece != EmptySquare )
17504               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17505                   *p++ = PieceToChar(piece);
17506         }
17507
17508         if( q == p ) *p++ = '-';
17509         *p++ = ']';
17510         *p++ = ' ';
17511     }
17512
17513     /* Active color */
17514     *p++ = whiteToPlay ? 'w' : 'b';
17515     *p++ = ' ';
17516
17517   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17518     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17519   } else {
17520   if(nrCastlingRights) {
17521      q = p;
17522      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17523        /* [HGM] write directly from rights */
17524            if(boards[move][CASTLING][2] != NoRights &&
17525               boards[move][CASTLING][0] != NoRights   )
17526                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17527            if(boards[move][CASTLING][2] != NoRights &&
17528               boards[move][CASTLING][1] != NoRights   )
17529                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17530            if(boards[move][CASTLING][5] != NoRights &&
17531               boards[move][CASTLING][3] != NoRights   )
17532                 *p++ = boards[move][CASTLING][3] + AAA;
17533            if(boards[move][CASTLING][5] != NoRights &&
17534               boards[move][CASTLING][4] != NoRights   )
17535                 *p++ = boards[move][CASTLING][4] + AAA;
17536      } else {
17537
17538         /* [HGM] write true castling rights */
17539         if( nrCastlingRights == 6 ) {
17540             int q, k=0;
17541             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17542                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17543             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17544                  boards[move][CASTLING][2] != NoRights  );
17545             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17546                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17547                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17548                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17549                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17550             }
17551             if(q) *p++ = 'Q';
17552             k = 0;
17553             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17554                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17555             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17556                  boards[move][CASTLING][5] != NoRights  );
17557             if(gameInfo.variant == VariantSChess) {
17558                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17559                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17560                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17561                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17562             }
17563             if(q) *p++ = 'q';
17564         }
17565      }
17566      if (q == p) *p++ = '-'; /* No castling rights */
17567      *p++ = ' ';
17568   }
17569
17570   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17571      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17572      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17573     /* En passant target square */
17574     if (move > backwardMostMove) {
17575         fromX = moveList[move - 1][0] - AAA;
17576         fromY = moveList[move - 1][1] - ONE;
17577         toX = moveList[move - 1][2] - AAA;
17578         toY = moveList[move - 1][3] - ONE;
17579         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17580             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17581             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17582             fromX == toX) {
17583             /* 2-square pawn move just happened */
17584             *p++ = toX + AAA;
17585             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17586         } else {
17587             *p++ = '-';
17588         }
17589     } else if(move == backwardMostMove) {
17590         // [HGM] perhaps we should always do it like this, and forget the above?
17591         if((signed char)boards[move][EP_STATUS] >= 0) {
17592             *p++ = boards[move][EP_STATUS] + AAA;
17593             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17594         } else {
17595             *p++ = '-';
17596         }
17597     } else {
17598         *p++ = '-';
17599     }
17600     *p++ = ' ';
17601   }
17602   }
17603
17604     if(moveCounts)
17605     {   int i = 0, j=move;
17606
17607         /* [HGM] find reversible plies */
17608         if (appData.debugMode) { int k;
17609             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17610             for(k=backwardMostMove; k<=forwardMostMove; k++)
17611                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17612
17613         }
17614
17615         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17616         if( j == backwardMostMove ) i += initialRulePlies;
17617         sprintf(p, "%d ", i);
17618         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17619
17620         /* Fullmove number */
17621         sprintf(p, "%d", (move / 2) + 1);
17622     } else *--p = NULLCHAR;
17623
17624     return StrSave(buf);
17625 }
17626
17627 Boolean
17628 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17629 {
17630     int i, j, k, w=0;
17631     char *p, c;
17632     int emptycount, virgin[BOARD_FILES];
17633     ChessSquare piece;
17634
17635     p = fen;
17636
17637     /* Piece placement data */
17638     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17639         j = 0;
17640         for (;;) {
17641             if (*p == '/' || *p == ' ' || *p == '[' ) {
17642                 if(j > w) w = j;
17643                 emptycount = gameInfo.boardWidth - j;
17644                 while (emptycount--)
17645                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17646                 if (*p == '/') p++;
17647                 else if(autoSize) { // we stumbled unexpectedly into end of board
17648                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17649                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17650                     }
17651                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17652                 }
17653                 break;
17654 #if(BOARD_FILES >= 10)
17655             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17656                 p++; emptycount=10;
17657                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17658                 while (emptycount--)
17659                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17660 #endif
17661             } else if (*p == '*') {
17662                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17663             } else if (isdigit(*p)) {
17664                 emptycount = *p++ - '0';
17665                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17666                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17667                 while (emptycount--)
17668                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17669             } else if (*p == '+' || isalpha(*p)) {
17670                 if (j >= gameInfo.boardWidth) return FALSE;
17671                 if(*p=='+') {
17672                     piece = CharToPiece(*++p);
17673                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17674                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17675                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17676                 } else piece = CharToPiece(*p++);
17677
17678                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17679                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17680                     piece = (ChessSquare) (PROMOTED piece);
17681                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17682                     p++;
17683                 }
17684                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17685             } else {
17686                 return FALSE;
17687             }
17688         }
17689     }
17690     while (*p == '/' || *p == ' ') p++;
17691
17692     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17693
17694     /* [HGM] by default clear Crazyhouse holdings, if present */
17695     if(gameInfo.holdingsWidth) {
17696        for(i=0; i<BOARD_HEIGHT; i++) {
17697            board[i][0]             = EmptySquare; /* black holdings */
17698            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17699            board[i][1]             = (ChessSquare) 0; /* black counts */
17700            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17701        }
17702     }
17703
17704     /* [HGM] look for Crazyhouse holdings here */
17705     while(*p==' ') p++;
17706     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17707         if(*p == '[') p++;
17708         if(*p == '-' ) p++; /* empty holdings */ else {
17709             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17710             /* if we would allow FEN reading to set board size, we would   */
17711             /* have to add holdings and shift the board read so far here   */
17712             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17713                 p++;
17714                 if((int) piece >= (int) BlackPawn ) {
17715                     i = (int)piece - (int)BlackPawn;
17716                     i = PieceToNumber((ChessSquare)i);
17717                     if( i >= gameInfo.holdingsSize ) return FALSE;
17718                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17719                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17720                 } else {
17721                     i = (int)piece - (int)WhitePawn;
17722                     i = PieceToNumber((ChessSquare)i);
17723                     if( i >= gameInfo.holdingsSize ) return FALSE;
17724                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17725                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17726                 }
17727             }
17728         }
17729         if(*p == ']') p++;
17730     }
17731
17732     while(*p == ' ') p++;
17733
17734     /* Active color */
17735     c = *p++;
17736     if(appData.colorNickNames) {
17737       if( c == appData.colorNickNames[0] ) c = 'w'; else
17738       if( c == appData.colorNickNames[1] ) c = 'b';
17739     }
17740     switch (c) {
17741       case 'w':
17742         *blackPlaysFirst = FALSE;
17743         break;
17744       case 'b':
17745         *blackPlaysFirst = TRUE;
17746         break;
17747       default:
17748         return FALSE;
17749     }
17750
17751     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17752     /* return the extra info in global variiables             */
17753
17754     /* set defaults in case FEN is incomplete */
17755     board[EP_STATUS] = EP_UNKNOWN;
17756     for(i=0; i<nrCastlingRights; i++ ) {
17757         board[CASTLING][i] =
17758             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17759     }   /* assume possible unless obviously impossible */
17760     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17761     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17762     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17763                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17764     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17765     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17766     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17767                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17768     FENrulePlies = 0;
17769
17770     while(*p==' ') p++;
17771     if(nrCastlingRights) {
17772       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17773       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17774           /* castling indicator present, so default becomes no castlings */
17775           for(i=0; i<nrCastlingRights; i++ ) {
17776                  board[CASTLING][i] = NoRights;
17777           }
17778       }
17779       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17780              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17781              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17782              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17783         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17784
17785         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17786             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17787             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17788         }
17789         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17790             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17791         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17792                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17793         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17794                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17795         switch(c) {
17796           case'K':
17797               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17798               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17799               board[CASTLING][2] = whiteKingFile;
17800               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17801               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17802               break;
17803           case'Q':
17804               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17805               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17806               board[CASTLING][2] = whiteKingFile;
17807               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17808               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17809               break;
17810           case'k':
17811               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17812               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17813               board[CASTLING][5] = blackKingFile;
17814               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17815               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17816               break;
17817           case'q':
17818               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17819               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17820               board[CASTLING][5] = blackKingFile;
17821               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17822               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17823           case '-':
17824               break;
17825           default: /* FRC castlings */
17826               if(c >= 'a') { /* black rights */
17827                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17828                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17829                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17830                   if(i == BOARD_RGHT) break;
17831                   board[CASTLING][5] = i;
17832                   c -= AAA;
17833                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17834                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17835                   if(c > i)
17836                       board[CASTLING][3] = c;
17837                   else
17838                       board[CASTLING][4] = c;
17839               } else { /* white rights */
17840                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17841                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17842                     if(board[0][i] == WhiteKing) break;
17843                   if(i == BOARD_RGHT) break;
17844                   board[CASTLING][2] = i;
17845                   c -= AAA - 'a' + 'A';
17846                   if(board[0][c] >= WhiteKing) break;
17847                   if(c > i)
17848                       board[CASTLING][0] = c;
17849                   else
17850                       board[CASTLING][1] = c;
17851               }
17852         }
17853       }
17854       for(i=0; i<nrCastlingRights; i++)
17855         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17856       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17857     if (appData.debugMode) {
17858         fprintf(debugFP, "FEN castling rights:");
17859         for(i=0; i<nrCastlingRights; i++)
17860         fprintf(debugFP, " %d", board[CASTLING][i]);
17861         fprintf(debugFP, "\n");
17862     }
17863
17864       while(*p==' ') p++;
17865     }
17866
17867     /* read e.p. field in games that know e.p. capture */
17868     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17869        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17870        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17871       if(*p=='-') {
17872         p++; board[EP_STATUS] = EP_NONE;
17873       } else {
17874          char c = *p++ - AAA;
17875
17876          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17877          if(*p >= '0' && *p <='9') p++;
17878          board[EP_STATUS] = c;
17879       }
17880     }
17881
17882
17883     if(sscanf(p, "%d", &i) == 1) {
17884         FENrulePlies = i; /* 50-move ply counter */
17885         /* (The move number is still ignored)    */
17886     }
17887
17888     return TRUE;
17889 }
17890
17891 void
17892 EditPositionPasteFEN (char *fen)
17893 {
17894   if (fen != NULL) {
17895     Board initial_position;
17896
17897     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17898       DisplayError(_("Bad FEN position in clipboard"), 0);
17899       return ;
17900     } else {
17901       int savedBlackPlaysFirst = blackPlaysFirst;
17902       EditPositionEvent();
17903       blackPlaysFirst = savedBlackPlaysFirst;
17904       CopyBoard(boards[0], initial_position);
17905       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17906       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17907       DisplayBothClocks();
17908       DrawPosition(FALSE, boards[currentMove]);
17909     }
17910   }
17911 }
17912
17913 static char cseq[12] = "\\   ";
17914
17915 Boolean
17916 set_cont_sequence (char *new_seq)
17917 {
17918     int len;
17919     Boolean ret;
17920
17921     // handle bad attempts to set the sequence
17922         if (!new_seq)
17923                 return 0; // acceptable error - no debug
17924
17925     len = strlen(new_seq);
17926     ret = (len > 0) && (len < sizeof(cseq));
17927     if (ret)
17928       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17929     else if (appData.debugMode)
17930       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17931     return ret;
17932 }
17933
17934 /*
17935     reformat a source message so words don't cross the width boundary.  internal
17936     newlines are not removed.  returns the wrapped size (no null character unless
17937     included in source message).  If dest is NULL, only calculate the size required
17938     for the dest buffer.  lp argument indicats line position upon entry, and it's
17939     passed back upon exit.
17940 */
17941 int
17942 wrap (char *dest, char *src, int count, int width, int *lp)
17943 {
17944     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17945
17946     cseq_len = strlen(cseq);
17947     old_line = line = *lp;
17948     ansi = len = clen = 0;
17949
17950     for (i=0; i < count; i++)
17951     {
17952         if (src[i] == '\033')
17953             ansi = 1;
17954
17955         // if we hit the width, back up
17956         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17957         {
17958             // store i & len in case the word is too long
17959             old_i = i, old_len = len;
17960
17961             // find the end of the last word
17962             while (i && src[i] != ' ' && src[i] != '\n')
17963             {
17964                 i--;
17965                 len--;
17966             }
17967
17968             // word too long?  restore i & len before splitting it
17969             if ((old_i-i+clen) >= width)
17970             {
17971                 i = old_i;
17972                 len = old_len;
17973             }
17974
17975             // extra space?
17976             if (i && src[i-1] == ' ')
17977                 len--;
17978
17979             if (src[i] != ' ' && src[i] != '\n')
17980             {
17981                 i--;
17982                 if (len)
17983                     len--;
17984             }
17985
17986             // now append the newline and continuation sequence
17987             if (dest)
17988                 dest[len] = '\n';
17989             len++;
17990             if (dest)
17991                 strncpy(dest+len, cseq, cseq_len);
17992             len += cseq_len;
17993             line = cseq_len;
17994             clen = cseq_len;
17995             continue;
17996         }
17997
17998         if (dest)
17999             dest[len] = src[i];
18000         len++;
18001         if (!ansi)
18002             line++;
18003         if (src[i] == '\n')
18004             line = 0;
18005         if (src[i] == 'm')
18006             ansi = 0;
18007     }
18008     if (dest && appData.debugMode)
18009     {
18010         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18011             count, width, line, len, *lp);
18012         show_bytes(debugFP, src, count);
18013         fprintf(debugFP, "\ndest: ");
18014         show_bytes(debugFP, dest, len);
18015         fprintf(debugFP, "\n");
18016     }
18017     *lp = dest ? line : old_line;
18018
18019     return len;
18020 }
18021
18022 // [HGM] vari: routines for shelving variations
18023 Boolean modeRestore = FALSE;
18024
18025 void
18026 PushInner (int firstMove, int lastMove)
18027 {
18028         int i, j, nrMoves = lastMove - firstMove;
18029
18030         // push current tail of game on stack
18031         savedResult[storedGames] = gameInfo.result;
18032         savedDetails[storedGames] = gameInfo.resultDetails;
18033         gameInfo.resultDetails = NULL;
18034         savedFirst[storedGames] = firstMove;
18035         savedLast [storedGames] = lastMove;
18036         savedFramePtr[storedGames] = framePtr;
18037         framePtr -= nrMoves; // reserve space for the boards
18038         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18039             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18040             for(j=0; j<MOVE_LEN; j++)
18041                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18042             for(j=0; j<2*MOVE_LEN; j++)
18043                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18044             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18045             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18046             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18047             pvInfoList[firstMove+i-1].depth = 0;
18048             commentList[framePtr+i] = commentList[firstMove+i];
18049             commentList[firstMove+i] = NULL;
18050         }
18051
18052         storedGames++;
18053         forwardMostMove = firstMove; // truncate game so we can start variation
18054 }
18055
18056 void
18057 PushTail (int firstMove, int lastMove)
18058 {
18059         if(appData.icsActive) { // only in local mode
18060                 forwardMostMove = currentMove; // mimic old ICS behavior
18061                 return;
18062         }
18063         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18064
18065         PushInner(firstMove, lastMove);
18066         if(storedGames == 1) GreyRevert(FALSE);
18067         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18068 }
18069
18070 void
18071 PopInner (Boolean annotate)
18072 {
18073         int i, j, nrMoves;
18074         char buf[8000], moveBuf[20];
18075
18076         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18077         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18078         nrMoves = savedLast[storedGames] - currentMove;
18079         if(annotate) {
18080                 int cnt = 10;
18081                 if(!WhiteOnMove(currentMove))
18082                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18083                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18084                 for(i=currentMove; i<forwardMostMove; i++) {
18085                         if(WhiteOnMove(i))
18086                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18087                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18088                         strcat(buf, moveBuf);
18089                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18090                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18091                 }
18092                 strcat(buf, ")");
18093         }
18094         for(i=1; i<=nrMoves; i++) { // copy last variation back
18095             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18096             for(j=0; j<MOVE_LEN; j++)
18097                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18098             for(j=0; j<2*MOVE_LEN; j++)
18099                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18100             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18101             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18102             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18103             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18104             commentList[currentMove+i] = commentList[framePtr+i];
18105             commentList[framePtr+i] = NULL;
18106         }
18107         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18108         framePtr = savedFramePtr[storedGames];
18109         gameInfo.result = savedResult[storedGames];
18110         if(gameInfo.resultDetails != NULL) {
18111             free(gameInfo.resultDetails);
18112       }
18113         gameInfo.resultDetails = savedDetails[storedGames];
18114         forwardMostMove = currentMove + nrMoves;
18115 }
18116
18117 Boolean
18118 PopTail (Boolean annotate)
18119 {
18120         if(appData.icsActive) return FALSE; // only in local mode
18121         if(!storedGames) return FALSE; // sanity
18122         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18123
18124         PopInner(annotate);
18125         if(currentMove < forwardMostMove) ForwardEvent(); else
18126         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18127
18128         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18129         return TRUE;
18130 }
18131
18132 void
18133 CleanupTail ()
18134 {       // remove all shelved variations
18135         int i;
18136         for(i=0; i<storedGames; i++) {
18137             if(savedDetails[i])
18138                 free(savedDetails[i]);
18139             savedDetails[i] = NULL;
18140         }
18141         for(i=framePtr; i<MAX_MOVES; i++) {
18142                 if(commentList[i]) free(commentList[i]);
18143                 commentList[i] = NULL;
18144         }
18145         framePtr = MAX_MOVES-1;
18146         storedGames = 0;
18147 }
18148
18149 void
18150 LoadVariation (int index, char *text)
18151 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18152         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18153         int level = 0, move;
18154
18155         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18156         // first find outermost bracketing variation
18157         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18158             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18159                 if(*p == '{') wait = '}'; else
18160                 if(*p == '[') wait = ']'; else
18161                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18162                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18163             }
18164             if(*p == wait) wait = NULLCHAR; // closing ]} found
18165             p++;
18166         }
18167         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18168         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18169         end[1] = NULLCHAR; // clip off comment beyond variation
18170         ToNrEvent(currentMove-1);
18171         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18172         // kludge: use ParsePV() to append variation to game
18173         move = currentMove;
18174         ParsePV(start, TRUE, TRUE);
18175         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18176         ClearPremoveHighlights();
18177         CommentPopDown();
18178         ToNrEvent(currentMove+1);
18179 }
18180
18181 void
18182 LoadTheme ()
18183 {
18184     char *p, *q, buf[MSG_SIZ];
18185     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18186         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18187         ParseArgsFromString(buf);
18188         ActivateTheme(TRUE); // also redo colors
18189         return;
18190     }
18191     p = nickName;
18192     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18193     {
18194         int len;
18195         q = appData.themeNames;
18196         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18197       if(appData.useBitmaps) {
18198         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18199                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18200                 appData.liteBackTextureMode,
18201                 appData.darkBackTextureMode );
18202       } else {
18203         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18204                 Col2Text(2),   // lightSquareColor
18205                 Col2Text(3) ); // darkSquareColor
18206       }
18207       if(appData.useBorder) {
18208         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18209                 appData.border);
18210       } else {
18211         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18212       }
18213       if(appData.useFont) {
18214         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18215                 appData.renderPiecesWithFont,
18216                 appData.fontToPieceTable,
18217                 Col2Text(9),    // appData.fontBackColorWhite
18218                 Col2Text(10) ); // appData.fontForeColorBlack
18219       } else {
18220         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18221                 appData.pieceDirectory);
18222         if(!appData.pieceDirectory[0])
18223           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18224                 Col2Text(0),   // whitePieceColor
18225                 Col2Text(1) ); // blackPieceColor
18226       }
18227       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18228                 Col2Text(4),   // highlightSquareColor
18229                 Col2Text(5) ); // premoveHighlightColor
18230         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18231         if(insert != q) insert[-1] = NULLCHAR;
18232         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18233         if(q)   free(q);
18234     }
18235     ActivateTheme(FALSE);
18236 }