Fix sweep promotions
[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             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5332             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion));
5333     if(toX >= 0) {
5334         int victim = boards[currentMove][toY][toX];
5335         boards[currentMove][toY][toX] = promoSweep;
5336         DrawPosition(FALSE, boards[currentMove]);
5337         boards[currentMove][toY][toX] = victim;
5338     } else
5339     ChangeDragPiece(promoSweep);
5340 }
5341
5342 int
5343 PromoScroll (int x, int y)
5344 {
5345   int step = 0;
5346
5347   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5348   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5349   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5350   if(!step) return FALSE;
5351   lastX = x; lastY = y;
5352   if((promoSweep < BlackPawn) == flipView) step = -step;
5353   if(step > 0) selectFlag = 1;
5354   if(!selectFlag) Sweep(step);
5355   return FALSE;
5356 }
5357
5358 void
5359 NextPiece (int step)
5360 {
5361     ChessSquare piece = boards[currentMove][toY][toX];
5362     do {
5363         pieceSweep -= step;
5364         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5365         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5366         if(!step) step = -1;
5367     } while(PieceToChar(pieceSweep) == '.');
5368     boards[currentMove][toY][toX] = pieceSweep;
5369     DrawPosition(FALSE, boards[currentMove]);
5370     boards[currentMove][toY][toX] = piece;
5371 }
5372 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5373 void
5374 AlphaRank (char *move, int n)
5375 {
5376 //    char *p = move, c; int x, y;
5377
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5380     }
5381
5382     if(move[1]=='*' &&
5383        move[2]>='0' && move[2]<='9' &&
5384        move[3]>='a' && move[3]<='x'    ) {
5385         move[1] = '@';
5386         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5387         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5388     } else
5389     if(move[0]>='0' && move[0]<='9' &&
5390        move[1]>='a' && move[1]<='x' &&
5391        move[2]>='0' && move[2]<='9' &&
5392        move[3]>='a' && move[3]<='x'    ) {
5393         /* input move, Shogi -> normal */
5394         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5395         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5396         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5397         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5398     } else
5399     if(move[1]=='@' &&
5400        move[3]>='0' && move[3]<='9' &&
5401        move[2]>='a' && move[2]<='x'    ) {
5402         move[1] = '*';
5403         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5404         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5405     } else
5406     if(
5407        move[0]>='a' && move[0]<='x' &&
5408        move[3]>='0' && move[3]<='9' &&
5409        move[2]>='a' && move[2]<='x'    ) {
5410          /* output move, normal -> Shogi */
5411         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5412         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5413         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5414         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5415         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5416     }
5417     if (appData.debugMode) {
5418         fprintf(debugFP, "   out = '%s'\n", move);
5419     }
5420 }
5421
5422 char yy_textstr[8000];
5423
5424 /* Parser for moves from gnuchess, ICS, or user typein box */
5425 Boolean
5426 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5427 {
5428     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5429
5430     switch (*moveType) {
5431       case WhitePromotion:
5432       case BlackPromotion:
5433       case WhiteNonPromotion:
5434       case BlackNonPromotion:
5435       case NormalMove:
5436       case FirstLeg:
5437       case WhiteCapturesEnPassant:
5438       case BlackCapturesEnPassant:
5439       case WhiteKingSideCastle:
5440       case WhiteQueenSideCastle:
5441       case BlackKingSideCastle:
5442       case BlackQueenSideCastle:
5443       case WhiteKingSideCastleWild:
5444       case WhiteQueenSideCastleWild:
5445       case BlackKingSideCastleWild:
5446       case BlackQueenSideCastleWild:
5447       /* Code added by Tord: */
5448       case WhiteHSideCastleFR:
5449       case WhiteASideCastleFR:
5450       case BlackHSideCastleFR:
5451       case BlackASideCastleFR:
5452       /* End of code added by Tord */
5453       case IllegalMove:         /* bug or odd chess variant */
5454         *fromX = currentMoveString[0] - AAA;
5455         *fromY = currentMoveString[1] - ONE;
5456         *toX = currentMoveString[2] - AAA;
5457         *toY = currentMoveString[3] - ONE;
5458         *promoChar = currentMoveString[4];
5459         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5460             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5461     if (appData.debugMode) {
5462         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5463     }
5464             *fromX = *fromY = *toX = *toY = 0;
5465             return FALSE;
5466         }
5467         if (appData.testLegality) {
5468           return (*moveType != IllegalMove);
5469         } else {
5470           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5471                          // [HGM] lion: if this is a double move we are less critical
5472                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5473         }
5474
5475       case WhiteDrop:
5476       case BlackDrop:
5477         *fromX = *moveType == WhiteDrop ?
5478           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5479           (int) CharToPiece(ToLower(currentMoveString[0]));
5480         *fromY = DROP_RANK;
5481         *toX = currentMoveString[2] - AAA;
5482         *toY = currentMoveString[3] - ONE;
5483         *promoChar = NULLCHAR;
5484         return TRUE;
5485
5486       case AmbiguousMove:
5487       case ImpossibleMove:
5488       case EndOfFile:
5489       case ElapsedTime:
5490       case Comment:
5491       case PGNTag:
5492       case NAG:
5493       case WhiteWins:
5494       case BlackWins:
5495       case GameIsDrawn:
5496       default:
5497     if (appData.debugMode) {
5498         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5499     }
5500         /* bug? */
5501         *fromX = *fromY = *toX = *toY = 0;
5502         *promoChar = NULLCHAR;
5503         return FALSE;
5504     }
5505 }
5506
5507 Boolean pushed = FALSE;
5508 char *lastParseAttempt;
5509
5510 void
5511 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5512 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5513   int fromX, fromY, toX, toY; char promoChar;
5514   ChessMove moveType;
5515   Boolean valid;
5516   int nr = 0;
5517
5518   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5519   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5520     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5521     pushed = TRUE;
5522   }
5523   endPV = forwardMostMove;
5524   do {
5525     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5526     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5527     lastParseAttempt = pv;
5528     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5529     if(!valid && nr == 0 &&
5530        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5531         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5532         // Hande case where played move is different from leading PV move
5533         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5534         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5535         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5536         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5537           endPV += 2; // if position different, keep this
5538           moveList[endPV-1][0] = fromX + AAA;
5539           moveList[endPV-1][1] = fromY + ONE;
5540           moveList[endPV-1][2] = toX + AAA;
5541           moveList[endPV-1][3] = toY + ONE;
5542           parseList[endPV-1][0] = NULLCHAR;
5543           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5544         }
5545       }
5546     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5547     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5548     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5549     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5550         valid++; // allow comments in PV
5551         continue;
5552     }
5553     nr++;
5554     if(endPV+1 > framePtr) break; // no space, truncate
5555     if(!valid) break;
5556     endPV++;
5557     CopyBoard(boards[endPV], boards[endPV-1]);
5558     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5559     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5560     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5561     CoordsToAlgebraic(boards[endPV - 1],
5562                              PosFlags(endPV - 1),
5563                              fromY, fromX, toY, toX, promoChar,
5564                              parseList[endPV - 1]);
5565   } while(valid);
5566   if(atEnd == 2) return; // used hidden, for PV conversion
5567   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5568   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5569   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5570                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5571   DrawPosition(TRUE, boards[currentMove]);
5572 }
5573
5574 int
5575 MultiPV (ChessProgramState *cps)
5576 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5577         int i;
5578         for(i=0; i<cps->nrOptions; i++)
5579             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5580                 return i;
5581         return -1;
5582 }
5583
5584 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5585
5586 Boolean
5587 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5588 {
5589         int startPV, multi, lineStart, origIndex = index;
5590         char *p, buf2[MSG_SIZ];
5591         ChessProgramState *cps = (pane ? &second : &first);
5592
5593         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5594         lastX = x; lastY = y;
5595         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5596         lineStart = startPV = index;
5597         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5598         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5599         index = startPV;
5600         do{ while(buf[index] && buf[index] != '\n') index++;
5601         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5602         buf[index] = 0;
5603         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5604                 int n = cps->option[multi].value;
5605                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5606                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5607                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5608                 cps->option[multi].value = n;
5609                 *start = *end = 0;
5610                 return FALSE;
5611         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5612                 ExcludeClick(origIndex - lineStart);
5613                 return FALSE;
5614         }
5615         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5616         *start = startPV; *end = index-1;
5617         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5618         return TRUE;
5619 }
5620
5621 char *
5622 PvToSAN (char *pv)
5623 {
5624         static char buf[10*MSG_SIZ];
5625         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5626         *buf = NULLCHAR;
5627         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5628         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5629         for(i = forwardMostMove; i<endPV; i++){
5630             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5631             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5632             k += strlen(buf+k);
5633         }
5634         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5635         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5636         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5637         endPV = savedEnd;
5638         return buf;
5639 }
5640
5641 Boolean
5642 LoadPV (int x, int y)
5643 { // called on right mouse click to load PV
5644   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5645   lastX = x; lastY = y;
5646   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5647   extendGame = FALSE;
5648   return TRUE;
5649 }
5650
5651 void
5652 UnLoadPV ()
5653 {
5654   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5655   if(endPV < 0) return;
5656   if(appData.autoCopyPV) CopyFENToClipboard();
5657   endPV = -1;
5658   if(extendGame && currentMove > forwardMostMove) {
5659         Boolean saveAnimate = appData.animate;
5660         if(pushed) {
5661             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5662                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5663             } else storedGames--; // abandon shelved tail of original game
5664         }
5665         pushed = FALSE;
5666         forwardMostMove = currentMove;
5667         currentMove = oldFMM;
5668         appData.animate = FALSE;
5669         ToNrEvent(forwardMostMove);
5670         appData.animate = saveAnimate;
5671   }
5672   currentMove = forwardMostMove;
5673   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5674   ClearPremoveHighlights();
5675   DrawPosition(TRUE, boards[currentMove]);
5676 }
5677
5678 void
5679 MovePV (int x, int y, int h)
5680 { // step through PV based on mouse coordinates (called on mouse move)
5681   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5682
5683   // we must somehow check if right button is still down (might be released off board!)
5684   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5685   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5686   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5687   if(!step) return;
5688   lastX = x; lastY = y;
5689
5690   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5691   if(endPV < 0) return;
5692   if(y < margin) step = 1; else
5693   if(y > h - margin) step = -1;
5694   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5695   currentMove += step;
5696   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5697   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5698                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5699   DrawPosition(FALSE, boards[currentMove]);
5700 }
5701
5702
5703 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5704 // All positions will have equal probability, but the current method will not provide a unique
5705 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5706 #define DARK 1
5707 #define LITE 2
5708 #define ANY 3
5709
5710 int squaresLeft[4];
5711 int piecesLeft[(int)BlackPawn];
5712 int seed, nrOfShuffles;
5713
5714 void
5715 GetPositionNumber ()
5716 {       // sets global variable seed
5717         int i;
5718
5719         seed = appData.defaultFrcPosition;
5720         if(seed < 0) { // randomize based on time for negative FRC position numbers
5721                 for(i=0; i<50; i++) seed += random();
5722                 seed = random() ^ random() >> 8 ^ random() << 8;
5723                 if(seed<0) seed = -seed;
5724         }
5725 }
5726
5727 int
5728 put (Board board, int pieceType, int rank, int n, int shade)
5729 // put the piece on the (n-1)-th empty squares of the given shade
5730 {
5731         int i;
5732
5733         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5734                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5735                         board[rank][i] = (ChessSquare) pieceType;
5736                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5737                         squaresLeft[ANY]--;
5738                         piecesLeft[pieceType]--;
5739                         return i;
5740                 }
5741         }
5742         return -1;
5743 }
5744
5745
5746 void
5747 AddOnePiece (Board board, int pieceType, int rank, int shade)
5748 // calculate where the next piece goes, (any empty square), and put it there
5749 {
5750         int i;
5751
5752         i = seed % squaresLeft[shade];
5753         nrOfShuffles *= squaresLeft[shade];
5754         seed /= squaresLeft[shade];
5755         put(board, pieceType, rank, i, shade);
5756 }
5757
5758 void
5759 AddTwoPieces (Board board, int pieceType, int rank)
5760 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5761 {
5762         int i, n=squaresLeft[ANY], j=n-1, k;
5763
5764         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5765         i = seed % k;  // pick one
5766         nrOfShuffles *= k;
5767         seed /= k;
5768         while(i >= j) i -= j--;
5769         j = n - 1 - j; i += j;
5770         put(board, pieceType, rank, j, ANY);
5771         put(board, pieceType, rank, i, ANY);
5772 }
5773
5774 void
5775 SetUpShuffle (Board board, int number)
5776 {
5777         int i, p, first=1;
5778
5779         GetPositionNumber(); nrOfShuffles = 1;
5780
5781         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5782         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5783         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5784
5785         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5786
5787         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5788             p = (int) board[0][i];
5789             if(p < (int) BlackPawn) piecesLeft[p] ++;
5790             board[0][i] = EmptySquare;
5791         }
5792
5793         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5794             // shuffles restricted to allow normal castling put KRR first
5795             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5796                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5797             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5798                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5799             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5800                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5801             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5802                 put(board, WhiteRook, 0, 0, ANY);
5803             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5804         }
5805
5806         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5807             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5808             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5809                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5810                 while(piecesLeft[p] >= 2) {
5811                     AddOnePiece(board, p, 0, LITE);
5812                     AddOnePiece(board, p, 0, DARK);
5813                 }
5814                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5815             }
5816
5817         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5818             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5819             // but we leave King and Rooks for last, to possibly obey FRC restriction
5820             if(p == (int)WhiteRook) continue;
5821             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5822             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5823         }
5824
5825         // now everything is placed, except perhaps King (Unicorn) and Rooks
5826
5827         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5828             // Last King gets castling rights
5829             while(piecesLeft[(int)WhiteUnicorn]) {
5830                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5831                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5832             }
5833
5834             while(piecesLeft[(int)WhiteKing]) {
5835                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5836                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5837             }
5838
5839
5840         } else {
5841             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5842             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5843         }
5844
5845         // Only Rooks can be left; simply place them all
5846         while(piecesLeft[(int)WhiteRook]) {
5847                 i = put(board, WhiteRook, 0, 0, ANY);
5848                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5849                         if(first) {
5850                                 first=0;
5851                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5852                         }
5853                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5854                 }
5855         }
5856         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5857             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5858         }
5859
5860         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5861 }
5862
5863 int
5864 SetCharTable (char *table, const char * map)
5865 /* [HGM] moved here from winboard.c because of its general usefulness */
5866 /*       Basically a safe strcpy that uses the last character as King */
5867 {
5868     int result = FALSE; int NrPieces;
5869
5870     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5871                     && NrPieces >= 12 && !(NrPieces&1)) {
5872         int i; /* [HGM] Accept even length from 12 to 34 */
5873
5874         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5875         for( i=0; i<NrPieces/2-1; i++ ) {
5876             table[i] = map[i];
5877             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5878         }
5879         table[(int) WhiteKing]  = map[NrPieces/2-1];
5880         table[(int) BlackKing]  = map[NrPieces-1];
5881
5882         result = TRUE;
5883     }
5884
5885     return result;
5886 }
5887
5888 void
5889 Prelude (Board board)
5890 {       // [HGM] superchess: random selection of exo-pieces
5891         int i, j, k; ChessSquare p;
5892         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5893
5894         GetPositionNumber(); // use FRC position number
5895
5896         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5897             SetCharTable(pieceToChar, appData.pieceToCharTable);
5898             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5899                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5900         }
5901
5902         j = seed%4;                 seed /= 4;
5903         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5904         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5905         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5906         j = seed%3 + (seed%3 >= j); seed /= 3;
5907         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5908         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5909         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5910         j = seed%3;                 seed /= 3;
5911         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5912         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5913         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5914         j = seed%2 + (seed%2 >= j); seed /= 2;
5915         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5916         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5917         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5918         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5919         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5920         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5921         put(board, exoPieces[0],    0, 0, ANY);
5922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5923 }
5924
5925 void
5926 InitPosition (int redraw)
5927 {
5928     ChessSquare (* pieces)[BOARD_FILES];
5929     int i, j, pawnRow=1, pieceRows=1, overrule,
5930     oldx = gameInfo.boardWidth,
5931     oldy = gameInfo.boardHeight,
5932     oldh = gameInfo.holdingsWidth;
5933     static int oldv;
5934
5935     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5936
5937     /* [AS] Initialize pv info list [HGM] and game status */
5938     {
5939         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5940             pvInfoList[i].depth = 0;
5941             boards[i][EP_STATUS] = EP_NONE;
5942             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5943         }
5944
5945         initialRulePlies = 0; /* 50-move counter start */
5946
5947         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5948         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5949     }
5950
5951
5952     /* [HGM] logic here is completely changed. In stead of full positions */
5953     /* the initialized data only consist of the two backranks. The switch */
5954     /* selects which one we will use, which is than copied to the Board   */
5955     /* initialPosition, which for the rest is initialized by Pawns and    */
5956     /* empty squares. This initial position is then copied to boards[0],  */
5957     /* possibly after shuffling, so that it remains available.            */
5958
5959     gameInfo.holdingsWidth = 0; /* default board sizes */
5960     gameInfo.boardWidth    = 8;
5961     gameInfo.boardHeight   = 8;
5962     gameInfo.holdingsSize  = 0;
5963     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5964     for(i=0; i<BOARD_FILES-2; i++)
5965       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5966     initialPosition[EP_STATUS] = EP_NONE;
5967     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5968     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5969          SetCharTable(pieceNickName, appData.pieceNickNames);
5970     else SetCharTable(pieceNickName, "............");
5971     pieces = FIDEArray;
5972
5973     switch (gameInfo.variant) {
5974     case VariantFischeRandom:
5975       shuffleOpenings = TRUE;
5976     default:
5977       break;
5978     case VariantShatranj:
5979       pieces = ShatranjArray;
5980       nrCastlingRights = 0;
5981       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5982       break;
5983     case VariantMakruk:
5984       pieces = makrukArray;
5985       nrCastlingRights = 0;
5986       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5987       break;
5988     case VariantASEAN:
5989       pieces = aseanArray;
5990       nrCastlingRights = 0;
5991       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5992       break;
5993     case VariantTwoKings:
5994       pieces = twoKingsArray;
5995       break;
5996     case VariantGrand:
5997       pieces = GrandArray;
5998       nrCastlingRights = 0;
5999       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6000       gameInfo.boardWidth = 10;
6001       gameInfo.boardHeight = 10;
6002       gameInfo.holdingsSize = 7;
6003       break;
6004     case VariantCapaRandom:
6005       shuffleOpenings = TRUE;
6006     case VariantCapablanca:
6007       pieces = CapablancaArray;
6008       gameInfo.boardWidth = 10;
6009       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6010       break;
6011     case VariantGothic:
6012       pieces = GothicArray;
6013       gameInfo.boardWidth = 10;
6014       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6015       break;
6016     case VariantSChess:
6017       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6018       gameInfo.holdingsSize = 7;
6019       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6020       break;
6021     case VariantJanus:
6022       pieces = JanusArray;
6023       gameInfo.boardWidth = 10;
6024       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6025       nrCastlingRights = 6;
6026         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6027         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6028         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6029         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6030         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6031         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6032       break;
6033     case VariantFalcon:
6034       pieces = FalconArray;
6035       gameInfo.boardWidth = 10;
6036       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6037       break;
6038     case VariantXiangqi:
6039       pieces = XiangqiArray;
6040       gameInfo.boardWidth  = 9;
6041       gameInfo.boardHeight = 10;
6042       nrCastlingRights = 0;
6043       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6044       break;
6045     case VariantShogi:
6046       pieces = ShogiArray;
6047       gameInfo.boardWidth  = 9;
6048       gameInfo.boardHeight = 9;
6049       gameInfo.holdingsSize = 7;
6050       nrCastlingRights = 0;
6051       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6052       break;
6053     case VariantChu:
6054       pieces = ChuArray; pieceRows = 3;
6055       gameInfo.boardWidth  = 12;
6056       gameInfo.boardHeight = 12;
6057       nrCastlingRights = 0;
6058       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6059                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6060       break;
6061     case VariantCourier:
6062       pieces = CourierArray;
6063       gameInfo.boardWidth  = 12;
6064       nrCastlingRights = 0;
6065       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6066       break;
6067     case VariantKnightmate:
6068       pieces = KnightmateArray;
6069       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6070       break;
6071     case VariantSpartan:
6072       pieces = SpartanArray;
6073       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6074       break;
6075     case VariantLion:
6076       pieces = lionArray;
6077       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6078       break;
6079     case VariantChuChess:
6080       pieces = ChuChessArray;
6081       gameInfo.boardWidth = 10;
6082       gameInfo.boardHeight = 10;
6083       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6084       break;
6085     case VariantFairy:
6086       pieces = fairyArray;
6087       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6088       break;
6089     case VariantGreat:
6090       pieces = GreatArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6093       gameInfo.holdingsSize = 8;
6094       break;
6095     case VariantSuper:
6096       pieces = FIDEArray;
6097       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6098       gameInfo.holdingsSize = 8;
6099       startedFromSetupPosition = TRUE;
6100       break;
6101     case VariantCrazyhouse:
6102     case VariantBughouse:
6103       pieces = FIDEArray;
6104       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6105       gameInfo.holdingsSize = 5;
6106       break;
6107     case VariantWildCastle:
6108       pieces = FIDEArray;
6109       /* !!?shuffle with kings guaranteed to be on d or e file */
6110       shuffleOpenings = 1;
6111       break;
6112     case VariantNoCastle:
6113       pieces = FIDEArray;
6114       nrCastlingRights = 0;
6115       /* !!?unconstrained back-rank shuffle */
6116       shuffleOpenings = 1;
6117       break;
6118     }
6119
6120     overrule = 0;
6121     if(appData.NrFiles >= 0) {
6122         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6123         gameInfo.boardWidth = appData.NrFiles;
6124     }
6125     if(appData.NrRanks >= 0) {
6126         gameInfo.boardHeight = appData.NrRanks;
6127     }
6128     if(appData.holdingsSize >= 0) {
6129         i = appData.holdingsSize;
6130         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6131         gameInfo.holdingsSize = i;
6132     }
6133     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6134     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6135         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6136
6137     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6138     if(pawnRow < 1) pawnRow = 1;
6139     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6140        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6141     if(gameInfo.variant == VariantChu) pawnRow = 3;
6142
6143     /* User pieceToChar list overrules defaults */
6144     if(appData.pieceToCharTable != NULL)
6145         SetCharTable(pieceToChar, appData.pieceToCharTable);
6146
6147     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6148
6149         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6150             s = (ChessSquare) 0; /* account holding counts in guard band */
6151         for( i=0; i<BOARD_HEIGHT; i++ )
6152             initialPosition[i][j] = s;
6153
6154         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6155         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6156         initialPosition[pawnRow][j] = WhitePawn;
6157         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6158         if(gameInfo.variant == VariantXiangqi) {
6159             if(j&1) {
6160                 initialPosition[pawnRow][j] =
6161                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6162                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6163                    initialPosition[2][j] = WhiteCannon;
6164                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6165                 }
6166             }
6167         }
6168         if(gameInfo.variant == VariantChu) {
6169              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6170                initialPosition[pawnRow+1][j] = WhiteCobra,
6171                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6172              for(i=1; i<pieceRows; i++) {
6173                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6174                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6175              }
6176         }
6177         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6178             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6179                initialPosition[0][j] = WhiteRook;
6180                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6181             }
6182         }
6183         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6184     }
6185     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6186     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6187
6188             j=BOARD_LEFT+1;
6189             initialPosition[1][j] = WhiteBishop;
6190             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6191             j=BOARD_RGHT-2;
6192             initialPosition[1][j] = WhiteRook;
6193             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6194     }
6195
6196     if( nrCastlingRights == -1) {
6197         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6198         /*       This sets default castling rights from none to normal corners   */
6199         /* Variants with other castling rights must set them themselves above    */
6200         nrCastlingRights = 6;
6201
6202         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6203         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6204         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6205         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6206         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6207         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6208      }
6209
6210      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6211      if(gameInfo.variant == VariantGreat) { // promotion commoners
6212         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6213         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6214         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6215         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6216      }
6217      if( gameInfo.variant == VariantSChess ) {
6218       initialPosition[1][0] = BlackMarshall;
6219       initialPosition[2][0] = BlackAngel;
6220       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6221       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6222       initialPosition[1][1] = initialPosition[2][1] =
6223       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6224      }
6225   if (appData.debugMode) {
6226     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6227   }
6228     if(shuffleOpenings) {
6229         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6230         startedFromSetupPosition = TRUE;
6231     }
6232     if(startedFromPositionFile) {
6233       /* [HGM] loadPos: use PositionFile for every new game */
6234       CopyBoard(initialPosition, filePosition);
6235       for(i=0; i<nrCastlingRights; i++)
6236           initialRights[i] = filePosition[CASTLING][i];
6237       startedFromSetupPosition = TRUE;
6238     }
6239
6240     CopyBoard(boards[0], initialPosition);
6241
6242     if(oldx != gameInfo.boardWidth ||
6243        oldy != gameInfo.boardHeight ||
6244        oldv != gameInfo.variant ||
6245        oldh != gameInfo.holdingsWidth
6246                                          )
6247             InitDrawingSizes(-2 ,0);
6248
6249     oldv = gameInfo.variant;
6250     if (redraw)
6251       DrawPosition(TRUE, boards[currentMove]);
6252 }
6253
6254 void
6255 SendBoard (ChessProgramState *cps, int moveNum)
6256 {
6257     char message[MSG_SIZ];
6258
6259     if (cps->useSetboard) {
6260       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6261       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6262       SendToProgram(message, cps);
6263       free(fen);
6264
6265     } else {
6266       ChessSquare *bp;
6267       int i, j, left=0, right=BOARD_WIDTH;
6268       /* Kludge to set black to move, avoiding the troublesome and now
6269        * deprecated "black" command.
6270        */
6271       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6272         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6273
6274       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6275
6276       SendToProgram("edit\n", cps);
6277       SendToProgram("#\n", cps);
6278       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6279         bp = &boards[moveNum][i][left];
6280         for (j = left; j < right; j++, bp++) {
6281           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6282           if ((int) *bp < (int) BlackPawn) {
6283             if(j == BOARD_RGHT+1)
6284                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6285             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6286             if(message[0] == '+' || message[0] == '~') {
6287               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6288                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6289                         AAA + j, ONE + i);
6290             }
6291             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6292                 message[1] = BOARD_RGHT   - 1 - j + '1';
6293                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6294             }
6295             SendToProgram(message, cps);
6296           }
6297         }
6298       }
6299
6300       SendToProgram("c\n", cps);
6301       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6302         bp = &boards[moveNum][i][left];
6303         for (j = left; j < right; j++, bp++) {
6304           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6305           if (((int) *bp != (int) EmptySquare)
6306               && ((int) *bp >= (int) BlackPawn)) {
6307             if(j == BOARD_LEFT-2)
6308                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6309             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6310                     AAA + j, ONE + i);
6311             if(message[0] == '+' || message[0] == '~') {
6312               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6313                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6314                         AAA + j, ONE + i);
6315             }
6316             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6317                 message[1] = BOARD_RGHT   - 1 - j + '1';
6318                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6319             }
6320             SendToProgram(message, cps);
6321           }
6322         }
6323       }
6324
6325       SendToProgram(".\n", cps);
6326     }
6327     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6328 }
6329
6330 char exclusionHeader[MSG_SIZ];
6331 int exCnt, excludePtr;
6332 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6333 static Exclusion excluTab[200];
6334 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6335
6336 static void
6337 WriteMap (int s)
6338 {
6339     int j;
6340     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6341     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6342 }
6343
6344 static void
6345 ClearMap ()
6346 {
6347     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6348     excludePtr = 24; exCnt = 0;
6349     WriteMap(0);
6350 }
6351
6352 static void
6353 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6354 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6355     char buf[2*MOVE_LEN], *p;
6356     Exclusion *e = excluTab;
6357     int i;
6358     for(i=0; i<exCnt; i++)
6359         if(e[i].ff == fromX && e[i].fr == fromY &&
6360            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6361     if(i == exCnt) { // was not in exclude list; add it
6362         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6363         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6364             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6365             return; // abort
6366         }
6367         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6368         excludePtr++; e[i].mark = excludePtr++;
6369         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6370         exCnt++;
6371     }
6372     exclusionHeader[e[i].mark] = state;
6373 }
6374
6375 static int
6376 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6377 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6378     char buf[MSG_SIZ];
6379     int j, k;
6380     ChessMove moveType;
6381     if((signed char)promoChar == -1) { // kludge to indicate best move
6382         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6383             return 1; // if unparsable, abort
6384     }
6385     // update exclusion map (resolving toggle by consulting existing state)
6386     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6387     j = k%8; k >>= 3;
6388     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6389     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6390          excludeMap[k] |=   1<<j;
6391     else excludeMap[k] &= ~(1<<j);
6392     // update header
6393     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6394     // inform engine
6395     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6396     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6397     SendToBoth(buf);
6398     return (state == '+');
6399 }
6400
6401 static void
6402 ExcludeClick (int index)
6403 {
6404     int i, j;
6405     Exclusion *e = excluTab;
6406     if(index < 25) { // none, best or tail clicked
6407         if(index < 13) { // none: include all
6408             WriteMap(0); // clear map
6409             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6410             SendToBoth("include all\n"); // and inform engine
6411         } else if(index > 18) { // tail
6412             if(exclusionHeader[19] == '-') { // tail was excluded
6413                 SendToBoth("include all\n");
6414                 WriteMap(0); // clear map completely
6415                 // now re-exclude selected moves
6416                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6417                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6418             } else { // tail was included or in mixed state
6419                 SendToBoth("exclude all\n");
6420                 WriteMap(0xFF); // fill map completely
6421                 // now re-include selected moves
6422                 j = 0; // count them
6423                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6424                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6425                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6426             }
6427         } else { // best
6428             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6429         }
6430     } else {
6431         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6432             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6433             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6434             break;
6435         }
6436     }
6437 }
6438
6439 ChessSquare
6440 DefaultPromoChoice (int white)
6441 {
6442     ChessSquare result;
6443     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6444        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6445         result = WhiteFerz; // no choice
6446     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6447         result= WhiteKing; // in Suicide Q is the last thing we want
6448     else if(gameInfo.variant == VariantSpartan)
6449         result = white ? WhiteQueen : WhiteAngel;
6450     else result = WhiteQueen;
6451     if(!white) result = WHITE_TO_BLACK result;
6452     return result;
6453 }
6454
6455 static int autoQueen; // [HGM] oneclick
6456
6457 int
6458 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6459 {
6460     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6461     /* [HGM] add Shogi promotions */
6462     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6463     ChessSquare piece, partner;
6464     ChessMove moveType;
6465     Boolean premove;
6466
6467     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6468     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6469
6470     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6471       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6472         return FALSE;
6473
6474     piece = boards[currentMove][fromY][fromX];
6475     if(gameInfo.variant == VariantChu) {
6476         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6477         promotionZoneSize = BOARD_HEIGHT/3;
6478         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6479     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6480         promotionZoneSize = BOARD_HEIGHT/3;
6481         highestPromotingPiece = (int)WhiteAlfil;
6482     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6483         promotionZoneSize = 3;
6484     }
6485
6486     // Treat Lance as Pawn when it is not representing Amazon
6487     if(gameInfo.variant != VariantSuper) {
6488         if(piece == WhiteLance) piece = WhitePawn; else
6489         if(piece == BlackLance) piece = BlackPawn;
6490     }
6491
6492     // next weed out all moves that do not touch the promotion zone at all
6493     if((int)piece >= BlackPawn) {
6494         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6495              return FALSE;
6496         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6497         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6498     } else {
6499         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6500            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6501         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6502              return FALSE;
6503     }
6504
6505     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6506
6507     // weed out mandatory Shogi promotions
6508     if(gameInfo.variant == VariantShogi) {
6509         if(piece >= BlackPawn) {
6510             if(toY == 0 && piece == BlackPawn ||
6511                toY == 0 && piece == BlackQueen ||
6512                toY <= 1 && piece == BlackKnight) {
6513                 *promoChoice = '+';
6514                 return FALSE;
6515             }
6516         } else {
6517             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6518                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6519                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6520                 *promoChoice = '+';
6521                 return FALSE;
6522             }
6523         }
6524     }
6525
6526     // weed out obviously illegal Pawn moves
6527     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6528         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6529         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6530         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6531         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6532         // note we are not allowed to test for valid (non-)capture, due to premove
6533     }
6534
6535     // we either have a choice what to promote to, or (in Shogi) whether to promote
6536     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6537        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6538         *promoChoice = PieceToChar(BlackFerz);  // no choice
6539         return FALSE;
6540     }
6541     // no sense asking what we must promote to if it is going to explode...
6542     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6543         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6544         return FALSE;
6545     }
6546     // give caller the default choice even if we will not make it
6547     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6548     partner = piece; // pieces can promote if the pieceToCharTable says so
6549     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6550     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6551     if(        sweepSelect && gameInfo.variant != VariantGreat
6552                            && gameInfo.variant != VariantGrand
6553                            && gameInfo.variant != VariantSuper) return FALSE;
6554     if(autoQueen) return FALSE; // predetermined
6555
6556     // suppress promotion popup on illegal moves that are not premoves
6557     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6558               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6559     if(appData.testLegality && !premove) {
6560         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6561                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6562         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6563         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6564             return FALSE;
6565     }
6566
6567     return TRUE;
6568 }
6569
6570 int
6571 InPalace (int row, int column)
6572 {   /* [HGM] for Xiangqi */
6573     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6574          column < (BOARD_WIDTH + 4)/2 &&
6575          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6576     return FALSE;
6577 }
6578
6579 int
6580 PieceForSquare (int x, int y)
6581 {
6582   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6583      return -1;
6584   else
6585      return boards[currentMove][y][x];
6586 }
6587
6588 int
6589 OKToStartUserMove (int x, int y)
6590 {
6591     ChessSquare from_piece;
6592     int white_piece;
6593
6594     if (matchMode) return FALSE;
6595     if (gameMode == EditPosition) return TRUE;
6596
6597     if (x >= 0 && y >= 0)
6598       from_piece = boards[currentMove][y][x];
6599     else
6600       from_piece = EmptySquare;
6601
6602     if (from_piece == EmptySquare) return FALSE;
6603
6604     white_piece = (int)from_piece >= (int)WhitePawn &&
6605       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6606
6607     switch (gameMode) {
6608       case AnalyzeFile:
6609       case TwoMachinesPlay:
6610       case EndOfGame:
6611         return FALSE;
6612
6613       case IcsObserving:
6614       case IcsIdle:
6615         return FALSE;
6616
6617       case MachinePlaysWhite:
6618       case IcsPlayingBlack:
6619         if (appData.zippyPlay) return FALSE;
6620         if (white_piece) {
6621             DisplayMoveError(_("You are playing Black"));
6622             return FALSE;
6623         }
6624         break;
6625
6626       case MachinePlaysBlack:
6627       case IcsPlayingWhite:
6628         if (appData.zippyPlay) return FALSE;
6629         if (!white_piece) {
6630             DisplayMoveError(_("You are playing White"));
6631             return FALSE;
6632         }
6633         break;
6634
6635       case PlayFromGameFile:
6636             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6637       case EditGame:
6638         if (!white_piece && WhiteOnMove(currentMove)) {
6639             DisplayMoveError(_("It is White's turn"));
6640             return FALSE;
6641         }
6642         if (white_piece && !WhiteOnMove(currentMove)) {
6643             DisplayMoveError(_("It is Black's turn"));
6644             return FALSE;
6645         }
6646         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6647             /* Editing correspondence game history */
6648             /* Could disallow this or prompt for confirmation */
6649             cmailOldMove = -1;
6650         }
6651         break;
6652
6653       case BeginningOfGame:
6654         if (appData.icsActive) return FALSE;
6655         if (!appData.noChessProgram) {
6656             if (!white_piece) {
6657                 DisplayMoveError(_("You are playing White"));
6658                 return FALSE;
6659             }
6660         }
6661         break;
6662
6663       case Training:
6664         if (!white_piece && WhiteOnMove(currentMove)) {
6665             DisplayMoveError(_("It is White's turn"));
6666             return FALSE;
6667         }
6668         if (white_piece && !WhiteOnMove(currentMove)) {
6669             DisplayMoveError(_("It is Black's turn"));
6670             return FALSE;
6671         }
6672         break;
6673
6674       default:
6675       case IcsExamining:
6676         break;
6677     }
6678     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6679         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6680         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6681         && gameMode != AnalyzeFile && gameMode != Training) {
6682         DisplayMoveError(_("Displayed position is not current"));
6683         return FALSE;
6684     }
6685     return TRUE;
6686 }
6687
6688 Boolean
6689 OnlyMove (int *x, int *y, Boolean captures)
6690 {
6691     DisambiguateClosure cl;
6692     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6693     switch(gameMode) {
6694       case MachinePlaysBlack:
6695       case IcsPlayingWhite:
6696       case BeginningOfGame:
6697         if(!WhiteOnMove(currentMove)) return FALSE;
6698         break;
6699       case MachinePlaysWhite:
6700       case IcsPlayingBlack:
6701         if(WhiteOnMove(currentMove)) return FALSE;
6702         break;
6703       case EditGame:
6704         break;
6705       default:
6706         return FALSE;
6707     }
6708     cl.pieceIn = EmptySquare;
6709     cl.rfIn = *y;
6710     cl.ffIn = *x;
6711     cl.rtIn = -1;
6712     cl.ftIn = -1;
6713     cl.promoCharIn = NULLCHAR;
6714     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6715     if( cl.kind == NormalMove ||
6716         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6717         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6718         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6719       fromX = cl.ff;
6720       fromY = cl.rf;
6721       *x = cl.ft;
6722       *y = cl.rt;
6723       return TRUE;
6724     }
6725     if(cl.kind != ImpossibleMove) return FALSE;
6726     cl.pieceIn = EmptySquare;
6727     cl.rfIn = -1;
6728     cl.ffIn = -1;
6729     cl.rtIn = *y;
6730     cl.ftIn = *x;
6731     cl.promoCharIn = NULLCHAR;
6732     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6733     if( cl.kind == NormalMove ||
6734         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6735         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6736         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6737       fromX = cl.ff;
6738       fromY = cl.rf;
6739       *x = cl.ft;
6740       *y = cl.rt;
6741       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6742       return TRUE;
6743     }
6744     return FALSE;
6745 }
6746
6747 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6748 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6749 int lastLoadGameUseList = FALSE;
6750 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6751 ChessMove lastLoadGameStart = EndOfFile;
6752 int doubleClick;
6753
6754 void
6755 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6756 {
6757     ChessMove moveType;
6758     ChessSquare pup;
6759     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6760
6761     /* Check if the user is playing in turn.  This is complicated because we
6762        let the user "pick up" a piece before it is his turn.  So the piece he
6763        tried to pick up may have been captured by the time he puts it down!
6764        Therefore we use the color the user is supposed to be playing in this
6765        test, not the color of the piece that is currently on the starting
6766        square---except in EditGame mode, where the user is playing both
6767        sides; fortunately there the capture race can't happen.  (It can
6768        now happen in IcsExamining mode, but that's just too bad.  The user
6769        will get a somewhat confusing message in that case.)
6770        */
6771
6772     switch (gameMode) {
6773       case AnalyzeFile:
6774       case TwoMachinesPlay:
6775       case EndOfGame:
6776       case IcsObserving:
6777       case IcsIdle:
6778         /* We switched into a game mode where moves are not accepted,
6779            perhaps while the mouse button was down. */
6780         return;
6781
6782       case MachinePlaysWhite:
6783         /* User is moving for Black */
6784         if (WhiteOnMove(currentMove)) {
6785             DisplayMoveError(_("It is White's turn"));
6786             return;
6787         }
6788         break;
6789
6790       case MachinePlaysBlack:
6791         /* User is moving for White */
6792         if (!WhiteOnMove(currentMove)) {
6793             DisplayMoveError(_("It is Black's turn"));
6794             return;
6795         }
6796         break;
6797
6798       case PlayFromGameFile:
6799             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6800       case EditGame:
6801       case IcsExamining:
6802       case BeginningOfGame:
6803       case AnalyzeMode:
6804       case Training:
6805         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6806         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6807             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6808             /* User is moving for Black */
6809             if (WhiteOnMove(currentMove)) {
6810                 DisplayMoveError(_("It is White's turn"));
6811                 return;
6812             }
6813         } else {
6814             /* User is moving for White */
6815             if (!WhiteOnMove(currentMove)) {
6816                 DisplayMoveError(_("It is Black's turn"));
6817                 return;
6818             }
6819         }
6820         break;
6821
6822       case IcsPlayingBlack:
6823         /* User is moving for Black */
6824         if (WhiteOnMove(currentMove)) {
6825             if (!appData.premove) {
6826                 DisplayMoveError(_("It is White's turn"));
6827             } else if (toX >= 0 && toY >= 0) {
6828                 premoveToX = toX;
6829                 premoveToY = toY;
6830                 premoveFromX = fromX;
6831                 premoveFromY = fromY;
6832                 premovePromoChar = promoChar;
6833                 gotPremove = 1;
6834                 if (appData.debugMode)
6835                     fprintf(debugFP, "Got premove: fromX %d,"
6836                             "fromY %d, toX %d, toY %d\n",
6837                             fromX, fromY, toX, toY);
6838             }
6839             return;
6840         }
6841         break;
6842
6843       case IcsPlayingWhite:
6844         /* User is moving for White */
6845         if (!WhiteOnMove(currentMove)) {
6846             if (!appData.premove) {
6847                 DisplayMoveError(_("It is Black's turn"));
6848             } else if (toX >= 0 && toY >= 0) {
6849                 premoveToX = toX;
6850                 premoveToY = toY;
6851                 premoveFromX = fromX;
6852                 premoveFromY = fromY;
6853                 premovePromoChar = promoChar;
6854                 gotPremove = 1;
6855                 if (appData.debugMode)
6856                     fprintf(debugFP, "Got premove: fromX %d,"
6857                             "fromY %d, toX %d, toY %d\n",
6858                             fromX, fromY, toX, toY);
6859             }
6860             return;
6861         }
6862         break;
6863
6864       default:
6865         break;
6866
6867       case EditPosition:
6868         /* EditPosition, empty square, or different color piece;
6869            click-click move is possible */
6870         if (toX == -2 || toY == -2) {
6871             boards[0][fromY][fromX] = EmptySquare;
6872             DrawPosition(FALSE, boards[currentMove]);
6873             return;
6874         } else if (toX >= 0 && toY >= 0) {
6875             boards[0][toY][toX] = boards[0][fromY][fromX];
6876             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6877                 if(boards[0][fromY][0] != EmptySquare) {
6878                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6879                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6880                 }
6881             } else
6882             if(fromX == BOARD_RGHT+1) {
6883                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6884                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6885                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6886                 }
6887             } else
6888             boards[0][fromY][fromX] = gatingPiece;
6889             DrawPosition(FALSE, boards[currentMove]);
6890             return;
6891         }
6892         return;
6893     }
6894
6895     if(toX < 0 || toY < 0) return;
6896     pup = boards[currentMove][toY][toX];
6897
6898     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6899     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6900          if( pup != EmptySquare ) return;
6901          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6902            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6903                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6904            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6905            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6906            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6907            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6908          fromY = DROP_RANK;
6909     }
6910
6911     /* [HGM] always test for legality, to get promotion info */
6912     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6913                                          fromY, fromX, toY, toX, promoChar);
6914
6915     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6916
6917     /* [HGM] but possibly ignore an IllegalMove result */
6918     if (appData.testLegality) {
6919         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6920             DisplayMoveError(_("Illegal move"));
6921             return;
6922         }
6923     }
6924
6925     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6926         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6927              ClearPremoveHighlights(); // was included
6928         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6929         return;
6930     }
6931
6932     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6933 }
6934
6935 /* Common tail of UserMoveEvent and DropMenuEvent */
6936 int
6937 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6938 {
6939     char *bookHit = 0;
6940
6941     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6942         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6943         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6944         if(WhiteOnMove(currentMove)) {
6945             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6946         } else {
6947             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6948         }
6949     }
6950
6951     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6952        move type in caller when we know the move is a legal promotion */
6953     if(moveType == NormalMove && promoChar)
6954         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6955
6956     /* [HGM] <popupFix> The following if has been moved here from
6957        UserMoveEvent(). Because it seemed to belong here (why not allow
6958        piece drops in training games?), and because it can only be
6959        performed after it is known to what we promote. */
6960     if (gameMode == Training) {
6961       /* compare the move played on the board to the next move in the
6962        * game. If they match, display the move and the opponent's response.
6963        * If they don't match, display an error message.
6964        */
6965       int saveAnimate;
6966       Board testBoard;
6967       CopyBoard(testBoard, boards[currentMove]);
6968       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6969
6970       if (CompareBoards(testBoard, boards[currentMove+1])) {
6971         ForwardInner(currentMove+1);
6972
6973         /* Autoplay the opponent's response.
6974          * if appData.animate was TRUE when Training mode was entered,
6975          * the response will be animated.
6976          */
6977         saveAnimate = appData.animate;
6978         appData.animate = animateTraining;
6979         ForwardInner(currentMove+1);
6980         appData.animate = saveAnimate;
6981
6982         /* check for the end of the game */
6983         if (currentMove >= forwardMostMove) {
6984           gameMode = PlayFromGameFile;
6985           ModeHighlight();
6986           SetTrainingModeOff();
6987           DisplayInformation(_("End of game"));
6988         }
6989       } else {
6990         DisplayError(_("Incorrect move"), 0);
6991       }
6992       return 1;
6993     }
6994
6995   /* Ok, now we know that the move is good, so we can kill
6996      the previous line in Analysis Mode */
6997   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6998                                 && currentMove < forwardMostMove) {
6999     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7000     else forwardMostMove = currentMove;
7001   }
7002
7003   ClearMap();
7004
7005   /* If we need the chess program but it's dead, restart it */
7006   ResurrectChessProgram();
7007
7008   /* A user move restarts a paused game*/
7009   if (pausing)
7010     PauseEvent();
7011
7012   thinkOutput[0] = NULLCHAR;
7013
7014   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7015
7016   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7017     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7018     return 1;
7019   }
7020
7021   if (gameMode == BeginningOfGame) {
7022     if (appData.noChessProgram) {
7023       gameMode = EditGame;
7024       SetGameInfo();
7025     } else {
7026       char buf[MSG_SIZ];
7027       gameMode = MachinePlaysBlack;
7028       StartClocks();
7029       SetGameInfo();
7030       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7031       DisplayTitle(buf);
7032       if (first.sendName) {
7033         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7034         SendToProgram(buf, &first);
7035       }
7036       StartClocks();
7037     }
7038     ModeHighlight();
7039   }
7040
7041   /* Relay move to ICS or chess engine */
7042   if (appData.icsActive) {
7043     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7044         gameMode == IcsExamining) {
7045       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7046         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7047         SendToICS("draw ");
7048         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7049       }
7050       // also send plain move, in case ICS does not understand atomic claims
7051       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7052       ics_user_moved = 1;
7053     }
7054   } else {
7055     if (first.sendTime && (gameMode == BeginningOfGame ||
7056                            gameMode == MachinePlaysWhite ||
7057                            gameMode == MachinePlaysBlack)) {
7058       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7059     }
7060     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7061          // [HGM] book: if program might be playing, let it use book
7062         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7063         first.maybeThinking = TRUE;
7064     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7065         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7066         SendBoard(&first, currentMove+1);
7067         if(second.analyzing) {
7068             if(!second.useSetboard) SendToProgram("undo\n", &second);
7069             SendBoard(&second, currentMove+1);
7070         }
7071     } else {
7072         SendMoveToProgram(forwardMostMove-1, &first);
7073         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7074     }
7075     if (currentMove == cmailOldMove + 1) {
7076       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7077     }
7078   }
7079
7080   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081
7082   switch (gameMode) {
7083   case EditGame:
7084     if(appData.testLegality)
7085     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7086     case MT_NONE:
7087     case MT_CHECK:
7088       break;
7089     case MT_CHECKMATE:
7090     case MT_STAINMATE:
7091       if (WhiteOnMove(currentMove)) {
7092         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7093       } else {
7094         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7095       }
7096       break;
7097     case MT_STALEMATE:
7098       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7099       break;
7100     }
7101     break;
7102
7103   case MachinePlaysBlack:
7104   case MachinePlaysWhite:
7105     /* disable certain menu options while machine is thinking */
7106     SetMachineThinkingEnables();
7107     break;
7108
7109   default:
7110     break;
7111   }
7112
7113   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7114   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7115
7116   if(bookHit) { // [HGM] book: simulate book reply
7117         static char bookMove[MSG_SIZ]; // a bit generous?
7118
7119         programStats.nodes = programStats.depth = programStats.time =
7120         programStats.score = programStats.got_only_move = 0;
7121         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7122
7123         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7124         strcat(bookMove, bookHit);
7125         HandleMachineMove(bookMove, &first);
7126   }
7127   return 1;
7128 }
7129
7130 void
7131 MarkByFEN(char *fen)
7132 {
7133         int r, f;
7134         if(!appData.markers || !appData.highlightDragging) return;
7135         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7136         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7137         while(*fen) {
7138             int s = 0;
7139             marker[r][f] = 0;
7140             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7141             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7142             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7143             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7144             if(*fen == 'T') marker[r][f++] = 0; else
7145             if(*fen == 'Y') marker[r][f++] = 1; else
7146             if(*fen == 'G') marker[r][f++] = 3; else
7147             if(*fen == 'B') marker[r][f++] = 4; else
7148             if(*fen == 'C') marker[r][f++] = 5; else
7149             if(*fen == 'M') marker[r][f++] = 6; else
7150             if(*fen == 'W') marker[r][f++] = 7; else
7151             if(*fen == 'D') marker[r][f++] = 8; else
7152             if(*fen == 'R') marker[r][f++] = 2; else {
7153                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7154               f += s; fen -= s>0;
7155             }
7156             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7157             if(r < 0) break;
7158             fen++;
7159         }
7160         DrawPosition(TRUE, NULL);
7161 }
7162
7163 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7164
7165 void
7166 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7167 {
7168     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7169     Markers *m = (Markers *) closure;
7170     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7171         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7172                          || kind == WhiteCapturesEnPassant
7173                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7174     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7175 }
7176
7177 void
7178 MarkTargetSquares (int clear)
7179 {
7180   int x, y, sum=0;
7181   if(clear) { // no reason to ever suppress clearing
7182     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;
7183     if(!sum) return; // nothing was cleared,no redraw needed
7184   } else {
7185     int capt = 0;
7186     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7187        !appData.testLegality || gameMode == EditPosition) return;
7188     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7189     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7190       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7191       if(capt)
7192       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7193     }
7194   }
7195   DrawPosition(FALSE, NULL);
7196 }
7197
7198 int
7199 Explode (Board board, int fromX, int fromY, int toX, int toY)
7200 {
7201     if(gameInfo.variant == VariantAtomic &&
7202        (board[toY][toX] != EmptySquare ||                     // capture?
7203         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7204                          board[fromY][fromX] == BlackPawn   )
7205       )) {
7206         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7207         return TRUE;
7208     }
7209     return FALSE;
7210 }
7211
7212 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7213
7214 int
7215 CanPromote (ChessSquare piece, int y)
7216 {
7217         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7218         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7219         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7220         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7221            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7222            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7223          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7224         return (piece == BlackPawn && y <= zone ||
7225                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7226                 piece == BlackLance && y == 1 ||
7227                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7228 }
7229
7230 void
7231 HoverEvent (int xPix, int yPix, int x, int y)
7232 {
7233         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7234         int r, f;
7235         if(!first.highlight) return;
7236         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7237         if(x == oldX && y == oldY) return; // only do something if we enter new square
7238         oldFromX = fromX; oldFromY = fromY;
7239         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7240           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7241             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7242         else if(oldX != x || oldY != y) {
7243           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7244           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7245             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7246           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7247             char buf[MSG_SIZ];
7248             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7249             SendToProgram(buf, &first);
7250           }
7251           oldX = x; oldY = y;
7252 //        SetHighlights(fromX, fromY, x, y);
7253         }
7254 }
7255
7256 void ReportClick(char *action, int x, int y)
7257 {
7258         char buf[MSG_SIZ]; // Inform engine of what user does
7259         int r, f;
7260         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7261           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7262         if(!first.highlight || gameMode == EditPosition) return;
7263         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7264         SendToProgram(buf, &first);
7265 }
7266
7267 void
7268 LeftClick (ClickType clickType, int xPix, int yPix)
7269 {
7270     int x, y;
7271     Boolean saveAnimate;
7272     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7273     char promoChoice = NULLCHAR;
7274     ChessSquare piece;
7275     static TimeMark lastClickTime, prevClickTime;
7276
7277     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7278
7279     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7280
7281     if (clickType == Press) ErrorPopDown();
7282     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7283
7284     x = EventToSquare(xPix, BOARD_WIDTH);
7285     y = EventToSquare(yPix, BOARD_HEIGHT);
7286     if (!flipView && y >= 0) {
7287         y = BOARD_HEIGHT - 1 - y;
7288     }
7289     if (flipView && x >= 0) {
7290         x = BOARD_WIDTH - 1 - x;
7291     }
7292
7293     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7294         defaultPromoChoice = promoSweep;
7295         promoSweep = EmptySquare;   // terminate sweep
7296         promoDefaultAltered = TRUE;
7297         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7298     }
7299
7300     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7301         if(clickType == Release) return; // ignore upclick of click-click destination
7302         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7303         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7304         if(gameInfo.holdingsWidth &&
7305                 (WhiteOnMove(currentMove)
7306                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7307                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7308             // click in right holdings, for determining promotion piece
7309             ChessSquare p = boards[currentMove][y][x];
7310             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7311             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7312             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7313                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7314                 fromX = fromY = -1;
7315                 return;
7316             }
7317         }
7318         DrawPosition(FALSE, boards[currentMove]);
7319         return;
7320     }
7321
7322     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7323     if(clickType == Press
7324             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7325               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7326               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7327         return;
7328
7329     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7330         // could be static click on premove from-square: abort premove
7331         gotPremove = 0;
7332         ClearPremoveHighlights();
7333     }
7334
7335     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7336         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7337
7338     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7339         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7340                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7341         defaultPromoChoice = DefaultPromoChoice(side);
7342     }
7343
7344     autoQueen = appData.alwaysPromoteToQueen;
7345
7346     if (fromX == -1) {
7347       int originalY = y;
7348       gatingPiece = EmptySquare;
7349       if (clickType != Press) {
7350         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7351             DragPieceEnd(xPix, yPix); dragging = 0;
7352             DrawPosition(FALSE, NULL);
7353         }
7354         return;
7355       }
7356       doubleClick = FALSE;
7357       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7358         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7359       }
7360       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7361       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7362          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7363          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7364             /* First square */
7365             if (OKToStartUserMove(fromX, fromY)) {
7366                 second = 0;
7367                 ReportClick("lift", x, y);
7368                 MarkTargetSquares(0);
7369                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7370                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7371                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7372                     promoSweep = defaultPromoChoice;
7373                     selectFlag = 0; lastX = xPix; lastY = yPix;
7374                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7375                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7376                 }
7377                 if (appData.highlightDragging) {
7378                     SetHighlights(fromX, fromY, -1, -1);
7379                 } else {
7380                     ClearHighlights();
7381                 }
7382             } else fromX = fromY = -1;
7383             return;
7384         }
7385     }
7386
7387     /* fromX != -1 */
7388     if (clickType == Press && gameMode != EditPosition) {
7389         ChessSquare fromP;
7390         ChessSquare toP;
7391         int frc;
7392
7393         // ignore off-board to clicks
7394         if(y < 0 || x < 0) return;
7395
7396         /* Check if clicking again on the same color piece */
7397         fromP = boards[currentMove][fromY][fromX];
7398         toP = boards[currentMove][y][x];
7399         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7400         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7401            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7402              WhitePawn <= toP && toP <= WhiteKing &&
7403              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7404              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7405             (BlackPawn <= fromP && fromP <= BlackKing &&
7406              BlackPawn <= toP && toP <= BlackKing &&
7407              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7408              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7409             /* Clicked again on same color piece -- changed his mind */
7410             second = (x == fromX && y == fromY);
7411             killX = killY = -1;
7412             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7413                 second = FALSE; // first double-click rather than scond click
7414                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7415             }
7416             promoDefaultAltered = FALSE;
7417             MarkTargetSquares(1);
7418            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7419             if (appData.highlightDragging) {
7420                 SetHighlights(x, y, -1, -1);
7421             } else {
7422                 ClearHighlights();
7423             }
7424             if (OKToStartUserMove(x, y)) {
7425                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7426                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7427                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7428                  gatingPiece = boards[currentMove][fromY][fromX];
7429                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7430                 fromX = x;
7431                 fromY = y; dragging = 1;
7432                 ReportClick("lift", x, y);
7433                 MarkTargetSquares(0);
7434                 DragPieceBegin(xPix, yPix, FALSE);
7435                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7436                     promoSweep = defaultPromoChoice;
7437                     selectFlag = 0; lastX = xPix; lastY = yPix;
7438                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7439                 }
7440             }
7441            }
7442            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7443            second = FALSE;
7444         }
7445         // ignore clicks on holdings
7446         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7447     }
7448
7449     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7450         DragPieceEnd(xPix, yPix); dragging = 0;
7451         if(clearFlag) {
7452             // a deferred attempt to click-click move an empty square on top of a piece
7453             boards[currentMove][y][x] = EmptySquare;
7454             ClearHighlights();
7455             DrawPosition(FALSE, boards[currentMove]);
7456             fromX = fromY = -1; clearFlag = 0;
7457             return;
7458         }
7459         if (appData.animateDragging) {
7460             /* Undo animation damage if any */
7461             DrawPosition(FALSE, NULL);
7462         }
7463         if (second || sweepSelecting) {
7464             /* Second up/down in same square; just abort move */
7465             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7466             second = sweepSelecting = 0;
7467             fromX = fromY = -1;
7468             gatingPiece = EmptySquare;
7469             MarkTargetSquares(1);
7470             ClearHighlights();
7471             gotPremove = 0;
7472             ClearPremoveHighlights();
7473         } else {
7474             /* First upclick in same square; start click-click mode */
7475             SetHighlights(x, y, -1, -1);
7476         }
7477         return;
7478     }
7479
7480     clearFlag = 0;
7481
7482     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7483         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7484         DisplayMessage(_("only marked squares are legal"),"");
7485         DrawPosition(TRUE, NULL);
7486         return; // ignore to-click
7487     }
7488
7489     /* we now have a different from- and (possibly off-board) to-square */
7490     /* Completed move */
7491     if(!sweepSelecting) {
7492         toX = x;
7493         toY = y;
7494     }
7495
7496     piece = boards[currentMove][fromY][fromX];
7497
7498     saveAnimate = appData.animate;
7499     if (clickType == Press) {
7500         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7501         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7502             // must be Edit Position mode with empty-square selected
7503             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7504             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7505             return;
7506         }
7507         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7508             return;
7509         }
7510         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7511             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7512         } else
7513         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7514         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7515           if(appData.sweepSelect) {
7516             promoSweep = defaultPromoChoice;
7517             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7518             selectFlag = 0; lastX = xPix; lastY = yPix;
7519             Sweep(0); // Pawn that is going to promote: preview promotion piece
7520             sweepSelecting = 1;
7521             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7522             MarkTargetSquares(1);
7523           }
7524           return; // promo popup appears on up-click
7525         }
7526         /* Finish clickclick move */
7527         if (appData.animate || appData.highlightLastMove) {
7528             SetHighlights(fromX, fromY, toX, toY);
7529         } else {
7530             ClearHighlights();
7531         }
7532     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7533         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7534         if (appData.animate || appData.highlightLastMove) {
7535             SetHighlights(fromX, fromY, toX, toY);
7536         } else {
7537             ClearHighlights();
7538         }
7539     } else {
7540 #if 0
7541 // [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
7542         /* Finish drag move */
7543         if (appData.highlightLastMove) {
7544             SetHighlights(fromX, fromY, toX, toY);
7545         } else {
7546             ClearHighlights();
7547         }
7548 #endif
7549         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7550         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7551           dragging *= 2;            // flag button-less dragging if we are dragging
7552           MarkTargetSquares(1);
7553           if(x == killX && y == killY) killX = killY = -1; else {
7554             killX = x; killY = y;     //remeber this square as intermediate
7555             ReportClick("put", x, y); // and inform engine
7556             ReportClick("lift", x, y);
7557             MarkTargetSquares(0);
7558             return;
7559           }
7560         }
7561         DragPieceEnd(xPix, yPix); dragging = 0;
7562         /* Don't animate move and drag both */
7563         appData.animate = FALSE;
7564     }
7565
7566     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7567     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7568         ChessSquare piece = boards[currentMove][fromY][fromX];
7569         if(gameMode == EditPosition && piece != EmptySquare &&
7570            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7571             int n;
7572
7573             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7574                 n = PieceToNumber(piece - (int)BlackPawn);
7575                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7576                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7577                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7578             } else
7579             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7580                 n = PieceToNumber(piece);
7581                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7582                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7583                 boards[currentMove][n][BOARD_WIDTH-2]++;
7584             }
7585             boards[currentMove][fromY][fromX] = EmptySquare;
7586         }
7587         ClearHighlights();
7588         fromX = fromY = -1;
7589         MarkTargetSquares(1);
7590         DrawPosition(TRUE, boards[currentMove]);
7591         return;
7592     }
7593
7594     // off-board moves should not be highlighted
7595     if(x < 0 || y < 0) ClearHighlights();
7596     else ReportClick("put", x, y);
7597
7598     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7599
7600     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7601         SetHighlights(fromX, fromY, toX, toY);
7602         MarkTargetSquares(1);
7603         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7604             // [HGM] super: promotion to captured piece selected from holdings
7605             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7606             promotionChoice = TRUE;
7607             // kludge follows to temporarily execute move on display, without promoting yet
7608             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7609             boards[currentMove][toY][toX] = p;
7610             DrawPosition(FALSE, boards[currentMove]);
7611             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7612             boards[currentMove][toY][toX] = q;
7613             DisplayMessage("Click in holdings to choose piece", "");
7614             return;
7615         }
7616         PromotionPopUp(promoChoice);
7617     } else {
7618         int oldMove = currentMove;
7619         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7620         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7621         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7622         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7623            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7624             DrawPosition(TRUE, boards[currentMove]);
7625         MarkTargetSquares(1);
7626         fromX = fromY = -1;
7627     }
7628     appData.animate = saveAnimate;
7629     if (appData.animate || appData.animateDragging) {
7630         /* Undo animation damage if needed */
7631         DrawPosition(FALSE, NULL);
7632     }
7633 }
7634
7635 int
7636 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7637 {   // front-end-free part taken out of PieceMenuPopup
7638     int whichMenu; int xSqr, ySqr;
7639
7640     if(seekGraphUp) { // [HGM] seekgraph
7641         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7642         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7643         return -2;
7644     }
7645
7646     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7647          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7648         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7649         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7650         if(action == Press)   {
7651             originalFlip = flipView;
7652             flipView = !flipView; // temporarily flip board to see game from partners perspective
7653             DrawPosition(TRUE, partnerBoard);
7654             DisplayMessage(partnerStatus, "");
7655             partnerUp = TRUE;
7656         } else if(action == Release) {
7657             flipView = originalFlip;
7658             DrawPosition(TRUE, boards[currentMove]);
7659             partnerUp = FALSE;
7660         }
7661         return -2;
7662     }
7663
7664     xSqr = EventToSquare(x, BOARD_WIDTH);
7665     ySqr = EventToSquare(y, BOARD_HEIGHT);
7666     if (action == Release) {
7667         if(pieceSweep != EmptySquare) {
7668             EditPositionMenuEvent(pieceSweep, toX, toY);
7669             pieceSweep = EmptySquare;
7670         } else UnLoadPV(); // [HGM] pv
7671     }
7672     if (action != Press) return -2; // return code to be ignored
7673     switch (gameMode) {
7674       case IcsExamining:
7675         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7676       case EditPosition:
7677         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7678         if (xSqr < 0 || ySqr < 0) return -1;
7679         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7680         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7681         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7682         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7683         NextPiece(0);
7684         return 2; // grab
7685       case IcsObserving:
7686         if(!appData.icsEngineAnalyze) return -1;
7687       case IcsPlayingWhite:
7688       case IcsPlayingBlack:
7689         if(!appData.zippyPlay) goto noZip;
7690       case AnalyzeMode:
7691       case AnalyzeFile:
7692       case MachinePlaysWhite:
7693       case MachinePlaysBlack:
7694       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7695         if (!appData.dropMenu) {
7696           LoadPV(x, y);
7697           return 2; // flag front-end to grab mouse events
7698         }
7699         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7700            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7701       case EditGame:
7702       noZip:
7703         if (xSqr < 0 || ySqr < 0) return -1;
7704         if (!appData.dropMenu || appData.testLegality &&
7705             gameInfo.variant != VariantBughouse &&
7706             gameInfo.variant != VariantCrazyhouse) return -1;
7707         whichMenu = 1; // drop menu
7708         break;
7709       default:
7710         return -1;
7711     }
7712
7713     if (((*fromX = xSqr) < 0) ||
7714         ((*fromY = ySqr) < 0)) {
7715         *fromX = *fromY = -1;
7716         return -1;
7717     }
7718     if (flipView)
7719       *fromX = BOARD_WIDTH - 1 - *fromX;
7720     else
7721       *fromY = BOARD_HEIGHT - 1 - *fromY;
7722
7723     return whichMenu;
7724 }
7725
7726 void
7727 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7728 {
7729 //    char * hint = lastHint;
7730     FrontEndProgramStats stats;
7731
7732     stats.which = cps == &first ? 0 : 1;
7733     stats.depth = cpstats->depth;
7734     stats.nodes = cpstats->nodes;
7735     stats.score = cpstats->score;
7736     stats.time = cpstats->time;
7737     stats.pv = cpstats->movelist;
7738     stats.hint = lastHint;
7739     stats.an_move_index = 0;
7740     stats.an_move_count = 0;
7741
7742     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7743         stats.hint = cpstats->move_name;
7744         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7745         stats.an_move_count = cpstats->nr_moves;
7746     }
7747
7748     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
7749
7750     SetProgramStats( &stats );
7751 }
7752
7753 void
7754 ClearEngineOutputPane (int which)
7755 {
7756     static FrontEndProgramStats dummyStats;
7757     dummyStats.which = which;
7758     dummyStats.pv = "#";
7759     SetProgramStats( &dummyStats );
7760 }
7761
7762 #define MAXPLAYERS 500
7763
7764 char *
7765 TourneyStandings (int display)
7766 {
7767     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7768     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7769     char result, *p, *names[MAXPLAYERS];
7770
7771     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7772         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7773     names[0] = p = strdup(appData.participants);
7774     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7775
7776     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7777
7778     while(result = appData.results[nr]) {
7779         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7780         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7781         wScore = bScore = 0;
7782         switch(result) {
7783           case '+': wScore = 2; break;
7784           case '-': bScore = 2; break;
7785           case '=': wScore = bScore = 1; break;
7786           case ' ':
7787           case '*': return strdup("busy"); // tourney not finished
7788         }
7789         score[w] += wScore;
7790         score[b] += bScore;
7791         games[w]++;
7792         games[b]++;
7793         nr++;
7794     }
7795     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7796     for(w=0; w<nPlayers; w++) {
7797         bScore = -1;
7798         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7799         ranking[w] = b; points[w] = bScore; score[b] = -2;
7800     }
7801     p = malloc(nPlayers*34+1);
7802     for(w=0; w<nPlayers && w<display; w++)
7803         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7804     free(names[0]);
7805     return p;
7806 }
7807
7808 void
7809 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7810 {       // count all piece types
7811         int p, f, r;
7812         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7813         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7814         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7815                 p = board[r][f];
7816                 pCnt[p]++;
7817                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7818                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7819                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7820                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7821                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7822                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7823         }
7824 }
7825
7826 int
7827 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7828 {
7829         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7830         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7831
7832         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7833         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7834         if(myPawns == 2 && nMine == 3) // KPP
7835             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7836         if(myPawns == 1 && nMine == 2) // KP
7837             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7838         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7839             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7840         if(myPawns) return FALSE;
7841         if(pCnt[WhiteRook+side])
7842             return pCnt[BlackRook-side] ||
7843                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7844                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7845                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7846         if(pCnt[WhiteCannon+side]) {
7847             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7848             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7849         }
7850         if(pCnt[WhiteKnight+side])
7851             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7852         return FALSE;
7853 }
7854
7855 int
7856 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7857 {
7858         VariantClass v = gameInfo.variant;
7859
7860         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7861         if(v == VariantShatranj) return TRUE; // always winnable through baring
7862         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7863         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7864
7865         if(v == VariantXiangqi) {
7866                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7867
7868                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7869                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7870                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7871                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7872                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7873                 if(stale) // we have at least one last-rank P plus perhaps C
7874                     return majors // KPKX
7875                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7876                 else // KCA*E*
7877                     return pCnt[WhiteFerz+side] // KCAK
7878                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7879                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7880                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7881
7882         } else if(v == VariantKnightmate) {
7883                 if(nMine == 1) return FALSE;
7884                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7885         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7886                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7887
7888                 if(nMine == 1) return FALSE; // bare King
7889                 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
7890                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7891                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7892                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7893                 if(pCnt[WhiteKnight+side])
7894                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7895                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7896                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7897                 if(nBishops)
7898                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7899                 if(pCnt[WhiteAlfil+side])
7900                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7901                 if(pCnt[WhiteWazir+side])
7902                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7903         }
7904
7905         return TRUE;
7906 }
7907
7908 int
7909 CompareWithRights (Board b1, Board b2)
7910 {
7911     int rights = 0;
7912     if(!CompareBoards(b1, b2)) return FALSE;
7913     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7914     /* compare castling rights */
7915     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7916            rights++; /* King lost rights, while rook still had them */
7917     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7918         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7919            rights++; /* but at least one rook lost them */
7920     }
7921     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7922            rights++;
7923     if( b1[CASTLING][5] != NoRights ) {
7924         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7925            rights++;
7926     }
7927     return rights == 0;
7928 }
7929
7930 int
7931 Adjudicate (ChessProgramState *cps)
7932 {       // [HGM] some adjudications useful with buggy engines
7933         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7934         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7935         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7936         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7937         int k, drop, count = 0; static int bare = 1;
7938         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7939         Boolean canAdjudicate = !appData.icsActive;
7940
7941         // most tests only when we understand the game, i.e. legality-checking on
7942             if( appData.testLegality )
7943             {   /* [HGM] Some more adjudications for obstinate engines */
7944                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7945                 static int moveCount = 6;
7946                 ChessMove result;
7947                 char *reason = NULL;
7948
7949                 /* Count what is on board. */
7950                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7951
7952                 /* Some material-based adjudications that have to be made before stalemate test */
7953                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7954                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7955                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7956                      if(canAdjudicate && appData.checkMates) {
7957                          if(engineOpponent)
7958                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7959                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7960                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7961                          return 1;
7962                      }
7963                 }
7964
7965                 /* Bare King in Shatranj (loses) or Losers (wins) */
7966                 if( nrW == 1 || nrB == 1) {
7967                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7968                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7969                      if(canAdjudicate && appData.checkMates) {
7970                          if(engineOpponent)
7971                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7972                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7973                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7974                          return 1;
7975                      }
7976                   } else
7977                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7978                   {    /* bare King */
7979                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7980                         if(canAdjudicate && appData.checkMates) {
7981                             /* but only adjudicate if adjudication enabled */
7982                             if(engineOpponent)
7983                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7984                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7985                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7986                             return 1;
7987                         }
7988                   }
7989                 } else bare = 1;
7990
7991
7992             // don't wait for engine to announce game end if we can judge ourselves
7993             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7994               case MT_CHECK:
7995                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7996                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7997                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7998                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7999                             checkCnt++;
8000                         if(checkCnt >= 2) {
8001                             reason = "Xboard adjudication: 3rd check";
8002                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8003                             break;
8004                         }
8005                     }
8006                 }
8007               case MT_NONE:
8008               default:
8009                 break;
8010               case MT_STALEMATE:
8011               case MT_STAINMATE:
8012                 reason = "Xboard adjudication: Stalemate";
8013                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8014                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8015                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8016                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8017                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8018                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8019                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8020                                                                         EP_CHECKMATE : EP_WINS);
8021                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8022                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8023                 }
8024                 break;
8025               case MT_CHECKMATE:
8026                 reason = "Xboard adjudication: Checkmate";
8027                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8028                 if(gameInfo.variant == VariantShogi) {
8029                     if(forwardMostMove > backwardMostMove
8030                        && moveList[forwardMostMove-1][1] == '@'
8031                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8032                         reason = "XBoard adjudication: pawn-drop mate";
8033                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8034                     }
8035                 }
8036                 break;
8037             }
8038
8039                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8040                     case EP_STALEMATE:
8041                         result = GameIsDrawn; break;
8042                     case EP_CHECKMATE:
8043                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8044                     case EP_WINS:
8045                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8046                     default:
8047                         result = EndOfFile;
8048                 }
8049                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8050                     if(engineOpponent)
8051                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8052                     GameEnds( result, reason, GE_XBOARD );
8053                     return 1;
8054                 }
8055
8056                 /* Next absolutely insufficient mating material. */
8057                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8058                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8059                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8060
8061                      /* always flag draws, for judging claims */
8062                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8063
8064                      if(canAdjudicate && appData.materialDraws) {
8065                          /* but only adjudicate them if adjudication enabled */
8066                          if(engineOpponent) {
8067                            SendToProgram("force\n", engineOpponent); // suppress reply
8068                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8069                          }
8070                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8071                          return 1;
8072                      }
8073                 }
8074
8075                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8076                 if(gameInfo.variant == VariantXiangqi ?
8077                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8078                  : nrW + nrB == 4 &&
8079                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8080                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8081                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8082                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8083                    ) ) {
8084                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8085                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8086                           if(engineOpponent) {
8087                             SendToProgram("force\n", engineOpponent); // suppress reply
8088                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8089                           }
8090                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8091                           return 1;
8092                      }
8093                 } else moveCount = 6;
8094             }
8095
8096         // Repetition draws and 50-move rule can be applied independently of legality testing
8097
8098                 /* Check for rep-draws */
8099                 count = 0;
8100                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8101                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8102                 for(k = forwardMostMove-2;
8103                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8104                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8105                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8106                     k-=2)
8107                 {   int rights=0;
8108                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8109                         /* compare castling rights */
8110                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8111                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8112                                 rights++; /* King lost rights, while rook still had them */
8113                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8114                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8115                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8116                                    rights++; /* but at least one rook lost them */
8117                         }
8118                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8119                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8120                                 rights++;
8121                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8122                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8123                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8124                                    rights++;
8125                         }
8126                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8127                             && appData.drawRepeats > 1) {
8128                              /* adjudicate after user-specified nr of repeats */
8129                              int result = GameIsDrawn;
8130                              char *details = "XBoard adjudication: repetition draw";
8131                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8132                                 // [HGM] xiangqi: check for forbidden perpetuals
8133                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8134                                 for(m=forwardMostMove; m>k; m-=2) {
8135                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8136                                         ourPerpetual = 0; // the current mover did not always check
8137                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8138                                         hisPerpetual = 0; // the opponent did not always check
8139                                 }
8140                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8141                                                                         ourPerpetual, hisPerpetual);
8142                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8143                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8144                                     details = "Xboard adjudication: perpetual checking";
8145                                 } else
8146                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8147                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8148                                 } else
8149                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8150                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8151                                         result = BlackWins;
8152                                         details = "Xboard adjudication: repetition";
8153                                     }
8154                                 } else // it must be XQ
8155                                 // Now check for perpetual chases
8156                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8157                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8158                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8159                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8160                                         static char resdet[MSG_SIZ];
8161                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8162                                         details = resdet;
8163                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8164                                     } else
8165                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8166                                         break; // Abort repetition-checking loop.
8167                                 }
8168                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8169                              }
8170                              if(engineOpponent) {
8171                                SendToProgram("force\n", engineOpponent); // suppress reply
8172                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8173                              }
8174                              GameEnds( result, details, GE_XBOARD );
8175                              return 1;
8176                         }
8177                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8178                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8179                     }
8180                 }
8181
8182                 /* Now we test for 50-move draws. Determine ply count */
8183                 count = forwardMostMove;
8184                 /* look for last irreversble move */
8185                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8186                     count--;
8187                 /* if we hit starting position, add initial plies */
8188                 if( count == backwardMostMove )
8189                     count -= initialRulePlies;
8190                 count = forwardMostMove - count;
8191                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8192                         // adjust reversible move counter for checks in Xiangqi
8193                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8194                         if(i < backwardMostMove) i = backwardMostMove;
8195                         while(i <= forwardMostMove) {
8196                                 lastCheck = inCheck; // check evasion does not count
8197                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8198                                 if(inCheck || lastCheck) count--; // check does not count
8199                                 i++;
8200                         }
8201                 }
8202                 if( count >= 100)
8203                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8204                          /* this is used to judge if draw claims are legal */
8205                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8206                          if(engineOpponent) {
8207                            SendToProgram("force\n", engineOpponent); // suppress reply
8208                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8209                          }
8210                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8211                          return 1;
8212                 }
8213
8214                 /* if draw offer is pending, treat it as a draw claim
8215                  * when draw condition present, to allow engines a way to
8216                  * claim draws before making their move to avoid a race
8217                  * condition occurring after their move
8218                  */
8219                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8220                          char *p = NULL;
8221                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8222                              p = "Draw claim: 50-move rule";
8223                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8224                              p = "Draw claim: 3-fold repetition";
8225                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8226                              p = "Draw claim: insufficient mating material";
8227                          if( p != NULL && canAdjudicate) {
8228                              if(engineOpponent) {
8229                                SendToProgram("force\n", engineOpponent); // suppress reply
8230                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8231                              }
8232                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8233                              return 1;
8234                          }
8235                 }
8236
8237                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8238                     if(engineOpponent) {
8239                       SendToProgram("force\n", engineOpponent); // suppress reply
8240                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8241                     }
8242                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8243                     return 1;
8244                 }
8245         return 0;
8246 }
8247
8248 char *
8249 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8250 {   // [HGM] book: this routine intercepts moves to simulate book replies
8251     char *bookHit = NULL;
8252
8253     //first determine if the incoming move brings opponent into his book
8254     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8255         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8256     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8257     if(bookHit != NULL && !cps->bookSuspend) {
8258         // make sure opponent is not going to reply after receiving move to book position
8259         SendToProgram("force\n", cps);
8260         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8261     }
8262     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8263     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8264     // now arrange restart after book miss
8265     if(bookHit) {
8266         // after a book hit we never send 'go', and the code after the call to this routine
8267         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8268         char buf[MSG_SIZ], *move = bookHit;
8269         if(cps->useSAN) {
8270             int fromX, fromY, toX, toY;
8271             char promoChar;
8272             ChessMove moveType;
8273             move = buf + 30;
8274             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8275                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8276                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8277                                     PosFlags(forwardMostMove),
8278                                     fromY, fromX, toY, toX, promoChar, move);
8279             } else {
8280                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8281                 bookHit = NULL;
8282             }
8283         }
8284         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8285         SendToProgram(buf, cps);
8286         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8287     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8288         SendToProgram("go\n", cps);
8289         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8290     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8291         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8292             SendToProgram("go\n", cps);
8293         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8294     }
8295     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8296 }
8297
8298 int
8299 LoadError (char *errmess, ChessProgramState *cps)
8300 {   // unloads engine and switches back to -ncp mode if it was first
8301     if(cps->initDone) return FALSE;
8302     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8303     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8304     cps->pr = NoProc;
8305     if(cps == &first) {
8306         appData.noChessProgram = TRUE;
8307         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8308         gameMode = BeginningOfGame; ModeHighlight();
8309         SetNCPMode();
8310     }
8311     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8312     DisplayMessage("", ""); // erase waiting message
8313     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8314     return TRUE;
8315 }
8316
8317 char *savedMessage;
8318 ChessProgramState *savedState;
8319 void
8320 DeferredBookMove (void)
8321 {
8322         if(savedState->lastPing != savedState->lastPong)
8323                     ScheduleDelayedEvent(DeferredBookMove, 10);
8324         else
8325         HandleMachineMove(savedMessage, savedState);
8326 }
8327
8328 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8329 static ChessProgramState *stalledEngine;
8330 static char stashedInputMove[MSG_SIZ];
8331
8332 void
8333 HandleMachineMove (char *message, ChessProgramState *cps)
8334 {
8335     static char firstLeg[20];
8336     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8337     char realname[MSG_SIZ];
8338     int fromX, fromY, toX, toY;
8339     ChessMove moveType;
8340     char promoChar, roar;
8341     char *p, *pv=buf1;
8342     int machineWhite, oldError;
8343     char *bookHit;
8344
8345     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8346         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8347         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8348             DisplayError(_("Invalid pairing from pairing engine"), 0);
8349             return;
8350         }
8351         pairingReceived = 1;
8352         NextMatchGame();
8353         return; // Skim the pairing messages here.
8354     }
8355
8356     oldError = cps->userError; cps->userError = 0;
8357
8358 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8359     /*
8360      * Kludge to ignore BEL characters
8361      */
8362     while (*message == '\007') message++;
8363
8364     /*
8365      * [HGM] engine debug message: ignore lines starting with '#' character
8366      */
8367     if(cps->debug && *message == '#') return;
8368
8369     /*
8370      * Look for book output
8371      */
8372     if (cps == &first && bookRequested) {
8373         if (message[0] == '\t' || message[0] == ' ') {
8374             /* Part of the book output is here; append it */
8375             strcat(bookOutput, message);
8376             strcat(bookOutput, "  \n");
8377             return;
8378         } else if (bookOutput[0] != NULLCHAR) {
8379             /* All of book output has arrived; display it */
8380             char *p = bookOutput;
8381             while (*p != NULLCHAR) {
8382                 if (*p == '\t') *p = ' ';
8383                 p++;
8384             }
8385             DisplayInformation(bookOutput);
8386             bookRequested = FALSE;
8387             /* Fall through to parse the current output */
8388         }
8389     }
8390
8391     /*
8392      * Look for machine move.
8393      */
8394     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8395         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8396     {
8397         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8398             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8399             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8400             stalledEngine = cps;
8401             if(appData.ponderNextMove) { // bring opponent out of ponder
8402                 if(gameMode == TwoMachinesPlay) {
8403                     if(cps->other->pause)
8404                         PauseEngine(cps->other);
8405                     else
8406                         SendToProgram("easy\n", cps->other);
8407                 }
8408             }
8409             StopClocks();
8410             return;
8411         }
8412
8413         /* This method is only useful on engines that support ping */
8414         if (cps->lastPing != cps->lastPong) {
8415           if (gameMode == BeginningOfGame) {
8416             /* Extra move from before last new; ignore */
8417             if (appData.debugMode) {
8418                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8419             }
8420           } else {
8421             if (appData.debugMode) {
8422                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8423                         cps->which, gameMode);
8424             }
8425
8426             SendToProgram("undo\n", cps);
8427           }
8428           return;
8429         }
8430
8431         switch (gameMode) {
8432           case BeginningOfGame:
8433             /* Extra move from before last reset; ignore */
8434             if (appData.debugMode) {
8435                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8436             }
8437             return;
8438
8439           case EndOfGame:
8440           case IcsIdle:
8441           default:
8442             /* Extra move after we tried to stop.  The mode test is
8443                not a reliable way of detecting this problem, but it's
8444                the best we can do on engines that don't support ping.
8445             */
8446             if (appData.debugMode) {
8447                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8448                         cps->which, gameMode);
8449             }
8450             SendToProgram("undo\n", cps);
8451             return;
8452
8453           case MachinePlaysWhite:
8454           case IcsPlayingWhite:
8455             machineWhite = TRUE;
8456             break;
8457
8458           case MachinePlaysBlack:
8459           case IcsPlayingBlack:
8460             machineWhite = FALSE;
8461             break;
8462
8463           case TwoMachinesPlay:
8464             machineWhite = (cps->twoMachinesColor[0] == 'w');
8465             break;
8466         }
8467         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8468             if (appData.debugMode) {
8469                 fprintf(debugFP,
8470                         "Ignoring move out of turn by %s, gameMode %d"
8471                         ", forwardMost %d\n",
8472                         cps->which, gameMode, forwardMostMove);
8473             }
8474             return;
8475         }
8476
8477         if(cps->alphaRank) AlphaRank(machineMove, 4);
8478
8479         // [HGM] lion: (some very limited) support for Alien protocol
8480         killX = killY = -1;
8481         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8482             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8483             return;
8484         } else if(firstLeg[0]) { // there was a previous leg;
8485             // only support case where same piece makes two step (and don't even test that!)
8486             char buf[20], *p = machineMove+1, *q = buf+1, f;
8487             safeStrCpy(buf, machineMove, 20);
8488             while(isdigit(*q)) q++; // find start of to-square
8489             safeStrCpy(machineMove, firstLeg, 20);
8490             while(isdigit(*p)) p++;
8491             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8492             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8493             firstLeg[0] = NULLCHAR;
8494         }
8495
8496         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8497                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8498             /* Machine move could not be parsed; ignore it. */
8499           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8500                     machineMove, _(cps->which));
8501             DisplayMoveError(buf1);
8502             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8503                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8504             if (gameMode == TwoMachinesPlay) {
8505               GameEnds(machineWhite ? BlackWins : WhiteWins,
8506                        buf1, GE_XBOARD);
8507             }
8508             return;
8509         }
8510
8511         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8512         /* So we have to redo legality test with true e.p. status here,  */
8513         /* to make sure an illegal e.p. capture does not slip through,   */
8514         /* to cause a forfeit on a justified illegal-move complaint      */
8515         /* of the opponent.                                              */
8516         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8517            ChessMove moveType;
8518            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8519                              fromY, fromX, toY, toX, promoChar);
8520             if(moveType == IllegalMove) {
8521               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8522                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8523                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8524                            buf1, GE_XBOARD);
8525                 return;
8526            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8527            /* [HGM] Kludge to handle engines that send FRC-style castling
8528               when they shouldn't (like TSCP-Gothic) */
8529            switch(moveType) {
8530              case WhiteASideCastleFR:
8531              case BlackASideCastleFR:
8532                toX+=2;
8533                currentMoveString[2]++;
8534                break;
8535              case WhiteHSideCastleFR:
8536              case BlackHSideCastleFR:
8537                toX--;
8538                currentMoveString[2]--;
8539                break;
8540              default: ; // nothing to do, but suppresses warning of pedantic compilers
8541            }
8542         }
8543         hintRequested = FALSE;
8544         lastHint[0] = NULLCHAR;
8545         bookRequested = FALSE;
8546         /* Program may be pondering now */
8547         cps->maybeThinking = TRUE;
8548         if (cps->sendTime == 2) cps->sendTime = 1;
8549         if (cps->offeredDraw) cps->offeredDraw--;
8550
8551         /* [AS] Save move info*/
8552         pvInfoList[ forwardMostMove ].score = programStats.score;
8553         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8554         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8555
8556         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8557
8558         /* Test suites abort the 'game' after one move */
8559         if(*appData.finger) {
8560            static FILE *f;
8561            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8562            if(!f) f = fopen(appData.finger, "w");
8563            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8564            else { DisplayFatalError("Bad output file", errno, 0); return; }
8565            free(fen);
8566            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8567         }
8568
8569         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8570         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8571             int count = 0;
8572
8573             while( count < adjudicateLossPlies ) {
8574                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8575
8576                 if( count & 1 ) {
8577                     score = -score; /* Flip score for winning side */
8578                 }
8579
8580                 if( score > adjudicateLossThreshold ) {
8581                     break;
8582                 }
8583
8584                 count++;
8585             }
8586
8587             if( count >= adjudicateLossPlies ) {
8588                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8589
8590                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8591                     "Xboard adjudication",
8592                     GE_XBOARD );
8593
8594                 return;
8595             }
8596         }
8597
8598         if(Adjudicate(cps)) {
8599             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8600             return; // [HGM] adjudicate: for all automatic game ends
8601         }
8602
8603 #if ZIPPY
8604         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8605             first.initDone) {
8606           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8607                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8608                 SendToICS("draw ");
8609                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8610           }
8611           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8612           ics_user_moved = 1;
8613           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8614                 char buf[3*MSG_SIZ];
8615
8616                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8617                         programStats.score / 100.,
8618                         programStats.depth,
8619                         programStats.time / 100.,
8620                         (unsigned int)programStats.nodes,
8621                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8622                         programStats.movelist);
8623                 SendToICS(buf);
8624 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8625           }
8626         }
8627 #endif
8628
8629         /* [AS] Clear stats for next move */
8630         ClearProgramStats();
8631         thinkOutput[0] = NULLCHAR;
8632         hiddenThinkOutputState = 0;
8633
8634         bookHit = NULL;
8635         if (gameMode == TwoMachinesPlay) {
8636             /* [HGM] relaying draw offers moved to after reception of move */
8637             /* and interpreting offer as claim if it brings draw condition */
8638             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8639                 SendToProgram("draw\n", cps->other);
8640             }
8641             if (cps->other->sendTime) {
8642                 SendTimeRemaining(cps->other,
8643                                   cps->other->twoMachinesColor[0] == 'w');
8644             }
8645             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8646             if (firstMove && !bookHit) {
8647                 firstMove = FALSE;
8648                 if (cps->other->useColors) {
8649                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8650                 }
8651                 SendToProgram("go\n", cps->other);
8652             }
8653             cps->other->maybeThinking = TRUE;
8654         }
8655
8656         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8657
8658         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8659
8660         if (!pausing && appData.ringBellAfterMoves) {
8661             if(!roar) RingBell();
8662         }
8663
8664         /*
8665          * Reenable menu items that were disabled while
8666          * machine was thinking
8667          */
8668         if (gameMode != TwoMachinesPlay)
8669             SetUserThinkingEnables();
8670
8671         // [HGM] book: after book hit opponent has received move and is now in force mode
8672         // force the book reply into it, and then fake that it outputted this move by jumping
8673         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8674         if(bookHit) {
8675                 static char bookMove[MSG_SIZ]; // a bit generous?
8676
8677                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8678                 strcat(bookMove, bookHit);
8679                 message = bookMove;
8680                 cps = cps->other;
8681                 programStats.nodes = programStats.depth = programStats.time =
8682                 programStats.score = programStats.got_only_move = 0;
8683                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8684
8685                 if(cps->lastPing != cps->lastPong) {
8686                     savedMessage = message; // args for deferred call
8687                     savedState = cps;
8688                     ScheduleDelayedEvent(DeferredBookMove, 10);
8689                     return;
8690                 }
8691                 goto FakeBookMove;
8692         }
8693
8694         return;
8695     }
8696
8697     /* Set special modes for chess engines.  Later something general
8698      *  could be added here; for now there is just one kludge feature,
8699      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8700      *  when "xboard" is given as an interactive command.
8701      */
8702     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8703         cps->useSigint = FALSE;
8704         cps->useSigterm = FALSE;
8705     }
8706     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8707       ParseFeatures(message+8, cps);
8708       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8709     }
8710
8711     if (!strncmp(message, "setup ", 6) && 
8712         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8713           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8714                                         ) { // [HGM] allow first engine to define opening position
8715       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8716       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8717       *buf = NULLCHAR;
8718       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8719       if(startedFromSetupPosition) return;
8720       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8721       if(dummy >= 3) {
8722         while(message[s] && message[s++] != ' ');
8723         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8724            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8725             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8726             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8727           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8728           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8729         }
8730       }
8731       ParseFEN(boards[0], &dummy, message+s, FALSE);
8732       DrawPosition(TRUE, boards[0]);
8733       startedFromSetupPosition = TRUE;
8734       return;
8735     }
8736     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8737      * want this, I was asked to put it in, and obliged.
8738      */
8739     if (!strncmp(message, "setboard ", 9)) {
8740         Board initial_position;
8741
8742         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8743
8744         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8745             DisplayError(_("Bad FEN received from engine"), 0);
8746             return ;
8747         } else {
8748            Reset(TRUE, FALSE);
8749            CopyBoard(boards[0], initial_position);
8750            initialRulePlies = FENrulePlies;
8751            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8752            else gameMode = MachinePlaysBlack;
8753            DrawPosition(FALSE, boards[currentMove]);
8754         }
8755         return;
8756     }
8757
8758     /*
8759      * Look for communication commands
8760      */
8761     if (!strncmp(message, "telluser ", 9)) {
8762         if(message[9] == '\\' && message[10] == '\\')
8763             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8764         PlayTellSound();
8765         DisplayNote(message + 9);
8766         return;
8767     }
8768     if (!strncmp(message, "tellusererror ", 14)) {
8769         cps->userError = 1;
8770         if(message[14] == '\\' && message[15] == '\\')
8771             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8772         PlayTellSound();
8773         DisplayError(message + 14, 0);
8774         return;
8775     }
8776     if (!strncmp(message, "tellopponent ", 13)) {
8777       if (appData.icsActive) {
8778         if (loggedOn) {
8779           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8780           SendToICS(buf1);
8781         }
8782       } else {
8783         DisplayNote(message + 13);
8784       }
8785       return;
8786     }
8787     if (!strncmp(message, "tellothers ", 11)) {
8788       if (appData.icsActive) {
8789         if (loggedOn) {
8790           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8791           SendToICS(buf1);
8792         }
8793       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8794       return;
8795     }
8796     if (!strncmp(message, "tellall ", 8)) {
8797       if (appData.icsActive) {
8798         if (loggedOn) {
8799           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8800           SendToICS(buf1);
8801         }
8802       } else {
8803         DisplayNote(message + 8);
8804       }
8805       return;
8806     }
8807     if (strncmp(message, "warning", 7) == 0) {
8808         /* Undocumented feature, use tellusererror in new code */
8809         DisplayError(message, 0);
8810         return;
8811     }
8812     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8813         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8814         strcat(realname, " query");
8815         AskQuestion(realname, buf2, buf1, cps->pr);
8816         return;
8817     }
8818     /* Commands from the engine directly to ICS.  We don't allow these to be
8819      *  sent until we are logged on. Crafty kibitzes have been known to
8820      *  interfere with the login process.
8821      */
8822     if (loggedOn) {
8823         if (!strncmp(message, "tellics ", 8)) {
8824             SendToICS(message + 8);
8825             SendToICS("\n");
8826             return;
8827         }
8828         if (!strncmp(message, "tellicsnoalias ", 15)) {
8829             SendToICS(ics_prefix);
8830             SendToICS(message + 15);
8831             SendToICS("\n");
8832             return;
8833         }
8834         /* The following are for backward compatibility only */
8835         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8836             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8837             SendToICS(ics_prefix);
8838             SendToICS(message);
8839             SendToICS("\n");
8840             return;
8841         }
8842     }
8843     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8844         if(initPing == cps->lastPong) {
8845             if(gameInfo.variant == VariantUnknown) {
8846                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8847                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8848                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8849             }
8850             initPing = -1;
8851         }
8852         return;
8853     }
8854     if(!strncmp(message, "highlight ", 10)) {
8855         if(appData.testLegality && appData.markers) return;
8856         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8857         return;
8858     }
8859     if(!strncmp(message, "click ", 6)) {
8860         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8861         if(appData.testLegality || !appData.oneClick) return;
8862         sscanf(message+6, "%c%d%c", &f, &y, &c);
8863         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8864         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8865         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8866         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8867         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8868         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8869             LeftClick(Release, lastLeftX, lastLeftY);
8870         controlKey  = (c == ',');
8871         LeftClick(Press, x, y);
8872         LeftClick(Release, x, y);
8873         first.highlight = f;
8874         return;
8875     }
8876     /*
8877      * If the move is illegal, cancel it and redraw the board.
8878      * Also deal with other error cases.  Matching is rather loose
8879      * here to accommodate engines written before the spec.
8880      */
8881     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8882         strncmp(message, "Error", 5) == 0) {
8883         if (StrStr(message, "name") ||
8884             StrStr(message, "rating") || StrStr(message, "?") ||
8885             StrStr(message, "result") || StrStr(message, "board") ||
8886             StrStr(message, "bk") || StrStr(message, "computer") ||
8887             StrStr(message, "variant") || StrStr(message, "hint") ||
8888             StrStr(message, "random") || StrStr(message, "depth") ||
8889             StrStr(message, "accepted")) {
8890             return;
8891         }
8892         if (StrStr(message, "protover")) {
8893           /* Program is responding to input, so it's apparently done
8894              initializing, and this error message indicates it is
8895              protocol version 1.  So we don't need to wait any longer
8896              for it to initialize and send feature commands. */
8897           FeatureDone(cps, 1);
8898           cps->protocolVersion = 1;
8899           return;
8900         }
8901         cps->maybeThinking = FALSE;
8902
8903         if (StrStr(message, "draw")) {
8904             /* Program doesn't have "draw" command */
8905             cps->sendDrawOffers = 0;
8906             return;
8907         }
8908         if (cps->sendTime != 1 &&
8909             (StrStr(message, "time") || StrStr(message, "otim"))) {
8910           /* Program apparently doesn't have "time" or "otim" command */
8911           cps->sendTime = 0;
8912           return;
8913         }
8914         if (StrStr(message, "analyze")) {
8915             cps->analysisSupport = FALSE;
8916             cps->analyzing = FALSE;
8917 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8918             EditGameEvent(); // [HGM] try to preserve loaded game
8919             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8920             DisplayError(buf2, 0);
8921             return;
8922         }
8923         if (StrStr(message, "(no matching move)st")) {
8924           /* Special kludge for GNU Chess 4 only */
8925           cps->stKludge = TRUE;
8926           SendTimeControl(cps, movesPerSession, timeControl,
8927                           timeIncrement, appData.searchDepth,
8928                           searchTime);
8929           return;
8930         }
8931         if (StrStr(message, "(no matching move)sd")) {
8932           /* Special kludge for GNU Chess 4 only */
8933           cps->sdKludge = TRUE;
8934           SendTimeControl(cps, movesPerSession, timeControl,
8935                           timeIncrement, appData.searchDepth,
8936                           searchTime);
8937           return;
8938         }
8939         if (!StrStr(message, "llegal")) {
8940             return;
8941         }
8942         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8943             gameMode == IcsIdle) return;
8944         if (forwardMostMove <= backwardMostMove) return;
8945         if (pausing) PauseEvent();
8946       if(appData.forceIllegal) {
8947             // [HGM] illegal: machine refused move; force position after move into it
8948           SendToProgram("force\n", cps);
8949           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8950                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8951                 // when black is to move, while there might be nothing on a2 or black
8952                 // might already have the move. So send the board as if white has the move.
8953                 // But first we must change the stm of the engine, as it refused the last move
8954                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8955                 if(WhiteOnMove(forwardMostMove)) {
8956                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8957                     SendBoard(cps, forwardMostMove); // kludgeless board
8958                 } else {
8959                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8960                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8961                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8962                 }
8963           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8964             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8965                  gameMode == TwoMachinesPlay)
8966               SendToProgram("go\n", cps);
8967             return;
8968       } else
8969         if (gameMode == PlayFromGameFile) {
8970             /* Stop reading this game file */
8971             gameMode = EditGame;
8972             ModeHighlight();
8973         }
8974         /* [HGM] illegal-move claim should forfeit game when Xboard */
8975         /* only passes fully legal moves                            */
8976         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8977             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8978                                 "False illegal-move claim", GE_XBOARD );
8979             return; // do not take back move we tested as valid
8980         }
8981         currentMove = forwardMostMove-1;
8982         DisplayMove(currentMove-1); /* before DisplayMoveError */
8983         SwitchClocks(forwardMostMove-1); // [HGM] race
8984         DisplayBothClocks();
8985         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8986                 parseList[currentMove], _(cps->which));
8987         DisplayMoveError(buf1);
8988         DrawPosition(FALSE, boards[currentMove]);
8989
8990         SetUserThinkingEnables();
8991         return;
8992     }
8993     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8994         /* Program has a broken "time" command that
8995            outputs a string not ending in newline.
8996            Don't use it. */
8997         cps->sendTime = 0;
8998     }
8999
9000     /*
9001      * If chess program startup fails, exit with an error message.
9002      * Attempts to recover here are futile. [HGM] Well, we try anyway
9003      */
9004     if ((StrStr(message, "unknown host") != NULL)
9005         || (StrStr(message, "No remote directory") != NULL)
9006         || (StrStr(message, "not found") != NULL)
9007         || (StrStr(message, "No such file") != NULL)
9008         || (StrStr(message, "can't alloc") != NULL)
9009         || (StrStr(message, "Permission denied") != NULL)) {
9010
9011         cps->maybeThinking = FALSE;
9012         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9013                 _(cps->which), cps->program, cps->host, message);
9014         RemoveInputSource(cps->isr);
9015         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9016             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9017             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9018         }
9019         return;
9020     }
9021
9022     /*
9023      * Look for hint output
9024      */
9025     if (sscanf(message, "Hint: %s", buf1) == 1) {
9026         if (cps == &first && hintRequested) {
9027             hintRequested = FALSE;
9028             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9029                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9030                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9031                                     PosFlags(forwardMostMove),
9032                                     fromY, fromX, toY, toX, promoChar, buf1);
9033                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9034                 DisplayInformation(buf2);
9035             } else {
9036                 /* Hint move could not be parsed!? */
9037               snprintf(buf2, sizeof(buf2),
9038                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9039                         buf1, _(cps->which));
9040                 DisplayError(buf2, 0);
9041             }
9042         } else {
9043           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9044         }
9045         return;
9046     }
9047
9048     /*
9049      * Ignore other messages if game is not in progress
9050      */
9051     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9052         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9053
9054     /*
9055      * look for win, lose, draw, or draw offer
9056      */
9057     if (strncmp(message, "1-0", 3) == 0) {
9058         char *p, *q, *r = "";
9059         p = strchr(message, '{');
9060         if (p) {
9061             q = strchr(p, '}');
9062             if (q) {
9063                 *q = NULLCHAR;
9064                 r = p + 1;
9065             }
9066         }
9067         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9068         return;
9069     } else if (strncmp(message, "0-1", 3) == 0) {
9070         char *p, *q, *r = "";
9071         p = strchr(message, '{');
9072         if (p) {
9073             q = strchr(p, '}');
9074             if (q) {
9075                 *q = NULLCHAR;
9076                 r = p + 1;
9077             }
9078         }
9079         /* Kludge for Arasan 4.1 bug */
9080         if (strcmp(r, "Black resigns") == 0) {
9081             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9082             return;
9083         }
9084         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9085         return;
9086     } else if (strncmp(message, "1/2", 3) == 0) {
9087         char *p, *q, *r = "";
9088         p = strchr(message, '{');
9089         if (p) {
9090             q = strchr(p, '}');
9091             if (q) {
9092                 *q = NULLCHAR;
9093                 r = p + 1;
9094             }
9095         }
9096
9097         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9098         return;
9099
9100     } else if (strncmp(message, "White resign", 12) == 0) {
9101         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9102         return;
9103     } else if (strncmp(message, "Black resign", 12) == 0) {
9104         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9105         return;
9106     } else if (strncmp(message, "White matches", 13) == 0 ||
9107                strncmp(message, "Black matches", 13) == 0   ) {
9108         /* [HGM] ignore GNUShogi noises */
9109         return;
9110     } else if (strncmp(message, "White", 5) == 0 &&
9111                message[5] != '(' &&
9112                StrStr(message, "Black") == NULL) {
9113         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9114         return;
9115     } else if (strncmp(message, "Black", 5) == 0 &&
9116                message[5] != '(') {
9117         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9118         return;
9119     } else if (strcmp(message, "resign") == 0 ||
9120                strcmp(message, "computer resigns") == 0) {
9121         switch (gameMode) {
9122           case MachinePlaysBlack:
9123           case IcsPlayingBlack:
9124             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9125             break;
9126           case MachinePlaysWhite:
9127           case IcsPlayingWhite:
9128             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9129             break;
9130           case TwoMachinesPlay:
9131             if (cps->twoMachinesColor[0] == 'w')
9132               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9133             else
9134               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9135             break;
9136           default:
9137             /* can't happen */
9138             break;
9139         }
9140         return;
9141     } else if (strncmp(message, "opponent mates", 14) == 0) {
9142         switch (gameMode) {
9143           case MachinePlaysBlack:
9144           case IcsPlayingBlack:
9145             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9146             break;
9147           case MachinePlaysWhite:
9148           case IcsPlayingWhite:
9149             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9150             break;
9151           case TwoMachinesPlay:
9152             if (cps->twoMachinesColor[0] == 'w')
9153               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9154             else
9155               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9156             break;
9157           default:
9158             /* can't happen */
9159             break;
9160         }
9161         return;
9162     } else if (strncmp(message, "computer mates", 14) == 0) {
9163         switch (gameMode) {
9164           case MachinePlaysBlack:
9165           case IcsPlayingBlack:
9166             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9167             break;
9168           case MachinePlaysWhite:
9169           case IcsPlayingWhite:
9170             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9171             break;
9172           case TwoMachinesPlay:
9173             if (cps->twoMachinesColor[0] == 'w')
9174               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9175             else
9176               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9177             break;
9178           default:
9179             /* can't happen */
9180             break;
9181         }
9182         return;
9183     } else if (strncmp(message, "checkmate", 9) == 0) {
9184         if (WhiteOnMove(forwardMostMove)) {
9185             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9186         } else {
9187             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9188         }
9189         return;
9190     } else if (strstr(message, "Draw") != NULL ||
9191                strstr(message, "game is a draw") != NULL) {
9192         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9193         return;
9194     } else if (strstr(message, "offer") != NULL &&
9195                strstr(message, "draw") != NULL) {
9196 #if ZIPPY
9197         if (appData.zippyPlay && first.initDone) {
9198             /* Relay offer to ICS */
9199             SendToICS(ics_prefix);
9200             SendToICS("draw\n");
9201         }
9202 #endif
9203         cps->offeredDraw = 2; /* valid until this engine moves twice */
9204         if (gameMode == TwoMachinesPlay) {
9205             if (cps->other->offeredDraw) {
9206                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9207             /* [HGM] in two-machine mode we delay relaying draw offer      */
9208             /* until after we also have move, to see if it is really claim */
9209             }
9210         } else if (gameMode == MachinePlaysWhite ||
9211                    gameMode == MachinePlaysBlack) {
9212           if (userOfferedDraw) {
9213             DisplayInformation(_("Machine accepts your draw offer"));
9214             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9215           } else {
9216             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9217           }
9218         }
9219     }
9220
9221
9222     /*
9223      * Look for thinking output
9224      */
9225     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9226           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9227                                 ) {
9228         int plylev, mvleft, mvtot, curscore, time;
9229         char mvname[MOVE_LEN];
9230         u64 nodes; // [DM]
9231         char plyext;
9232         int ignore = FALSE;
9233         int prefixHint = FALSE;
9234         mvname[0] = NULLCHAR;
9235
9236         switch (gameMode) {
9237           case MachinePlaysBlack:
9238           case IcsPlayingBlack:
9239             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9240             break;
9241           case MachinePlaysWhite:
9242           case IcsPlayingWhite:
9243             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9244             break;
9245           case AnalyzeMode:
9246           case AnalyzeFile:
9247             break;
9248           case IcsObserving: /* [DM] icsEngineAnalyze */
9249             if (!appData.icsEngineAnalyze) ignore = TRUE;
9250             break;
9251           case TwoMachinesPlay:
9252             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9253                 ignore = TRUE;
9254             }
9255             break;
9256           default:
9257             ignore = TRUE;
9258             break;
9259         }
9260
9261         if (!ignore) {
9262             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9263             buf1[0] = NULLCHAR;
9264             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9265                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9266
9267                 if (plyext != ' ' && plyext != '\t') {
9268                     time *= 100;
9269                 }
9270
9271                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9272                 if( cps->scoreIsAbsolute &&
9273                     ( gameMode == MachinePlaysBlack ||
9274                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9275                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9276                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9277                      !WhiteOnMove(currentMove)
9278                     ) )
9279                 {
9280                     curscore = -curscore;
9281                 }
9282
9283                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9284
9285                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9286                         char buf[MSG_SIZ];
9287                         FILE *f;
9288                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9289                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9290                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9291                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9292                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9293                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9294                                 fclose(f);
9295                         }
9296                         else
9297                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9298                           DisplayError(_("failed writing PV"), 0);
9299                 }
9300
9301                 tempStats.depth = plylev;
9302                 tempStats.nodes = nodes;
9303                 tempStats.time = time;
9304                 tempStats.score = curscore;
9305                 tempStats.got_only_move = 0;
9306
9307                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9308                         int ticklen;
9309
9310                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9311                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9312                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9313                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9314                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9315                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9316                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9317                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9318                 }
9319
9320                 /* Buffer overflow protection */
9321                 if (pv[0] != NULLCHAR) {
9322                     if (strlen(pv) >= sizeof(tempStats.movelist)
9323                         && appData.debugMode) {
9324                         fprintf(debugFP,
9325                                 "PV is too long; using the first %u bytes.\n",
9326                                 (unsigned) sizeof(tempStats.movelist) - 1);
9327                     }
9328
9329                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9330                 } else {
9331                     sprintf(tempStats.movelist, " no PV\n");
9332                 }
9333
9334                 if (tempStats.seen_stat) {
9335                     tempStats.ok_to_send = 1;
9336                 }
9337
9338                 if (strchr(tempStats.movelist, '(') != NULL) {
9339                     tempStats.line_is_book = 1;
9340                     tempStats.nr_moves = 0;
9341                     tempStats.moves_left = 0;
9342                 } else {
9343                     tempStats.line_is_book = 0;
9344                 }
9345
9346                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9347                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9348
9349                 SendProgramStatsToFrontend( cps, &tempStats );
9350
9351                 /*
9352                     [AS] Protect the thinkOutput buffer from overflow... this
9353                     is only useful if buf1 hasn't overflowed first!
9354                 */
9355                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9356                          plylev,
9357                          (gameMode == TwoMachinesPlay ?
9358                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9359                          ((double) curscore) / 100.0,
9360                          prefixHint ? lastHint : "",
9361                          prefixHint ? " " : "" );
9362
9363                 if( buf1[0] != NULLCHAR ) {
9364                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9365
9366                     if( strlen(pv) > max_len ) {
9367                         if( appData.debugMode) {
9368                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9369                         }
9370                         pv[max_len+1] = '\0';
9371                     }
9372
9373                     strcat( thinkOutput, pv);
9374                 }
9375
9376                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9377                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9378                     DisplayMove(currentMove - 1);
9379                 }
9380                 return;
9381
9382             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9383                 /* crafty (9.25+) says "(only move) <move>"
9384                  * if there is only 1 legal move
9385                  */
9386                 sscanf(p, "(only move) %s", buf1);
9387                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9388                 sprintf(programStats.movelist, "%s (only move)", buf1);
9389                 programStats.depth = 1;
9390                 programStats.nr_moves = 1;
9391                 programStats.moves_left = 1;
9392                 programStats.nodes = 1;
9393                 programStats.time = 1;
9394                 programStats.got_only_move = 1;
9395
9396                 /* Not really, but we also use this member to
9397                    mean "line isn't going to change" (Crafty
9398                    isn't searching, so stats won't change) */
9399                 programStats.line_is_book = 1;
9400
9401                 SendProgramStatsToFrontend( cps, &programStats );
9402
9403                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9404                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9405                     DisplayMove(currentMove - 1);
9406                 }
9407                 return;
9408             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9409                               &time, &nodes, &plylev, &mvleft,
9410                               &mvtot, mvname) >= 5) {
9411                 /* The stat01: line is from Crafty (9.29+) in response
9412                    to the "." command */
9413                 programStats.seen_stat = 1;
9414                 cps->maybeThinking = TRUE;
9415
9416                 if (programStats.got_only_move || !appData.periodicUpdates)
9417                   return;
9418
9419                 programStats.depth = plylev;
9420                 programStats.time = time;
9421                 programStats.nodes = nodes;
9422                 programStats.moves_left = mvleft;
9423                 programStats.nr_moves = mvtot;
9424                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9425                 programStats.ok_to_send = 1;
9426                 programStats.movelist[0] = '\0';
9427
9428                 SendProgramStatsToFrontend( cps, &programStats );
9429
9430                 return;
9431
9432             } else if (strncmp(message,"++",2) == 0) {
9433                 /* Crafty 9.29+ outputs this */
9434                 programStats.got_fail = 2;
9435                 return;
9436
9437             } else if (strncmp(message,"--",2) == 0) {
9438                 /* Crafty 9.29+ outputs this */
9439                 programStats.got_fail = 1;
9440                 return;
9441
9442             } else if (thinkOutput[0] != NULLCHAR &&
9443                        strncmp(message, "    ", 4) == 0) {
9444                 unsigned message_len;
9445
9446                 p = message;
9447                 while (*p && *p == ' ') p++;
9448
9449                 message_len = strlen( p );
9450
9451                 /* [AS] Avoid buffer overflow */
9452                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9453                     strcat(thinkOutput, " ");
9454                     strcat(thinkOutput, p);
9455                 }
9456
9457                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9458                     strcat(programStats.movelist, " ");
9459                     strcat(programStats.movelist, p);
9460                 }
9461
9462                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9463                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9464                     DisplayMove(currentMove - 1);
9465                 }
9466                 return;
9467             }
9468         }
9469         else {
9470             buf1[0] = NULLCHAR;
9471
9472             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9473                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9474             {
9475                 ChessProgramStats cpstats;
9476
9477                 if (plyext != ' ' && plyext != '\t') {
9478                     time *= 100;
9479                 }
9480
9481                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9482                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9483                     curscore = -curscore;
9484                 }
9485
9486                 cpstats.depth = plylev;
9487                 cpstats.nodes = nodes;
9488                 cpstats.time = time;
9489                 cpstats.score = curscore;
9490                 cpstats.got_only_move = 0;
9491                 cpstats.movelist[0] = '\0';
9492
9493                 if (buf1[0] != NULLCHAR) {
9494                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9495                 }
9496
9497                 cpstats.ok_to_send = 0;
9498                 cpstats.line_is_book = 0;
9499                 cpstats.nr_moves = 0;
9500                 cpstats.moves_left = 0;
9501
9502                 SendProgramStatsToFrontend( cps, &cpstats );
9503             }
9504         }
9505     }
9506 }
9507
9508
9509 /* Parse a game score from the character string "game", and
9510    record it as the history of the current game.  The game
9511    score is NOT assumed to start from the standard position.
9512    The display is not updated in any way.
9513    */
9514 void
9515 ParseGameHistory (char *game)
9516 {
9517     ChessMove moveType;
9518     int fromX, fromY, toX, toY, boardIndex;
9519     char promoChar;
9520     char *p, *q;
9521     char buf[MSG_SIZ];
9522
9523     if (appData.debugMode)
9524       fprintf(debugFP, "Parsing game history: %s\n", game);
9525
9526     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9527     gameInfo.site = StrSave(appData.icsHost);
9528     gameInfo.date = PGNDate();
9529     gameInfo.round = StrSave("-");
9530
9531     /* Parse out names of players */
9532     while (*game == ' ') game++;
9533     p = buf;
9534     while (*game != ' ') *p++ = *game++;
9535     *p = NULLCHAR;
9536     gameInfo.white = StrSave(buf);
9537     while (*game == ' ') game++;
9538     p = buf;
9539     while (*game != ' ' && *game != '\n') *p++ = *game++;
9540     *p = NULLCHAR;
9541     gameInfo.black = StrSave(buf);
9542
9543     /* Parse moves */
9544     boardIndex = blackPlaysFirst ? 1 : 0;
9545     yynewstr(game);
9546     for (;;) {
9547         yyboardindex = boardIndex;
9548         moveType = (ChessMove) Myylex();
9549         switch (moveType) {
9550           case IllegalMove:             /* maybe suicide chess, etc. */
9551   if (appData.debugMode) {
9552     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9553     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9554     setbuf(debugFP, NULL);
9555   }
9556           case WhitePromotion:
9557           case BlackPromotion:
9558           case WhiteNonPromotion:
9559           case BlackNonPromotion:
9560           case NormalMove:
9561           case FirstLeg:
9562           case WhiteCapturesEnPassant:
9563           case BlackCapturesEnPassant:
9564           case WhiteKingSideCastle:
9565           case WhiteQueenSideCastle:
9566           case BlackKingSideCastle:
9567           case BlackQueenSideCastle:
9568           case WhiteKingSideCastleWild:
9569           case WhiteQueenSideCastleWild:
9570           case BlackKingSideCastleWild:
9571           case BlackQueenSideCastleWild:
9572           /* PUSH Fabien */
9573           case WhiteHSideCastleFR:
9574           case WhiteASideCastleFR:
9575           case BlackHSideCastleFR:
9576           case BlackASideCastleFR:
9577           /* POP Fabien */
9578             fromX = currentMoveString[0] - AAA;
9579             fromY = currentMoveString[1] - ONE;
9580             toX = currentMoveString[2] - AAA;
9581             toY = currentMoveString[3] - ONE;
9582             promoChar = currentMoveString[4];
9583             break;
9584           case WhiteDrop:
9585           case BlackDrop:
9586             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9587             fromX = moveType == WhiteDrop ?
9588               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9589             (int) CharToPiece(ToLower(currentMoveString[0]));
9590             fromY = DROP_RANK;
9591             toX = currentMoveString[2] - AAA;
9592             toY = currentMoveString[3] - ONE;
9593             promoChar = NULLCHAR;
9594             break;
9595           case AmbiguousMove:
9596             /* bug? */
9597             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9598   if (appData.debugMode) {
9599     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9600     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9601     setbuf(debugFP, NULL);
9602   }
9603             DisplayError(buf, 0);
9604             return;
9605           case ImpossibleMove:
9606             /* bug? */
9607             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9608   if (appData.debugMode) {
9609     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9610     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9611     setbuf(debugFP, NULL);
9612   }
9613             DisplayError(buf, 0);
9614             return;
9615           case EndOfFile:
9616             if (boardIndex < backwardMostMove) {
9617                 /* Oops, gap.  How did that happen? */
9618                 DisplayError(_("Gap in move list"), 0);
9619                 return;
9620             }
9621             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9622             if (boardIndex > forwardMostMove) {
9623                 forwardMostMove = boardIndex;
9624             }
9625             return;
9626           case ElapsedTime:
9627             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9628                 strcat(parseList[boardIndex-1], " ");
9629                 strcat(parseList[boardIndex-1], yy_text);
9630             }
9631             continue;
9632           case Comment:
9633           case PGNTag:
9634           case NAG:
9635           default:
9636             /* ignore */
9637             continue;
9638           case WhiteWins:
9639           case BlackWins:
9640           case GameIsDrawn:
9641           case GameUnfinished:
9642             if (gameMode == IcsExamining) {
9643                 if (boardIndex < backwardMostMove) {
9644                     /* Oops, gap.  How did that happen? */
9645                     return;
9646                 }
9647                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9648                 return;
9649             }
9650             gameInfo.result = moveType;
9651             p = strchr(yy_text, '{');
9652             if (p == NULL) p = strchr(yy_text, '(');
9653             if (p == NULL) {
9654                 p = yy_text;
9655                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9656             } else {
9657                 q = strchr(p, *p == '{' ? '}' : ')');
9658                 if (q != NULL) *q = NULLCHAR;
9659                 p++;
9660             }
9661             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9662             gameInfo.resultDetails = StrSave(p);
9663             continue;
9664         }
9665         if (boardIndex >= forwardMostMove &&
9666             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9667             backwardMostMove = blackPlaysFirst ? 1 : 0;
9668             return;
9669         }
9670         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9671                                  fromY, fromX, toY, toX, promoChar,
9672                                  parseList[boardIndex]);
9673         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9674         /* currentMoveString is set as a side-effect of yylex */
9675         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9676         strcat(moveList[boardIndex], "\n");
9677         boardIndex++;
9678         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9679         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9680           case MT_NONE:
9681           case MT_STALEMATE:
9682           default:
9683             break;
9684           case MT_CHECK:
9685             if(gameInfo.variant != VariantShogi)
9686                 strcat(parseList[boardIndex - 1], "+");
9687             break;
9688           case MT_CHECKMATE:
9689           case MT_STAINMATE:
9690             strcat(parseList[boardIndex - 1], "#");
9691             break;
9692         }
9693     }
9694 }
9695
9696
9697 /* Apply a move to the given board  */
9698 void
9699 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9700 {
9701   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9702   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9703
9704     /* [HGM] compute & store e.p. status and castling rights for new position */
9705     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9706
9707       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9708       oldEP = (signed char)board[EP_STATUS];
9709       board[EP_STATUS] = EP_NONE;
9710
9711   if (fromY == DROP_RANK) {
9712         /* must be first */
9713         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9714             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9715             return;
9716         }
9717         piece = board[toY][toX] = (ChessSquare) fromX;
9718   } else {
9719       ChessSquare victim;
9720       int i;
9721
9722       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9723            victim = board[killY][killX],
9724            board[killY][killX] = EmptySquare,
9725            board[EP_STATUS] = EP_CAPTURE;
9726
9727       if( board[toY][toX] != EmptySquare ) {
9728            board[EP_STATUS] = EP_CAPTURE;
9729            if( (fromX != toX || fromY != toY) && // not igui!
9730                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9731                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9732                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9733            }
9734       }
9735
9736       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9737            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9738                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9739       } else
9740       if( board[fromY][fromX] == WhitePawn ) {
9741            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9742                board[EP_STATUS] = EP_PAWN_MOVE;
9743            if( toY-fromY==2) {
9744                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9745                         gameInfo.variant != VariantBerolina || toX < fromX)
9746                       board[EP_STATUS] = toX | berolina;
9747                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9748                         gameInfo.variant != VariantBerolina || toX > fromX)
9749                       board[EP_STATUS] = toX;
9750            }
9751       } else
9752       if( board[fromY][fromX] == BlackPawn ) {
9753            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9754                board[EP_STATUS] = EP_PAWN_MOVE;
9755            if( toY-fromY== -2) {
9756                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9757                         gameInfo.variant != VariantBerolina || toX < fromX)
9758                       board[EP_STATUS] = toX | berolina;
9759                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9760                         gameInfo.variant != VariantBerolina || toX > fromX)
9761                       board[EP_STATUS] = toX;
9762            }
9763        }
9764
9765        for(i=0; i<nrCastlingRights; i++) {
9766            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9767               board[CASTLING][i] == toX   && castlingRank[i] == toY
9768              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9769        }
9770
9771        if(gameInfo.variant == VariantSChess) { // update virginity
9772            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9773            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9774            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9775            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9776        }
9777
9778      if (fromX == toX && fromY == toY) return;
9779
9780      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9781      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9782      if(gameInfo.variant == VariantKnightmate)
9783          king += (int) WhiteUnicorn - (int) WhiteKing;
9784
9785     /* Code added by Tord: */
9786     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9787     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9788         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9789       board[fromY][fromX] = EmptySquare;
9790       board[toY][toX] = EmptySquare;
9791       if((toX > fromX) != (piece == WhiteRook)) {
9792         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9793       } else {
9794         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9795       }
9796     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9797                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9798       board[fromY][fromX] = EmptySquare;
9799       board[toY][toX] = EmptySquare;
9800       if((toX > fromX) != (piece == BlackRook)) {
9801         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9802       } else {
9803         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9804       }
9805     /* End of code added by Tord */
9806
9807     } else if (board[fromY][fromX] == king
9808         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9809         && toY == fromY && toX > fromX+1) {
9810         board[fromY][fromX] = EmptySquare;
9811         board[toY][toX] = king;
9812         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9813         board[fromY][BOARD_RGHT-1] = EmptySquare;
9814     } else if (board[fromY][fromX] == king
9815         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9816                && toY == fromY && toX < fromX-1) {
9817         board[fromY][fromX] = EmptySquare;
9818         board[toY][toX] = king;
9819         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9820         board[fromY][BOARD_LEFT] = EmptySquare;
9821     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9822                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9823                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9824                ) {
9825         /* white pawn promotion */
9826         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9827         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9828             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9829         board[fromY][fromX] = EmptySquare;
9830     } else if ((fromY >= BOARD_HEIGHT>>1)
9831                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9832                && (toX != fromX)
9833                && gameInfo.variant != VariantXiangqi
9834                && gameInfo.variant != VariantBerolina
9835                && (board[fromY][fromX] == WhitePawn)
9836                && (board[toY][toX] == EmptySquare)) {
9837         board[fromY][fromX] = EmptySquare;
9838         board[toY][toX] = WhitePawn;
9839         captured = board[toY - 1][toX];
9840         board[toY - 1][toX] = EmptySquare;
9841     } else if ((fromY == BOARD_HEIGHT-4)
9842                && (toX == fromX)
9843                && gameInfo.variant == VariantBerolina
9844                && (board[fromY][fromX] == WhitePawn)
9845                && (board[toY][toX] == EmptySquare)) {
9846         board[fromY][fromX] = EmptySquare;
9847         board[toY][toX] = WhitePawn;
9848         if(oldEP & EP_BEROLIN_A) {
9849                 captured = board[fromY][fromX-1];
9850                 board[fromY][fromX-1] = EmptySquare;
9851         }else{  captured = board[fromY][fromX+1];
9852                 board[fromY][fromX+1] = EmptySquare;
9853         }
9854     } else if (board[fromY][fromX] == king
9855         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9856                && toY == fromY && toX > fromX+1) {
9857         board[fromY][fromX] = EmptySquare;
9858         board[toY][toX] = king;
9859         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9860         board[fromY][BOARD_RGHT-1] = EmptySquare;
9861     } else if (board[fromY][fromX] == king
9862         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9863                && toY == fromY && toX < fromX-1) {
9864         board[fromY][fromX] = EmptySquare;
9865         board[toY][toX] = king;
9866         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9867         board[fromY][BOARD_LEFT] = EmptySquare;
9868     } else if (fromY == 7 && fromX == 3
9869                && board[fromY][fromX] == BlackKing
9870                && toY == 7 && toX == 5) {
9871         board[fromY][fromX] = EmptySquare;
9872         board[toY][toX] = BlackKing;
9873         board[fromY][7] = EmptySquare;
9874         board[toY][4] = BlackRook;
9875     } else if (fromY == 7 && fromX == 3
9876                && board[fromY][fromX] == BlackKing
9877                && toY == 7 && toX == 1) {
9878         board[fromY][fromX] = EmptySquare;
9879         board[toY][toX] = BlackKing;
9880         board[fromY][0] = EmptySquare;
9881         board[toY][2] = BlackRook;
9882     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9883                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9884                && toY < promoRank && promoChar
9885                ) {
9886         /* black pawn promotion */
9887         board[toY][toX] = CharToPiece(ToLower(promoChar));
9888         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9889             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9890         board[fromY][fromX] = EmptySquare;
9891     } else if ((fromY < BOARD_HEIGHT>>1)
9892                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9893                && (toX != fromX)
9894                && gameInfo.variant != VariantXiangqi
9895                && gameInfo.variant != VariantBerolina
9896                && (board[fromY][fromX] == BlackPawn)
9897                && (board[toY][toX] == EmptySquare)) {
9898         board[fromY][fromX] = EmptySquare;
9899         board[toY][toX] = BlackPawn;
9900         captured = board[toY + 1][toX];
9901         board[toY + 1][toX] = EmptySquare;
9902     } else if ((fromY == 3)
9903                && (toX == fromX)
9904                && gameInfo.variant == VariantBerolina
9905                && (board[fromY][fromX] == BlackPawn)
9906                && (board[toY][toX] == EmptySquare)) {
9907         board[fromY][fromX] = EmptySquare;
9908         board[toY][toX] = BlackPawn;
9909         if(oldEP & EP_BEROLIN_A) {
9910                 captured = board[fromY][fromX-1];
9911                 board[fromY][fromX-1] = EmptySquare;
9912         }else{  captured = board[fromY][fromX+1];
9913                 board[fromY][fromX+1] = EmptySquare;
9914         }
9915     } else {
9916         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9917         board[fromY][fromX] = EmptySquare;
9918         board[toY][toX] = piece;
9919     }
9920   }
9921
9922     if (gameInfo.holdingsWidth != 0) {
9923
9924       /* !!A lot more code needs to be written to support holdings  */
9925       /* [HGM] OK, so I have written it. Holdings are stored in the */
9926       /* penultimate board files, so they are automaticlly stored   */
9927       /* in the game history.                                       */
9928       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9929                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9930         /* Delete from holdings, by decreasing count */
9931         /* and erasing image if necessary            */
9932         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9933         if(p < (int) BlackPawn) { /* white drop */
9934              p -= (int)WhitePawn;
9935                  p = PieceToNumber((ChessSquare)p);
9936              if(p >= gameInfo.holdingsSize) p = 0;
9937              if(--board[p][BOARD_WIDTH-2] <= 0)
9938                   board[p][BOARD_WIDTH-1] = EmptySquare;
9939              if((int)board[p][BOARD_WIDTH-2] < 0)
9940                         board[p][BOARD_WIDTH-2] = 0;
9941         } else {                  /* black drop */
9942              p -= (int)BlackPawn;
9943                  p = PieceToNumber((ChessSquare)p);
9944              if(p >= gameInfo.holdingsSize) p = 0;
9945              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9946                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9947              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9948                         board[BOARD_HEIGHT-1-p][1] = 0;
9949         }
9950       }
9951       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9952           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9953         /* [HGM] holdings: Add to holdings, if holdings exist */
9954         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9955                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9956                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9957         }
9958         p = (int) captured;
9959         if (p >= (int) BlackPawn) {
9960           p -= (int)BlackPawn;
9961           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9962                   /* in Shogi restore piece to its original  first */
9963                   captured = (ChessSquare) (DEMOTED captured);
9964                   p = DEMOTED p;
9965           }
9966           p = PieceToNumber((ChessSquare)p);
9967           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9968           board[p][BOARD_WIDTH-2]++;
9969           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9970         } else {
9971           p -= (int)WhitePawn;
9972           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9973                   captured = (ChessSquare) (DEMOTED captured);
9974                   p = DEMOTED p;
9975           }
9976           p = PieceToNumber((ChessSquare)p);
9977           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9978           board[BOARD_HEIGHT-1-p][1]++;
9979           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9980         }
9981       }
9982     } else if (gameInfo.variant == VariantAtomic) {
9983       if (captured != EmptySquare) {
9984         int y, x;
9985         for (y = toY-1; y <= toY+1; y++) {
9986           for (x = toX-1; x <= toX+1; x++) {
9987             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9988                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9989               board[y][x] = EmptySquare;
9990             }
9991           }
9992         }
9993         board[toY][toX] = EmptySquare;
9994       }
9995     }
9996     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9997         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9998     } else
9999     if(promoChar == '+') {
10000         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10001         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10002         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10003           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10004     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10005         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10006         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10007            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10008         board[toY][toX] = newPiece;
10009     }
10010     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10011                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10012         // [HGM] superchess: take promotion piece out of holdings
10013         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10014         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10015             if(!--board[k][BOARD_WIDTH-2])
10016                 board[k][BOARD_WIDTH-1] = EmptySquare;
10017         } else {
10018             if(!--board[BOARD_HEIGHT-1-k][1])
10019                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10020         }
10021     }
10022 }
10023
10024 /* Updates forwardMostMove */
10025 void
10026 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10027 {
10028     int x = toX, y = toY;
10029     char *s = parseList[forwardMostMove];
10030     ChessSquare p = boards[forwardMostMove][toY][toX];
10031 //    forwardMostMove++; // [HGM] bare: moved downstream
10032
10033     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10034     (void) CoordsToAlgebraic(boards[forwardMostMove],
10035                              PosFlags(forwardMostMove),
10036                              fromY, fromX, y, x, promoChar,
10037                              s);
10038     if(killX >= 0 && killY >= 0)
10039         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10040
10041     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10042         int timeLeft; static int lastLoadFlag=0; int king, piece;
10043         piece = boards[forwardMostMove][fromY][fromX];
10044         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10045         if(gameInfo.variant == VariantKnightmate)
10046             king += (int) WhiteUnicorn - (int) WhiteKing;
10047         if(forwardMostMove == 0) {
10048             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10049                 fprintf(serverMoves, "%s;", UserName());
10050             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10051                 fprintf(serverMoves, "%s;", second.tidy);
10052             fprintf(serverMoves, "%s;", first.tidy);
10053             if(gameMode == MachinePlaysWhite)
10054                 fprintf(serverMoves, "%s;", UserName());
10055             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10056                 fprintf(serverMoves, "%s;", second.tidy);
10057         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10058         lastLoadFlag = loadFlag;
10059         // print base move
10060         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10061         // print castling suffix
10062         if( toY == fromY && piece == king ) {
10063             if(toX-fromX > 1)
10064                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10065             if(fromX-toX >1)
10066                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10067         }
10068         // e.p. suffix
10069         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10070              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10071              boards[forwardMostMove][toY][toX] == EmptySquare
10072              && fromX != toX && fromY != toY)
10073                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10074         // promotion suffix
10075         if(promoChar != NULLCHAR) {
10076             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10077                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10078                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10079             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10080         }
10081         if(!loadFlag) {
10082                 char buf[MOVE_LEN*2], *p; int len;
10083             fprintf(serverMoves, "/%d/%d",
10084                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10085             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10086             else                      timeLeft = blackTimeRemaining/1000;
10087             fprintf(serverMoves, "/%d", timeLeft);
10088                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10089                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10090                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10091                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10092             fprintf(serverMoves, "/%s", buf);
10093         }
10094         fflush(serverMoves);
10095     }
10096
10097     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10098         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10099       return;
10100     }
10101     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10102     if (commentList[forwardMostMove+1] != NULL) {
10103         free(commentList[forwardMostMove+1]);
10104         commentList[forwardMostMove+1] = NULL;
10105     }
10106     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10107     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10108     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10109     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10110     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10111     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10112     adjustedClock = FALSE;
10113     gameInfo.result = GameUnfinished;
10114     if (gameInfo.resultDetails != NULL) {
10115         free(gameInfo.resultDetails);
10116         gameInfo.resultDetails = NULL;
10117     }
10118     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10119                               moveList[forwardMostMove - 1]);
10120     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10121       case MT_NONE:
10122       case MT_STALEMATE:
10123       default:
10124         break;
10125       case MT_CHECK:
10126         if(gameInfo.variant != VariantShogi)
10127             strcat(parseList[forwardMostMove - 1], "+");
10128         break;
10129       case MT_CHECKMATE:
10130       case MT_STAINMATE:
10131         strcat(parseList[forwardMostMove - 1], "#");
10132         break;
10133     }
10134 }
10135
10136 /* Updates currentMove if not pausing */
10137 void
10138 ShowMove (int fromX, int fromY, int toX, int toY)
10139 {
10140     int instant = (gameMode == PlayFromGameFile) ?
10141         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10142     if(appData.noGUI) return;
10143     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10144         if (!instant) {
10145             if (forwardMostMove == currentMove + 1) {
10146                 AnimateMove(boards[forwardMostMove - 1],
10147                             fromX, fromY, toX, toY);
10148             }
10149         }
10150         currentMove = forwardMostMove;
10151     }
10152
10153     killX = killY = -1; // [HGM] lion: used up
10154
10155     if (instant) return;
10156
10157     DisplayMove(currentMove - 1);
10158     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10159             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10160                 SetHighlights(fromX, fromY, toX, toY);
10161             }
10162     }
10163     DrawPosition(FALSE, boards[currentMove]);
10164     DisplayBothClocks();
10165     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10166 }
10167
10168 void
10169 SendEgtPath (ChessProgramState *cps)
10170 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10171         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10172
10173         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10174
10175         while(*p) {
10176             char c, *q = name+1, *r, *s;
10177
10178             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10179             while(*p && *p != ',') *q++ = *p++;
10180             *q++ = ':'; *q = 0;
10181             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10182                 strcmp(name, ",nalimov:") == 0 ) {
10183                 // take nalimov path from the menu-changeable option first, if it is defined
10184               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10185                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10186             } else
10187             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10188                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10189                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10190                 s = r = StrStr(s, ":") + 1; // beginning of path info
10191                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10192                 c = *r; *r = 0;             // temporarily null-terminate path info
10193                     *--q = 0;               // strip of trailig ':' from name
10194                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10195                 *r = c;
10196                 SendToProgram(buf,cps);     // send egtbpath command for this format
10197             }
10198             if(*p == ',') p++; // read away comma to position for next format name
10199         }
10200 }
10201
10202 static int
10203 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10204 {
10205       int width = 8, height = 8, holdings = 0;             // most common sizes
10206       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10207       // correct the deviations default for each variant
10208       if( v == VariantXiangqi ) width = 9,  height = 10;
10209       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10210       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10211       if( v == VariantCapablanca || v == VariantCapaRandom ||
10212           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10213                                 width = 10;
10214       if( v == VariantCourier ) width = 12;
10215       if( v == VariantSuper )                            holdings = 8;
10216       if( v == VariantGreat )   width = 10,              holdings = 8;
10217       if( v == VariantSChess )                           holdings = 7;
10218       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10219       if( v == VariantChuChess) width = 10, height = 10;
10220       if( v == VariantChu )     width = 12, height = 12;
10221       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10222              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10223              holdingsSize >= 0 && holdingsSize != holdings;
10224 }
10225
10226 char variantError[MSG_SIZ];
10227
10228 char *
10229 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10230 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10231       char *p, *variant = VariantName(v);
10232       static char b[MSG_SIZ];
10233       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10234            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10235                                                holdingsSize, variant); // cook up sized variant name
10236            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10237            if(StrStr(list, b) == NULL) {
10238                // specific sized variant not known, check if general sizing allowed
10239                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10240                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10241                             boardWidth, boardHeight, holdingsSize, engine);
10242                    return NULL;
10243                }
10244                /* [HGM] here we really should compare with the maximum supported board size */
10245            }
10246       } else snprintf(b, MSG_SIZ,"%s", variant);
10247       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10248       p = StrStr(list, b);
10249       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10250       if(p == NULL) {
10251           // occurs not at all in list, or only as sub-string
10252           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10253           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10254               int l = strlen(variantError);
10255               char *q;
10256               while(p != list && p[-1] != ',') p--;
10257               q = strchr(p, ',');
10258               if(q) *q = NULLCHAR;
10259               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10260               if(q) *q= ',';
10261           }
10262           return NULL;
10263       }
10264       return b;
10265 }
10266
10267 void
10268 InitChessProgram (ChessProgramState *cps, int setup)
10269 /* setup needed to setup FRC opening position */
10270 {
10271     char buf[MSG_SIZ], *b;
10272     if (appData.noChessProgram) return;
10273     hintRequested = FALSE;
10274     bookRequested = FALSE;
10275
10276     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10277     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10278     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10279     if(cps->memSize) { /* [HGM] memory */
10280       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10281         SendToProgram(buf, cps);
10282     }
10283     SendEgtPath(cps); /* [HGM] EGT */
10284     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10285       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10286         SendToProgram(buf, cps);
10287     }
10288
10289     SendToProgram(cps->initString, cps);
10290     if (gameInfo.variant != VariantNormal &&
10291         gameInfo.variant != VariantLoadable
10292         /* [HGM] also send variant if board size non-standard */
10293         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10294
10295       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10296                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10297       if (b == NULL) {
10298         DisplayFatalError(variantError, 0, 1);
10299         return;
10300       }
10301
10302       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10303       SendToProgram(buf, cps);
10304     }
10305     currentlyInitializedVariant = gameInfo.variant;
10306
10307     /* [HGM] send opening position in FRC to first engine */
10308     if(setup) {
10309           SendToProgram("force\n", cps);
10310           SendBoard(cps, 0);
10311           /* engine is now in force mode! Set flag to wake it up after first move. */
10312           setboardSpoiledMachineBlack = 1;
10313     }
10314
10315     if (cps->sendICS) {
10316       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10317       SendToProgram(buf, cps);
10318     }
10319     cps->maybeThinking = FALSE;
10320     cps->offeredDraw = 0;
10321     if (!appData.icsActive) {
10322         SendTimeControl(cps, movesPerSession, timeControl,
10323                         timeIncrement, appData.searchDepth,
10324                         searchTime);
10325     }
10326     if (appData.showThinking
10327         // [HGM] thinking: four options require thinking output to be sent
10328         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10329                                 ) {
10330         SendToProgram("post\n", cps);
10331     }
10332     SendToProgram("hard\n", cps);
10333     if (!appData.ponderNextMove) {
10334         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10335            it without being sure what state we are in first.  "hard"
10336            is not a toggle, so that one is OK.
10337          */
10338         SendToProgram("easy\n", cps);
10339     }
10340     if (cps->usePing) {
10341       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10342       SendToProgram(buf, cps);
10343     }
10344     cps->initDone = TRUE;
10345     ClearEngineOutputPane(cps == &second);
10346 }
10347
10348
10349 void
10350 ResendOptions (ChessProgramState *cps)
10351 { // send the stored value of the options
10352   int i;
10353   char buf[MSG_SIZ];
10354   Option *opt = cps->option;
10355   for(i=0; i<cps->nrOptions; i++, opt++) {
10356       switch(opt->type) {
10357         case Spin:
10358         case Slider:
10359         case CheckBox:
10360             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10361           break;
10362         case ComboBox:
10363           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10364           break;
10365         default:
10366             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10367           break;
10368         case Button:
10369         case SaveButton:
10370           continue;
10371       }
10372       SendToProgram(buf, cps);
10373   }
10374 }
10375
10376 void
10377 StartChessProgram (ChessProgramState *cps)
10378 {
10379     char buf[MSG_SIZ];
10380     int err;
10381
10382     if (appData.noChessProgram) return;
10383     cps->initDone = FALSE;
10384
10385     if (strcmp(cps->host, "localhost") == 0) {
10386         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10387     } else if (*appData.remoteShell == NULLCHAR) {
10388         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10389     } else {
10390         if (*appData.remoteUser == NULLCHAR) {
10391           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10392                     cps->program);
10393         } else {
10394           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10395                     cps->host, appData.remoteUser, cps->program);
10396         }
10397         err = StartChildProcess(buf, "", &cps->pr);
10398     }
10399
10400     if (err != 0) {
10401       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10402         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10403         if(cps != &first) return;
10404         appData.noChessProgram = TRUE;
10405         ThawUI();
10406         SetNCPMode();
10407 //      DisplayFatalError(buf, err, 1);
10408 //      cps->pr = NoProc;
10409 //      cps->isr = NULL;
10410         return;
10411     }
10412
10413     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10414     if (cps->protocolVersion > 1) {
10415       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10416       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10417         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10418         cps->comboCnt = 0;  //                and values of combo boxes
10419       }
10420       SendToProgram(buf, cps);
10421       if(cps->reload) ResendOptions(cps);
10422     } else {
10423       SendToProgram("xboard\n", cps);
10424     }
10425 }
10426
10427 void
10428 TwoMachinesEventIfReady P((void))
10429 {
10430   static int curMess = 0;
10431   if (first.lastPing != first.lastPong) {
10432     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10433     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10434     return;
10435   }
10436   if (second.lastPing != second.lastPong) {
10437     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10438     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10439     return;
10440   }
10441   DisplayMessage("", ""); curMess = 0;
10442   TwoMachinesEvent();
10443 }
10444
10445 char *
10446 MakeName (char *template)
10447 {
10448     time_t clock;
10449     struct tm *tm;
10450     static char buf[MSG_SIZ];
10451     char *p = buf;
10452     int i;
10453
10454     clock = time((time_t *)NULL);
10455     tm = localtime(&clock);
10456
10457     while(*p++ = *template++) if(p[-1] == '%') {
10458         switch(*template++) {
10459           case 0:   *p = 0; return buf;
10460           case 'Y': i = tm->tm_year+1900; break;
10461           case 'y': i = tm->tm_year-100; break;
10462           case 'M': i = tm->tm_mon+1; break;
10463           case 'd': i = tm->tm_mday; break;
10464           case 'h': i = tm->tm_hour; break;
10465           case 'm': i = tm->tm_min; break;
10466           case 's': i = tm->tm_sec; break;
10467           default:  i = 0;
10468         }
10469         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10470     }
10471     return buf;
10472 }
10473
10474 int
10475 CountPlayers (char *p)
10476 {
10477     int n = 0;
10478     while(p = strchr(p, '\n')) p++, n++; // count participants
10479     return n;
10480 }
10481
10482 FILE *
10483 WriteTourneyFile (char *results, FILE *f)
10484 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10485     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10486     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10487         // create a file with tournament description
10488         fprintf(f, "-participants {%s}\n", appData.participants);
10489         fprintf(f, "-seedBase %d\n", appData.seedBase);
10490         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10491         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10492         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10493         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10494         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10495         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10496         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10497         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10498         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10499         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10500         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10501         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10502         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10503         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10504         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10505         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10506         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10507         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10508         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10509         fprintf(f, "-smpCores %d\n", appData.smpCores);
10510         if(searchTime > 0)
10511                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10512         else {
10513                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10514                 fprintf(f, "-tc %s\n", appData.timeControl);
10515                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10516         }
10517         fprintf(f, "-results \"%s\"\n", results);
10518     }
10519     return f;
10520 }
10521
10522 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10523
10524 void
10525 Substitute (char *participants, int expunge)
10526 {
10527     int i, changed, changes=0, nPlayers=0;
10528     char *p, *q, *r, buf[MSG_SIZ];
10529     if(participants == NULL) return;
10530     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10531     r = p = participants; q = appData.participants;
10532     while(*p && *p == *q) {
10533         if(*p == '\n') r = p+1, nPlayers++;
10534         p++; q++;
10535     }
10536     if(*p) { // difference
10537         while(*p && *p++ != '\n');
10538         while(*q && *q++ != '\n');
10539       changed = nPlayers;
10540         changes = 1 + (strcmp(p, q) != 0);
10541     }
10542     if(changes == 1) { // a single engine mnemonic was changed
10543         q = r; while(*q) nPlayers += (*q++ == '\n');
10544         p = buf; while(*r && (*p = *r++) != '\n') p++;
10545         *p = NULLCHAR;
10546         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10547         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10548         if(mnemonic[i]) { // The substitute is valid
10549             FILE *f;
10550             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10551                 flock(fileno(f), LOCK_EX);
10552                 ParseArgsFromFile(f);
10553                 fseek(f, 0, SEEK_SET);
10554                 FREE(appData.participants); appData.participants = participants;
10555                 if(expunge) { // erase results of replaced engine
10556                     int len = strlen(appData.results), w, b, dummy;
10557                     for(i=0; i<len; i++) {
10558                         Pairing(i, nPlayers, &w, &b, &dummy);
10559                         if((w == changed || b == changed) && appData.results[i] == '*') {
10560                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10561                             fclose(f);
10562                             return;
10563                         }
10564                     }
10565                     for(i=0; i<len; i++) {
10566                         Pairing(i, nPlayers, &w, &b, &dummy);
10567                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10568                     }
10569                 }
10570                 WriteTourneyFile(appData.results, f);
10571                 fclose(f); // release lock
10572                 return;
10573             }
10574         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10575     }
10576     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10577     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10578     free(participants);
10579     return;
10580 }
10581
10582 int
10583 CheckPlayers (char *participants)
10584 {
10585         int i;
10586         char buf[MSG_SIZ], *p;
10587         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10588         while(p = strchr(participants, '\n')) {
10589             *p = NULLCHAR;
10590             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10591             if(!mnemonic[i]) {
10592                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10593                 *p = '\n';
10594                 DisplayError(buf, 0);
10595                 return 1;
10596             }
10597             *p = '\n';
10598             participants = p + 1;
10599         }
10600         return 0;
10601 }
10602
10603 int
10604 CreateTourney (char *name)
10605 {
10606         FILE *f;
10607         if(matchMode && strcmp(name, appData.tourneyFile)) {
10608              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10609         }
10610         if(name[0] == NULLCHAR) {
10611             if(appData.participants[0])
10612                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10613             return 0;
10614         }
10615         f = fopen(name, "r");
10616         if(f) { // file exists
10617             ASSIGN(appData.tourneyFile, name);
10618             ParseArgsFromFile(f); // parse it
10619         } else {
10620             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10621             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10622                 DisplayError(_("Not enough participants"), 0);
10623                 return 0;
10624             }
10625             if(CheckPlayers(appData.participants)) return 0;
10626             ASSIGN(appData.tourneyFile, name);
10627             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10628             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10629         }
10630         fclose(f);
10631         appData.noChessProgram = FALSE;
10632         appData.clockMode = TRUE;
10633         SetGNUMode();
10634         return 1;
10635 }
10636
10637 int
10638 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10639 {
10640     char buf[MSG_SIZ], *p, *q;
10641     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10642     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10643     skip = !all && group[0]; // if group requested, we start in skip mode
10644     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10645         p = names; q = buf; header = 0;
10646         while(*p && *p != '\n') *q++ = *p++;
10647         *q = 0;
10648         if(*p == '\n') p++;
10649         if(buf[0] == '#') {
10650             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10651             depth++; // we must be entering a new group
10652             if(all) continue; // suppress printing group headers when complete list requested
10653             header = 1;
10654             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10655         }
10656         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10657         if(engineList[i]) free(engineList[i]);
10658         engineList[i] = strdup(buf);
10659         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10660         if(engineMnemonic[i]) free(engineMnemonic[i]);
10661         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10662             strcat(buf, " (");
10663             sscanf(q + 8, "%s", buf + strlen(buf));
10664             strcat(buf, ")");
10665         }
10666         engineMnemonic[i] = strdup(buf);
10667         i++;
10668     }
10669     engineList[i] = engineMnemonic[i] = NULL;
10670     return i;
10671 }
10672
10673 // following implemented as macro to avoid type limitations
10674 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10675
10676 void
10677 SwapEngines (int n)
10678 {   // swap settings for first engine and other engine (so far only some selected options)
10679     int h;
10680     char *p;
10681     if(n == 0) return;
10682     SWAP(directory, p)
10683     SWAP(chessProgram, p)
10684     SWAP(isUCI, h)
10685     SWAP(hasOwnBookUCI, h)
10686     SWAP(protocolVersion, h)
10687     SWAP(reuse, h)
10688     SWAP(scoreIsAbsolute, h)
10689     SWAP(timeOdds, h)
10690     SWAP(logo, p)
10691     SWAP(pgnName, p)
10692     SWAP(pvSAN, h)
10693     SWAP(engOptions, p)
10694     SWAP(engInitString, p)
10695     SWAP(computerString, p)
10696     SWAP(features, p)
10697     SWAP(fenOverride, p)
10698     SWAP(NPS, h)
10699     SWAP(accumulateTC, h)
10700     SWAP(host, p)
10701 }
10702
10703 int
10704 GetEngineLine (char *s, int n)
10705 {
10706     int i;
10707     char buf[MSG_SIZ];
10708     extern char *icsNames;
10709     if(!s || !*s) return 0;
10710     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10711     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10712     if(!mnemonic[i]) return 0;
10713     if(n == 11) return 1; // just testing if there was a match
10714     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10715     if(n == 1) SwapEngines(n);
10716     ParseArgsFromString(buf);
10717     if(n == 1) SwapEngines(n);
10718     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10719         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10720         ParseArgsFromString(buf);
10721     }
10722     return 1;
10723 }
10724
10725 int
10726 SetPlayer (int player, char *p)
10727 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10728     int i;
10729     char buf[MSG_SIZ], *engineName;
10730     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10731     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10732     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10733     if(mnemonic[i]) {
10734         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10735         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10736         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10737         ParseArgsFromString(buf);
10738     } else { // no engine with this nickname is installed!
10739         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10740         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10741         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10742         ModeHighlight();
10743         DisplayError(buf, 0);
10744         return 0;
10745     }
10746     free(engineName);
10747     return i;
10748 }
10749
10750 char *recentEngines;
10751
10752 void
10753 RecentEngineEvent (int nr)
10754 {
10755     int n;
10756 //    SwapEngines(1); // bump first to second
10757 //    ReplaceEngine(&second, 1); // and load it there
10758     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10759     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10760     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10761         ReplaceEngine(&first, 0);
10762         FloatToFront(&appData.recentEngineList, command[n]);
10763     }
10764 }
10765
10766 int
10767 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10768 {   // determine players from game number
10769     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10770
10771     if(appData.tourneyType == 0) {
10772         roundsPerCycle = (nPlayers - 1) | 1;
10773         pairingsPerRound = nPlayers / 2;
10774     } else if(appData.tourneyType > 0) {
10775         roundsPerCycle = nPlayers - appData.tourneyType;
10776         pairingsPerRound = appData.tourneyType;
10777     }
10778     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10779     gamesPerCycle = gamesPerRound * roundsPerCycle;
10780     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10781     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10782     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10783     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10784     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10785     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10786
10787     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10788     if(appData.roundSync) *syncInterval = gamesPerRound;
10789
10790     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10791
10792     if(appData.tourneyType == 0) {
10793         if(curPairing == (nPlayers-1)/2 ) {
10794             *whitePlayer = curRound;
10795             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10796         } else {
10797             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10798             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10799             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10800             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10801         }
10802     } else if(appData.tourneyType > 1) {
10803         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10804         *whitePlayer = curRound + appData.tourneyType;
10805     } else if(appData.tourneyType > 0) {
10806         *whitePlayer = curPairing;
10807         *blackPlayer = curRound + appData.tourneyType;
10808     }
10809
10810     // take care of white/black alternation per round.
10811     // For cycles and games this is already taken care of by default, derived from matchGame!
10812     return curRound & 1;
10813 }
10814
10815 int
10816 NextTourneyGame (int nr, int *swapColors)
10817 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10818     char *p, *q;
10819     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10820     FILE *tf;
10821     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10822     tf = fopen(appData.tourneyFile, "r");
10823     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10824     ParseArgsFromFile(tf); fclose(tf);
10825     InitTimeControls(); // TC might be altered from tourney file
10826
10827     nPlayers = CountPlayers(appData.participants); // count participants
10828     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10829     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10830
10831     if(syncInterval) {
10832         p = q = appData.results;
10833         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10834         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10835             DisplayMessage(_("Waiting for other game(s)"),"");
10836             waitingForGame = TRUE;
10837             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10838             return 0;
10839         }
10840         waitingForGame = FALSE;
10841     }
10842
10843     if(appData.tourneyType < 0) {
10844         if(nr>=0 && !pairingReceived) {
10845             char buf[1<<16];
10846             if(pairing.pr == NoProc) {
10847                 if(!appData.pairingEngine[0]) {
10848                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10849                     return 0;
10850                 }
10851                 StartChessProgram(&pairing); // starts the pairing engine
10852             }
10853             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10854             SendToProgram(buf, &pairing);
10855             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10856             SendToProgram(buf, &pairing);
10857             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10858         }
10859         pairingReceived = 0;                              // ... so we continue here
10860         *swapColors = 0;
10861         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10862         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10863         matchGame = 1; roundNr = nr / syncInterval + 1;
10864     }
10865
10866     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10867
10868     // redefine engines, engine dir, etc.
10869     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10870     if(first.pr == NoProc) {
10871       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10872       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10873     }
10874     if(second.pr == NoProc) {
10875       SwapEngines(1);
10876       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10877       SwapEngines(1);         // and make that valid for second engine by swapping
10878       InitEngine(&second, 1);
10879     }
10880     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10881     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10882     return OK;
10883 }
10884
10885 void
10886 NextMatchGame ()
10887 {   // performs game initialization that does not invoke engines, and then tries to start the game
10888     int res, firstWhite, swapColors = 0;
10889     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10890     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
10891         char buf[MSG_SIZ];
10892         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10893         if(strcmp(buf, currentDebugFile)) { // name has changed
10894             FILE *f = fopen(buf, "w");
10895             if(f) { // if opening the new file failed, just keep using the old one
10896                 ASSIGN(currentDebugFile, buf);
10897                 fclose(debugFP);
10898                 debugFP = f;
10899             }
10900             if(appData.serverFileName) {
10901                 if(serverFP) fclose(serverFP);
10902                 serverFP = fopen(appData.serverFileName, "w");
10903                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10904                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10905             }
10906         }
10907     }
10908     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10909     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10910     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10911     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10912     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10913     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10914     Reset(FALSE, first.pr != NoProc);
10915     res = LoadGameOrPosition(matchGame); // setup game
10916     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10917     if(!res) return; // abort when bad game/pos file
10918     TwoMachinesEvent();
10919 }
10920
10921 void
10922 UserAdjudicationEvent (int result)
10923 {
10924     ChessMove gameResult = GameIsDrawn;
10925
10926     if( result > 0 ) {
10927         gameResult = WhiteWins;
10928     }
10929     else if( result < 0 ) {
10930         gameResult = BlackWins;
10931     }
10932
10933     if( gameMode == TwoMachinesPlay ) {
10934         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10935     }
10936 }
10937
10938
10939 // [HGM] save: calculate checksum of game to make games easily identifiable
10940 int
10941 StringCheckSum (char *s)
10942 {
10943         int i = 0;
10944         if(s==NULL) return 0;
10945         while(*s) i = i*259 + *s++;
10946         return i;
10947 }
10948
10949 int
10950 GameCheckSum ()
10951 {
10952         int i, sum=0;
10953         for(i=backwardMostMove; i<forwardMostMove; i++) {
10954                 sum += pvInfoList[i].depth;
10955                 sum += StringCheckSum(parseList[i]);
10956                 sum += StringCheckSum(commentList[i]);
10957                 sum *= 261;
10958         }
10959         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10960         return sum + StringCheckSum(commentList[i]);
10961 } // end of save patch
10962
10963 void
10964 GameEnds (ChessMove result, char *resultDetails, int whosays)
10965 {
10966     GameMode nextGameMode;
10967     int isIcsGame;
10968     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10969
10970     if(endingGame) return; /* [HGM] crash: forbid recursion */
10971     endingGame = 1;
10972     if(twoBoards) { // [HGM] dual: switch back to one board
10973         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10974         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10975     }
10976     if (appData.debugMode) {
10977       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10978               result, resultDetails ? resultDetails : "(null)", whosays);
10979     }
10980
10981     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10982
10983     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10984
10985     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10986         /* If we are playing on ICS, the server decides when the
10987            game is over, but the engine can offer to draw, claim
10988            a draw, or resign.
10989          */
10990 #if ZIPPY
10991         if (appData.zippyPlay && first.initDone) {
10992             if (result == GameIsDrawn) {
10993                 /* In case draw still needs to be claimed */
10994                 SendToICS(ics_prefix);
10995                 SendToICS("draw\n");
10996             } else if (StrCaseStr(resultDetails, "resign")) {
10997                 SendToICS(ics_prefix);
10998                 SendToICS("resign\n");
10999             }
11000         }
11001 #endif
11002         endingGame = 0; /* [HGM] crash */
11003         return;
11004     }
11005
11006     /* If we're loading the game from a file, stop */
11007     if (whosays == GE_FILE) {
11008       (void) StopLoadGameTimer();
11009       gameFileFP = NULL;
11010     }
11011
11012     /* Cancel draw offers */
11013     first.offeredDraw = second.offeredDraw = 0;
11014
11015     /* If this is an ICS game, only ICS can really say it's done;
11016        if not, anyone can. */
11017     isIcsGame = (gameMode == IcsPlayingWhite ||
11018                  gameMode == IcsPlayingBlack ||
11019                  gameMode == IcsObserving    ||
11020                  gameMode == IcsExamining);
11021
11022     if (!isIcsGame || whosays == GE_ICS) {
11023         /* OK -- not an ICS game, or ICS said it was done */
11024         StopClocks();
11025         if (!isIcsGame && !appData.noChessProgram)
11026           SetUserThinkingEnables();
11027
11028         /* [HGM] if a machine claims the game end we verify this claim */
11029         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11030             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11031                 char claimer;
11032                 ChessMove trueResult = (ChessMove) -1;
11033
11034                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11035                                             first.twoMachinesColor[0] :
11036                                             second.twoMachinesColor[0] ;
11037
11038                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11039                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11040                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11041                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11042                 } else
11043                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11044                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11045                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11046                 } else
11047                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11048                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11049                 }
11050
11051                 // now verify win claims, but not in drop games, as we don't understand those yet
11052                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11053                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11054                     (result == WhiteWins && claimer == 'w' ||
11055                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11056                       if (appData.debugMode) {
11057                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11058                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11059                       }
11060                       if(result != trueResult) {
11061                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11062                               result = claimer == 'w' ? BlackWins : WhiteWins;
11063                               resultDetails = buf;
11064                       }
11065                 } else
11066                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11067                     && (forwardMostMove <= backwardMostMove ||
11068                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11069                         (claimer=='b')==(forwardMostMove&1))
11070                                                                                   ) {
11071                       /* [HGM] verify: draws that were not flagged are false claims */
11072                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11073                       result = claimer == 'w' ? BlackWins : WhiteWins;
11074                       resultDetails = buf;
11075                 }
11076                 /* (Claiming a loss is accepted no questions asked!) */
11077             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11078                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11079                 result = GameUnfinished;
11080                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11081             }
11082             /* [HGM] bare: don't allow bare King to win */
11083             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11084                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11085                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11086                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11087                && result != GameIsDrawn)
11088             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11089                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11090                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11091                         if(p >= 0 && p <= (int)WhiteKing) k++;
11092                 }
11093                 if (appData.debugMode) {
11094                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11095                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11096                 }
11097                 if(k <= 1) {
11098                         result = GameIsDrawn;
11099                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11100                         resultDetails = buf;
11101                 }
11102             }
11103         }
11104
11105
11106         if(serverMoves != NULL && !loadFlag) { char c = '=';
11107             if(result==WhiteWins) c = '+';
11108             if(result==BlackWins) c = '-';
11109             if(resultDetails != NULL)
11110                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11111         }
11112         if (resultDetails != NULL) {
11113             gameInfo.result = result;
11114             gameInfo.resultDetails = StrSave(resultDetails);
11115
11116             /* display last move only if game was not loaded from file */
11117             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11118                 DisplayMove(currentMove - 1);
11119
11120             if (forwardMostMove != 0) {
11121                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11122                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11123                                                                 ) {
11124                     if (*appData.saveGameFile != NULLCHAR) {
11125                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11126                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11127                         else
11128                         SaveGameToFile(appData.saveGameFile, TRUE);
11129                     } else if (appData.autoSaveGames) {
11130                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11131                     }
11132                     if (*appData.savePositionFile != NULLCHAR) {
11133                         SavePositionToFile(appData.savePositionFile);
11134                     }
11135                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11136                 }
11137             }
11138
11139             /* Tell program how game ended in case it is learning */
11140             /* [HGM] Moved this to after saving the PGN, just in case */
11141             /* engine died and we got here through time loss. In that */
11142             /* case we will get a fatal error writing the pipe, which */
11143             /* would otherwise lose us the PGN.                       */
11144             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11145             /* output during GameEnds should never be fatal anymore   */
11146             if (gameMode == MachinePlaysWhite ||
11147                 gameMode == MachinePlaysBlack ||
11148                 gameMode == TwoMachinesPlay ||
11149                 gameMode == IcsPlayingWhite ||
11150                 gameMode == IcsPlayingBlack ||
11151                 gameMode == BeginningOfGame) {
11152                 char buf[MSG_SIZ];
11153                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11154                         resultDetails);
11155                 if (first.pr != NoProc) {
11156                     SendToProgram(buf, &first);
11157                 }
11158                 if (second.pr != NoProc &&
11159                     gameMode == TwoMachinesPlay) {
11160                     SendToProgram(buf, &second);
11161                 }
11162             }
11163         }
11164
11165         if (appData.icsActive) {
11166             if (appData.quietPlay &&
11167                 (gameMode == IcsPlayingWhite ||
11168                  gameMode == IcsPlayingBlack)) {
11169                 SendToICS(ics_prefix);
11170                 SendToICS("set shout 1\n");
11171             }
11172             nextGameMode = IcsIdle;
11173             ics_user_moved = FALSE;
11174             /* clean up premove.  It's ugly when the game has ended and the
11175              * premove highlights are still on the board.
11176              */
11177             if (gotPremove) {
11178               gotPremove = FALSE;
11179               ClearPremoveHighlights();
11180               DrawPosition(FALSE, boards[currentMove]);
11181             }
11182             if (whosays == GE_ICS) {
11183                 switch (result) {
11184                 case WhiteWins:
11185                     if (gameMode == IcsPlayingWhite)
11186                         PlayIcsWinSound();
11187                     else if(gameMode == IcsPlayingBlack)
11188                         PlayIcsLossSound();
11189                     break;
11190                 case BlackWins:
11191                     if (gameMode == IcsPlayingBlack)
11192                         PlayIcsWinSound();
11193                     else if(gameMode == IcsPlayingWhite)
11194                         PlayIcsLossSound();
11195                     break;
11196                 case GameIsDrawn:
11197                     PlayIcsDrawSound();
11198                     break;
11199                 default:
11200                     PlayIcsUnfinishedSound();
11201                 }
11202             }
11203             if(appData.quitNext) { ExitEvent(0); return; }
11204         } else if (gameMode == EditGame ||
11205                    gameMode == PlayFromGameFile ||
11206                    gameMode == AnalyzeMode ||
11207                    gameMode == AnalyzeFile) {
11208             nextGameMode = gameMode;
11209         } else {
11210             nextGameMode = EndOfGame;
11211         }
11212         pausing = FALSE;
11213         ModeHighlight();
11214     } else {
11215         nextGameMode = gameMode;
11216     }
11217
11218     if (appData.noChessProgram) {
11219         gameMode = nextGameMode;
11220         ModeHighlight();
11221         endingGame = 0; /* [HGM] crash */
11222         return;
11223     }
11224
11225     if (first.reuse) {
11226         /* Put first chess program into idle state */
11227         if (first.pr != NoProc &&
11228             (gameMode == MachinePlaysWhite ||
11229              gameMode == MachinePlaysBlack ||
11230              gameMode == TwoMachinesPlay ||
11231              gameMode == IcsPlayingWhite ||
11232              gameMode == IcsPlayingBlack ||
11233              gameMode == BeginningOfGame)) {
11234             SendToProgram("force\n", &first);
11235             if (first.usePing) {
11236               char buf[MSG_SIZ];
11237               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11238               SendToProgram(buf, &first);
11239             }
11240         }
11241     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11242         /* Kill off first chess program */
11243         if (first.isr != NULL)
11244           RemoveInputSource(first.isr);
11245         first.isr = NULL;
11246
11247         if (first.pr != NoProc) {
11248             ExitAnalyzeMode();
11249             DoSleep( appData.delayBeforeQuit );
11250             SendToProgram("quit\n", &first);
11251             DoSleep( appData.delayAfterQuit );
11252             DestroyChildProcess(first.pr, first.useSigterm);
11253             first.reload = TRUE;
11254         }
11255         first.pr = NoProc;
11256     }
11257     if (second.reuse) {
11258         /* Put second chess program into idle state */
11259         if (second.pr != NoProc &&
11260             gameMode == TwoMachinesPlay) {
11261             SendToProgram("force\n", &second);
11262             if (second.usePing) {
11263               char buf[MSG_SIZ];
11264               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11265               SendToProgram(buf, &second);
11266             }
11267         }
11268     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11269         /* Kill off second chess program */
11270         if (second.isr != NULL)
11271           RemoveInputSource(second.isr);
11272         second.isr = NULL;
11273
11274         if (second.pr != NoProc) {
11275             DoSleep( appData.delayBeforeQuit );
11276             SendToProgram("quit\n", &second);
11277             DoSleep( appData.delayAfterQuit );
11278             DestroyChildProcess(second.pr, second.useSigterm);
11279             second.reload = TRUE;
11280         }
11281         second.pr = NoProc;
11282     }
11283
11284     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11285         char resChar = '=';
11286         switch (result) {
11287         case WhiteWins:
11288           resChar = '+';
11289           if (first.twoMachinesColor[0] == 'w') {
11290             first.matchWins++;
11291           } else {
11292             second.matchWins++;
11293           }
11294           break;
11295         case BlackWins:
11296           resChar = '-';
11297           if (first.twoMachinesColor[0] == 'b') {
11298             first.matchWins++;
11299           } else {
11300             second.matchWins++;
11301           }
11302           break;
11303         case GameUnfinished:
11304           resChar = ' ';
11305         default:
11306           break;
11307         }
11308
11309         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11310         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11311             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11312             ReserveGame(nextGame, resChar); // sets nextGame
11313             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11314             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11315         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11316
11317         if (nextGame <= appData.matchGames && !abortMatch) {
11318             gameMode = nextGameMode;
11319             matchGame = nextGame; // this will be overruled in tourney mode!
11320             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11321             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11322             endingGame = 0; /* [HGM] crash */
11323             return;
11324         } else {
11325             gameMode = nextGameMode;
11326             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11327                      first.tidy, second.tidy,
11328                      first.matchWins, second.matchWins,
11329                      appData.matchGames - (first.matchWins + second.matchWins));
11330             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11331             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11332             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11333             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11334                 first.twoMachinesColor = "black\n";
11335                 second.twoMachinesColor = "white\n";
11336             } else {
11337                 first.twoMachinesColor = "white\n";
11338                 second.twoMachinesColor = "black\n";
11339             }
11340         }
11341     }
11342     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11343         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11344       ExitAnalyzeMode();
11345     gameMode = nextGameMode;
11346     ModeHighlight();
11347     endingGame = 0;  /* [HGM] crash */
11348     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11349         if(matchMode == TRUE) { // match through command line: exit with or without popup
11350             if(ranking) {
11351                 ToNrEvent(forwardMostMove);
11352                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11353                 else ExitEvent(0);
11354             } else DisplayFatalError(buf, 0, 0);
11355         } else { // match through menu; just stop, with or without popup
11356             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11357             ModeHighlight();
11358             if(ranking){
11359                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11360             } else DisplayNote(buf);
11361       }
11362       if(ranking) free(ranking);
11363     }
11364 }
11365
11366 /* Assumes program was just initialized (initString sent).
11367    Leaves program in force mode. */
11368 void
11369 FeedMovesToProgram (ChessProgramState *cps, int upto)
11370 {
11371     int i;
11372
11373     if (appData.debugMode)
11374       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11375               startedFromSetupPosition ? "position and " : "",
11376               backwardMostMove, upto, cps->which);
11377     if(currentlyInitializedVariant != gameInfo.variant) {
11378       char buf[MSG_SIZ];
11379         // [HGM] variantswitch: make engine aware of new variant
11380         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11381                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11382                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11383         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11384         SendToProgram(buf, cps);
11385         currentlyInitializedVariant = gameInfo.variant;
11386     }
11387     SendToProgram("force\n", cps);
11388     if (startedFromSetupPosition) {
11389         SendBoard(cps, backwardMostMove);
11390     if (appData.debugMode) {
11391         fprintf(debugFP, "feedMoves\n");
11392     }
11393     }
11394     for (i = backwardMostMove; i < upto; i++) {
11395         SendMoveToProgram(i, cps);
11396     }
11397 }
11398
11399
11400 int
11401 ResurrectChessProgram ()
11402 {
11403      /* The chess program may have exited.
11404         If so, restart it and feed it all the moves made so far. */
11405     static int doInit = 0;
11406
11407     if (appData.noChessProgram) return 1;
11408
11409     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11410         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11411         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11412         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11413     } else {
11414         if (first.pr != NoProc) return 1;
11415         StartChessProgram(&first);
11416     }
11417     InitChessProgram(&first, FALSE);
11418     FeedMovesToProgram(&first, currentMove);
11419
11420     if (!first.sendTime) {
11421         /* can't tell gnuchess what its clock should read,
11422            so we bow to its notion. */
11423         ResetClocks();
11424         timeRemaining[0][currentMove] = whiteTimeRemaining;
11425         timeRemaining[1][currentMove] = blackTimeRemaining;
11426     }
11427
11428     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11429                 appData.icsEngineAnalyze) && first.analysisSupport) {
11430       SendToProgram("analyze\n", &first);
11431       first.analyzing = TRUE;
11432     }
11433     return 1;
11434 }
11435
11436 /*
11437  * Button procedures
11438  */
11439 void
11440 Reset (int redraw, int init)
11441 {
11442     int i;
11443
11444     if (appData.debugMode) {
11445         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11446                 redraw, init, gameMode);
11447     }
11448     CleanupTail(); // [HGM] vari: delete any stored variations
11449     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11450     pausing = pauseExamInvalid = FALSE;
11451     startedFromSetupPosition = blackPlaysFirst = FALSE;
11452     firstMove = TRUE;
11453     whiteFlag = blackFlag = FALSE;
11454     userOfferedDraw = FALSE;
11455     hintRequested = bookRequested = FALSE;
11456     first.maybeThinking = FALSE;
11457     second.maybeThinking = FALSE;
11458     first.bookSuspend = FALSE; // [HGM] book
11459     second.bookSuspend = FALSE;
11460     thinkOutput[0] = NULLCHAR;
11461     lastHint[0] = NULLCHAR;
11462     ClearGameInfo(&gameInfo);
11463     gameInfo.variant = StringToVariant(appData.variant);
11464     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11465     ics_user_moved = ics_clock_paused = FALSE;
11466     ics_getting_history = H_FALSE;
11467     ics_gamenum = -1;
11468     white_holding[0] = black_holding[0] = NULLCHAR;
11469     ClearProgramStats();
11470     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11471
11472     ResetFrontEnd();
11473     ClearHighlights();
11474     flipView = appData.flipView;
11475     ClearPremoveHighlights();
11476     gotPremove = FALSE;
11477     alarmSounded = FALSE;
11478     killX = killY = -1; // [HGM] lion
11479
11480     GameEnds(EndOfFile, NULL, GE_PLAYER);
11481     if(appData.serverMovesName != NULL) {
11482         /* [HGM] prepare to make moves file for broadcasting */
11483         clock_t t = clock();
11484         if(serverMoves != NULL) fclose(serverMoves);
11485         serverMoves = fopen(appData.serverMovesName, "r");
11486         if(serverMoves != NULL) {
11487             fclose(serverMoves);
11488             /* delay 15 sec before overwriting, so all clients can see end */
11489             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11490         }
11491         serverMoves = fopen(appData.serverMovesName, "w");
11492     }
11493
11494     ExitAnalyzeMode();
11495     gameMode = BeginningOfGame;
11496     ModeHighlight();
11497     if(appData.icsActive) gameInfo.variant = VariantNormal;
11498     currentMove = forwardMostMove = backwardMostMove = 0;
11499     MarkTargetSquares(1);
11500     InitPosition(redraw);
11501     for (i = 0; i < MAX_MOVES; i++) {
11502         if (commentList[i] != NULL) {
11503             free(commentList[i]);
11504             commentList[i] = NULL;
11505         }
11506     }
11507     ResetClocks();
11508     timeRemaining[0][0] = whiteTimeRemaining;
11509     timeRemaining[1][0] = blackTimeRemaining;
11510
11511     if (first.pr == NoProc) {
11512         StartChessProgram(&first);
11513     }
11514     if (init) {
11515             InitChessProgram(&first, startedFromSetupPosition);
11516     }
11517     DisplayTitle("");
11518     DisplayMessage("", "");
11519     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11520     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11521     ClearMap();        // [HGM] exclude: invalidate map
11522 }
11523
11524 void
11525 AutoPlayGameLoop ()
11526 {
11527     for (;;) {
11528         if (!AutoPlayOneMove())
11529           return;
11530         if (matchMode || appData.timeDelay == 0)
11531           continue;
11532         if (appData.timeDelay < 0)
11533           return;
11534         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11535         break;
11536     }
11537 }
11538
11539 void
11540 AnalyzeNextGame()
11541 {
11542     ReloadGame(1); // next game
11543 }
11544
11545 int
11546 AutoPlayOneMove ()
11547 {
11548     int fromX, fromY, toX, toY;
11549
11550     if (appData.debugMode) {
11551       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11552     }
11553
11554     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11555       return FALSE;
11556
11557     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11558       pvInfoList[currentMove].depth = programStats.depth;
11559       pvInfoList[currentMove].score = programStats.score;
11560       pvInfoList[currentMove].time  = 0;
11561       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11562       else { // append analysis of final position as comment
11563         char buf[MSG_SIZ];
11564         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11565         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11566       }
11567       programStats.depth = 0;
11568     }
11569
11570     if (currentMove >= forwardMostMove) {
11571       if(gameMode == AnalyzeFile) {
11572           if(appData.loadGameIndex == -1) {
11573             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11574           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11575           } else {
11576           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11577         }
11578       }
11579 //      gameMode = EndOfGame;
11580 //      ModeHighlight();
11581
11582       /* [AS] Clear current move marker at the end of a game */
11583       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11584
11585       return FALSE;
11586     }
11587
11588     toX = moveList[currentMove][2] - AAA;
11589     toY = moveList[currentMove][3] - ONE;
11590
11591     if (moveList[currentMove][1] == '@') {
11592         if (appData.highlightLastMove) {
11593             SetHighlights(-1, -1, toX, toY);
11594         }
11595     } else {
11596         fromX = moveList[currentMove][0] - AAA;
11597         fromY = moveList[currentMove][1] - ONE;
11598
11599         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11600
11601         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11602
11603         if (appData.highlightLastMove) {
11604             SetHighlights(fromX, fromY, toX, toY);
11605         }
11606     }
11607     DisplayMove(currentMove);
11608     SendMoveToProgram(currentMove++, &first);
11609     DisplayBothClocks();
11610     DrawPosition(FALSE, boards[currentMove]);
11611     // [HGM] PV info: always display, routine tests if empty
11612     DisplayComment(currentMove - 1, commentList[currentMove]);
11613     return TRUE;
11614 }
11615
11616
11617 int
11618 LoadGameOneMove (ChessMove readAhead)
11619 {
11620     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11621     char promoChar = NULLCHAR;
11622     ChessMove moveType;
11623     char move[MSG_SIZ];
11624     char *p, *q;
11625
11626     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11627         gameMode != AnalyzeMode && gameMode != Training) {
11628         gameFileFP = NULL;
11629         return FALSE;
11630     }
11631
11632     yyboardindex = forwardMostMove;
11633     if (readAhead != EndOfFile) {
11634       moveType = readAhead;
11635     } else {
11636       if (gameFileFP == NULL)
11637           return FALSE;
11638       moveType = (ChessMove) Myylex();
11639     }
11640
11641     done = FALSE;
11642     switch (moveType) {
11643       case Comment:
11644         if (appData.debugMode)
11645           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11646         p = yy_text;
11647
11648         /* append the comment but don't display it */
11649         AppendComment(currentMove, p, FALSE);
11650         return TRUE;
11651
11652       case WhiteCapturesEnPassant:
11653       case BlackCapturesEnPassant:
11654       case WhitePromotion:
11655       case BlackPromotion:
11656       case WhiteNonPromotion:
11657       case BlackNonPromotion:
11658       case NormalMove:
11659       case FirstLeg:
11660       case WhiteKingSideCastle:
11661       case WhiteQueenSideCastle:
11662       case BlackKingSideCastle:
11663       case BlackQueenSideCastle:
11664       case WhiteKingSideCastleWild:
11665       case WhiteQueenSideCastleWild:
11666       case BlackKingSideCastleWild:
11667       case BlackQueenSideCastleWild:
11668       /* PUSH Fabien */
11669       case WhiteHSideCastleFR:
11670       case WhiteASideCastleFR:
11671       case BlackHSideCastleFR:
11672       case BlackASideCastleFR:
11673       /* POP Fabien */
11674         if (appData.debugMode)
11675           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11676         fromX = currentMoveString[0] - AAA;
11677         fromY = currentMoveString[1] - ONE;
11678         toX = currentMoveString[2] - AAA;
11679         toY = currentMoveString[3] - ONE;
11680         promoChar = currentMoveString[4];
11681         if(promoChar == ';') promoChar = NULLCHAR;
11682         break;
11683
11684       case WhiteDrop:
11685       case BlackDrop:
11686         if (appData.debugMode)
11687           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11688         fromX = moveType == WhiteDrop ?
11689           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11690         (int) CharToPiece(ToLower(currentMoveString[0]));
11691         fromY = DROP_RANK;
11692         toX = currentMoveString[2] - AAA;
11693         toY = currentMoveString[3] - ONE;
11694         break;
11695
11696       case WhiteWins:
11697       case BlackWins:
11698       case GameIsDrawn:
11699       case GameUnfinished:
11700         if (appData.debugMode)
11701           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11702         p = strchr(yy_text, '{');
11703         if (p == NULL) p = strchr(yy_text, '(');
11704         if (p == NULL) {
11705             p = yy_text;
11706             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11707         } else {
11708             q = strchr(p, *p == '{' ? '}' : ')');
11709             if (q != NULL) *q = NULLCHAR;
11710             p++;
11711         }
11712         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11713         GameEnds(moveType, p, GE_FILE);
11714         done = TRUE;
11715         if (cmailMsgLoaded) {
11716             ClearHighlights();
11717             flipView = WhiteOnMove(currentMove);
11718             if (moveType == GameUnfinished) flipView = !flipView;
11719             if (appData.debugMode)
11720               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11721         }
11722         break;
11723
11724       case EndOfFile:
11725         if (appData.debugMode)
11726           fprintf(debugFP, "Parser hit end of file\n");
11727         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11728           case MT_NONE:
11729           case MT_CHECK:
11730             break;
11731           case MT_CHECKMATE:
11732           case MT_STAINMATE:
11733             if (WhiteOnMove(currentMove)) {
11734                 GameEnds(BlackWins, "Black mates", GE_FILE);
11735             } else {
11736                 GameEnds(WhiteWins, "White mates", GE_FILE);
11737             }
11738             break;
11739           case MT_STALEMATE:
11740             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11741             break;
11742         }
11743         done = TRUE;
11744         break;
11745
11746       case MoveNumberOne:
11747         if (lastLoadGameStart == GNUChessGame) {
11748             /* GNUChessGames have numbers, but they aren't move numbers */
11749             if (appData.debugMode)
11750               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11751                       yy_text, (int) moveType);
11752             return LoadGameOneMove(EndOfFile); /* tail recursion */
11753         }
11754         /* else fall thru */
11755
11756       case XBoardGame:
11757       case GNUChessGame:
11758       case PGNTag:
11759         /* Reached start of next game in file */
11760         if (appData.debugMode)
11761           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11762         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11763           case MT_NONE:
11764           case MT_CHECK:
11765             break;
11766           case MT_CHECKMATE:
11767           case MT_STAINMATE:
11768             if (WhiteOnMove(currentMove)) {
11769                 GameEnds(BlackWins, "Black mates", GE_FILE);
11770             } else {
11771                 GameEnds(WhiteWins, "White mates", GE_FILE);
11772             }
11773             break;
11774           case MT_STALEMATE:
11775             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11776             break;
11777         }
11778         done = TRUE;
11779         break;
11780
11781       case PositionDiagram:     /* should not happen; ignore */
11782       case ElapsedTime:         /* ignore */
11783       case NAG:                 /* ignore */
11784         if (appData.debugMode)
11785           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11786                   yy_text, (int) moveType);
11787         return LoadGameOneMove(EndOfFile); /* tail recursion */
11788
11789       case IllegalMove:
11790         if (appData.testLegality) {
11791             if (appData.debugMode)
11792               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11793             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11794                     (forwardMostMove / 2) + 1,
11795                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11796             DisplayError(move, 0);
11797             done = TRUE;
11798         } else {
11799             if (appData.debugMode)
11800               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11801                       yy_text, currentMoveString);
11802             fromX = currentMoveString[0] - AAA;
11803             fromY = currentMoveString[1] - ONE;
11804             toX = currentMoveString[2] - AAA;
11805             toY = currentMoveString[3] - ONE;
11806             promoChar = currentMoveString[4];
11807         }
11808         break;
11809
11810       case AmbiguousMove:
11811         if (appData.debugMode)
11812           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11813         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11814                 (forwardMostMove / 2) + 1,
11815                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11816         DisplayError(move, 0);
11817         done = TRUE;
11818         break;
11819
11820       default:
11821       case ImpossibleMove:
11822         if (appData.debugMode)
11823           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11824         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11825                 (forwardMostMove / 2) + 1,
11826                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11827         DisplayError(move, 0);
11828         done = TRUE;
11829         break;
11830     }
11831
11832     if (done) {
11833         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11834             DrawPosition(FALSE, boards[currentMove]);
11835             DisplayBothClocks();
11836             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11837               DisplayComment(currentMove - 1, commentList[currentMove]);
11838         }
11839         (void) StopLoadGameTimer();
11840         gameFileFP = NULL;
11841         cmailOldMove = forwardMostMove;
11842         return FALSE;
11843     } else {
11844         /* currentMoveString is set as a side-effect of yylex */
11845
11846         thinkOutput[0] = NULLCHAR;
11847         MakeMove(fromX, fromY, toX, toY, promoChar);
11848         killX = killY = -1; // [HGM] lion: used up
11849         currentMove = forwardMostMove;
11850         return TRUE;
11851     }
11852 }
11853
11854 /* Load the nth game from the given file */
11855 int
11856 LoadGameFromFile (char *filename, int n, char *title, int useList)
11857 {
11858     FILE *f;
11859     char buf[MSG_SIZ];
11860
11861     if (strcmp(filename, "-") == 0) {
11862         f = stdin;
11863         title = "stdin";
11864     } else {
11865         f = fopen(filename, "rb");
11866         if (f == NULL) {
11867           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11868             DisplayError(buf, errno);
11869             return FALSE;
11870         }
11871     }
11872     if (fseek(f, 0, 0) == -1) {
11873         /* f is not seekable; probably a pipe */
11874         useList = FALSE;
11875     }
11876     if (useList && n == 0) {
11877         int error = GameListBuild(f);
11878         if (error) {
11879             DisplayError(_("Cannot build game list"), error);
11880         } else if (!ListEmpty(&gameList) &&
11881                    ((ListGame *) gameList.tailPred)->number > 1) {
11882             GameListPopUp(f, title);
11883             return TRUE;
11884         }
11885         GameListDestroy();
11886         n = 1;
11887     }
11888     if (n == 0) n = 1;
11889     return LoadGame(f, n, title, FALSE);
11890 }
11891
11892
11893 void
11894 MakeRegisteredMove ()
11895 {
11896     int fromX, fromY, toX, toY;
11897     char promoChar;
11898     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11899         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11900           case CMAIL_MOVE:
11901           case CMAIL_DRAW:
11902             if (appData.debugMode)
11903               fprintf(debugFP, "Restoring %s for game %d\n",
11904                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11905
11906             thinkOutput[0] = NULLCHAR;
11907             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11908             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11909             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11910             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11911             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11912             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11913             MakeMove(fromX, fromY, toX, toY, promoChar);
11914             ShowMove(fromX, fromY, toX, toY);
11915
11916             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11917               case MT_NONE:
11918               case MT_CHECK:
11919                 break;
11920
11921               case MT_CHECKMATE:
11922               case MT_STAINMATE:
11923                 if (WhiteOnMove(currentMove)) {
11924                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11925                 } else {
11926                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11927                 }
11928                 break;
11929
11930               case MT_STALEMATE:
11931                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11932                 break;
11933             }
11934
11935             break;
11936
11937           case CMAIL_RESIGN:
11938             if (WhiteOnMove(currentMove)) {
11939                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11940             } else {
11941                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11942             }
11943             break;
11944
11945           case CMAIL_ACCEPT:
11946             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11947             break;
11948
11949           default:
11950             break;
11951         }
11952     }
11953
11954     return;
11955 }
11956
11957 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11958 int
11959 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11960 {
11961     int retVal;
11962
11963     if (gameNumber > nCmailGames) {
11964         DisplayError(_("No more games in this message"), 0);
11965         return FALSE;
11966     }
11967     if (f == lastLoadGameFP) {
11968         int offset = gameNumber - lastLoadGameNumber;
11969         if (offset == 0) {
11970             cmailMsg[0] = NULLCHAR;
11971             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11972                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11973                 nCmailMovesRegistered--;
11974             }
11975             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11976             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11977                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11978             }
11979         } else {
11980             if (! RegisterMove()) return FALSE;
11981         }
11982     }
11983
11984     retVal = LoadGame(f, gameNumber, title, useList);
11985
11986     /* Make move registered during previous look at this game, if any */
11987     MakeRegisteredMove();
11988
11989     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11990         commentList[currentMove]
11991           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11992         DisplayComment(currentMove - 1, commentList[currentMove]);
11993     }
11994
11995     return retVal;
11996 }
11997
11998 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11999 int
12000 ReloadGame (int offset)
12001 {
12002     int gameNumber = lastLoadGameNumber + offset;
12003     if (lastLoadGameFP == NULL) {
12004         DisplayError(_("No game has been loaded yet"), 0);
12005         return FALSE;
12006     }
12007     if (gameNumber <= 0) {
12008         DisplayError(_("Can't back up any further"), 0);
12009         return FALSE;
12010     }
12011     if (cmailMsgLoaded) {
12012         return CmailLoadGame(lastLoadGameFP, gameNumber,
12013                              lastLoadGameTitle, lastLoadGameUseList);
12014     } else {
12015         return LoadGame(lastLoadGameFP, gameNumber,
12016                         lastLoadGameTitle, lastLoadGameUseList);
12017     }
12018 }
12019
12020 int keys[EmptySquare+1];
12021
12022 int
12023 PositionMatches (Board b1, Board b2)
12024 {
12025     int r, f, sum=0;
12026     switch(appData.searchMode) {
12027         case 1: return CompareWithRights(b1, b2);
12028         case 2:
12029             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12030                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12031             }
12032             return TRUE;
12033         case 3:
12034             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12035               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12036                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12037             }
12038             return sum==0;
12039         case 4:
12040             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12041                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12042             }
12043             return sum==0;
12044     }
12045     return TRUE;
12046 }
12047
12048 #define Q_PROMO  4
12049 #define Q_EP     3
12050 #define Q_BCASTL 2
12051 #define Q_WCASTL 1
12052
12053 int pieceList[256], quickBoard[256];
12054 ChessSquare pieceType[256] = { EmptySquare };
12055 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12056 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12057 int soughtTotal, turn;
12058 Boolean epOK, flipSearch;
12059
12060 typedef struct {
12061     unsigned char piece, to;
12062 } Move;
12063
12064 #define DSIZE (250000)
12065
12066 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12067 Move *moveDatabase = initialSpace;
12068 unsigned int movePtr, dataSize = DSIZE;
12069
12070 int
12071 MakePieceList (Board board, int *counts)
12072 {
12073     int r, f, n=Q_PROMO, total=0;
12074     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12075     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12076         int sq = f + (r<<4);
12077         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12078             quickBoard[sq] = ++n;
12079             pieceList[n] = sq;
12080             pieceType[n] = board[r][f];
12081             counts[board[r][f]]++;
12082             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12083             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12084             total++;
12085         }
12086     }
12087     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12088     return total;
12089 }
12090
12091 void
12092 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12093 {
12094     int sq = fromX + (fromY<<4);
12095     int piece = quickBoard[sq];
12096     quickBoard[sq] = 0;
12097     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12098     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12099         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12100         moveDatabase[movePtr++].piece = Q_WCASTL;
12101         quickBoard[sq] = piece;
12102         piece = quickBoard[from]; quickBoard[from] = 0;
12103         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12104     } else
12105     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12106         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12107         moveDatabase[movePtr++].piece = Q_BCASTL;
12108         quickBoard[sq] = piece;
12109         piece = quickBoard[from]; quickBoard[from] = 0;
12110         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12111     } else
12112     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12113         quickBoard[(fromY<<4)+toX] = 0;
12114         moveDatabase[movePtr].piece = Q_EP;
12115         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12116         moveDatabase[movePtr].to = sq;
12117     } else
12118     if(promoPiece != pieceType[piece]) {
12119         moveDatabase[movePtr++].piece = Q_PROMO;
12120         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12121     }
12122     moveDatabase[movePtr].piece = piece;
12123     quickBoard[sq] = piece;
12124     movePtr++;
12125 }
12126
12127 int
12128 PackGame (Board board)
12129 {
12130     Move *newSpace = NULL;
12131     moveDatabase[movePtr].piece = 0; // terminate previous game
12132     if(movePtr > dataSize) {
12133         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12134         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12135         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12136         if(newSpace) {
12137             int i;
12138             Move *p = moveDatabase, *q = newSpace;
12139             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12140             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12141             moveDatabase = newSpace;
12142         } else { // calloc failed, we must be out of memory. Too bad...
12143             dataSize = 0; // prevent calloc events for all subsequent games
12144             return 0;     // and signal this one isn't cached
12145         }
12146     }
12147     movePtr++;
12148     MakePieceList(board, counts);
12149     return movePtr;
12150 }
12151
12152 int
12153 QuickCompare (Board board, int *minCounts, int *maxCounts)
12154 {   // compare according to search mode
12155     int r, f;
12156     switch(appData.searchMode)
12157     {
12158       case 1: // exact position match
12159         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12160         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12161             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12162         }
12163         break;
12164       case 2: // can have extra material on empty squares
12165         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12166             if(board[r][f] == EmptySquare) continue;
12167             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12168         }
12169         break;
12170       case 3: // material with exact Pawn structure
12171         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12172             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12173             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12174         } // fall through to material comparison
12175       case 4: // exact material
12176         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12177         break;
12178       case 6: // material range with given imbalance
12179         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12180         // fall through to range comparison
12181       case 5: // material range
12182         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12183     }
12184     return TRUE;
12185 }
12186
12187 int
12188 QuickScan (Board board, Move *move)
12189 {   // reconstruct game,and compare all positions in it
12190     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12191     do {
12192         int piece = move->piece;
12193         int to = move->to, from = pieceList[piece];
12194         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12195           if(!piece) return -1;
12196           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12197             piece = (++move)->piece;
12198             from = pieceList[piece];
12199             counts[pieceType[piece]]--;
12200             pieceType[piece] = (ChessSquare) move->to;
12201             counts[move->to]++;
12202           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12203             counts[pieceType[quickBoard[to]]]--;
12204             quickBoard[to] = 0; total--;
12205             move++;
12206             continue;
12207           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12208             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12209             from  = pieceList[piece]; // so this must be King
12210             quickBoard[from] = 0;
12211             pieceList[piece] = to;
12212             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12213             quickBoard[from] = 0; // rook
12214             quickBoard[to] = piece;
12215             to = move->to; piece = move->piece;
12216             goto aftercastle;
12217           }
12218         }
12219         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12220         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12221         quickBoard[from] = 0;
12222       aftercastle:
12223         quickBoard[to] = piece;
12224         pieceList[piece] = to;
12225         cnt++; turn ^= 3;
12226         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12227            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12228            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12229                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12230           ) {
12231             static int lastCounts[EmptySquare+1];
12232             int i;
12233             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12234             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12235         } else stretch = 0;
12236         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12237         move++;
12238     } while(1);
12239 }
12240
12241 void
12242 InitSearch ()
12243 {
12244     int r, f;
12245     flipSearch = FALSE;
12246     CopyBoard(soughtBoard, boards[currentMove]);
12247     soughtTotal = MakePieceList(soughtBoard, maxSought);
12248     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12249     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12250     CopyBoard(reverseBoard, boards[currentMove]);
12251     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12252         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12253         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12254         reverseBoard[r][f] = piece;
12255     }
12256     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12257     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12258     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12259                  || (boards[currentMove][CASTLING][2] == NoRights ||
12260                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12261                  && (boards[currentMove][CASTLING][5] == NoRights ||
12262                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12263       ) {
12264         flipSearch = TRUE;
12265         CopyBoard(flipBoard, soughtBoard);
12266         CopyBoard(rotateBoard, reverseBoard);
12267         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12268             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12269             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12270         }
12271     }
12272     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12273     if(appData.searchMode >= 5) {
12274         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12275         MakePieceList(soughtBoard, minSought);
12276         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12277     }
12278     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12279         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12280 }
12281
12282 GameInfo dummyInfo;
12283 static int creatingBook;
12284
12285 int
12286 GameContainsPosition (FILE *f, ListGame *lg)
12287 {
12288     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12289     int fromX, fromY, toX, toY;
12290     char promoChar;
12291     static int initDone=FALSE;
12292
12293     // weed out games based on numerical tag comparison
12294     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12295     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12296     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12297     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12298     if(!initDone) {
12299         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12300         initDone = TRUE;
12301     }
12302     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12303     else CopyBoard(boards[scratch], initialPosition); // default start position
12304     if(lg->moves) {
12305         turn = btm + 1;
12306         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12307         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12308     }
12309     if(btm) plyNr++;
12310     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12311     fseek(f, lg->offset, 0);
12312     yynewfile(f);
12313     while(1) {
12314         yyboardindex = scratch;
12315         quickFlag = plyNr+1;
12316         next = Myylex();
12317         quickFlag = 0;
12318         switch(next) {
12319             case PGNTag:
12320                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12321             default:
12322                 continue;
12323
12324             case XBoardGame:
12325             case GNUChessGame:
12326                 if(plyNr) return -1; // after we have seen moves, this is for new game
12327               continue;
12328
12329             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12330             case ImpossibleMove:
12331             case WhiteWins: // game ends here with these four
12332             case BlackWins:
12333             case GameIsDrawn:
12334             case GameUnfinished:
12335                 return -1;
12336
12337             case IllegalMove:
12338                 if(appData.testLegality) return -1;
12339             case WhiteCapturesEnPassant:
12340             case BlackCapturesEnPassant:
12341             case WhitePromotion:
12342             case BlackPromotion:
12343             case WhiteNonPromotion:
12344             case BlackNonPromotion:
12345             case NormalMove:
12346             case FirstLeg:
12347             case WhiteKingSideCastle:
12348             case WhiteQueenSideCastle:
12349             case BlackKingSideCastle:
12350             case BlackQueenSideCastle:
12351             case WhiteKingSideCastleWild:
12352             case WhiteQueenSideCastleWild:
12353             case BlackKingSideCastleWild:
12354             case BlackQueenSideCastleWild:
12355             case WhiteHSideCastleFR:
12356             case WhiteASideCastleFR:
12357             case BlackHSideCastleFR:
12358             case BlackASideCastleFR:
12359                 fromX = currentMoveString[0] - AAA;
12360                 fromY = currentMoveString[1] - ONE;
12361                 toX = currentMoveString[2] - AAA;
12362                 toY = currentMoveString[3] - ONE;
12363                 promoChar = currentMoveString[4];
12364                 break;
12365             case WhiteDrop:
12366             case BlackDrop:
12367                 fromX = next == WhiteDrop ?
12368                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12369                   (int) CharToPiece(ToLower(currentMoveString[0]));
12370                 fromY = DROP_RANK;
12371                 toX = currentMoveString[2] - AAA;
12372                 toY = currentMoveString[3] - ONE;
12373                 promoChar = 0;
12374                 break;
12375         }
12376         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12377         plyNr++;
12378         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12379         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12380         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12381         if(appData.findMirror) {
12382             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12383             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12384         }
12385     }
12386 }
12387
12388 /* Load the nth game from open file f */
12389 int
12390 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12391 {
12392     ChessMove cm;
12393     char buf[MSG_SIZ];
12394     int gn = gameNumber;
12395     ListGame *lg = NULL;
12396     int numPGNTags = 0;
12397     int err, pos = -1;
12398     GameMode oldGameMode;
12399     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12400
12401     if (appData.debugMode)
12402         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12403
12404     if (gameMode == Training )
12405         SetTrainingModeOff();
12406
12407     oldGameMode = gameMode;
12408     if (gameMode != BeginningOfGame) {
12409       Reset(FALSE, TRUE);
12410     }
12411     killX = killY = -1; // [HGM] lion: in case we did not Reset
12412
12413     gameFileFP = f;
12414     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12415         fclose(lastLoadGameFP);
12416     }
12417
12418     if (useList) {
12419         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12420
12421         if (lg) {
12422             fseek(f, lg->offset, 0);
12423             GameListHighlight(gameNumber);
12424             pos = lg->position;
12425             gn = 1;
12426         }
12427         else {
12428             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12429               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12430             else
12431             DisplayError(_("Game number out of range"), 0);
12432             return FALSE;
12433         }
12434     } else {
12435         GameListDestroy();
12436         if (fseek(f, 0, 0) == -1) {
12437             if (f == lastLoadGameFP ?
12438                 gameNumber == lastLoadGameNumber + 1 :
12439                 gameNumber == 1) {
12440                 gn = 1;
12441             } else {
12442                 DisplayError(_("Can't seek on game file"), 0);
12443                 return FALSE;
12444             }
12445         }
12446     }
12447     lastLoadGameFP = f;
12448     lastLoadGameNumber = gameNumber;
12449     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12450     lastLoadGameUseList = useList;
12451
12452     yynewfile(f);
12453
12454     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12455       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12456                 lg->gameInfo.black);
12457             DisplayTitle(buf);
12458     } else if (*title != NULLCHAR) {
12459         if (gameNumber > 1) {
12460           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12461             DisplayTitle(buf);
12462         } else {
12463             DisplayTitle(title);
12464         }
12465     }
12466
12467     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12468         gameMode = PlayFromGameFile;
12469         ModeHighlight();
12470     }
12471
12472     currentMove = forwardMostMove = backwardMostMove = 0;
12473     CopyBoard(boards[0], initialPosition);
12474     StopClocks();
12475
12476     /*
12477      * Skip the first gn-1 games in the file.
12478      * Also skip over anything that precedes an identifiable
12479      * start of game marker, to avoid being confused by
12480      * garbage at the start of the file.  Currently
12481      * recognized start of game markers are the move number "1",
12482      * the pattern "gnuchess .* game", the pattern
12483      * "^[#;%] [^ ]* game file", and a PGN tag block.
12484      * A game that starts with one of the latter two patterns
12485      * will also have a move number 1, possibly
12486      * following a position diagram.
12487      * 5-4-02: Let's try being more lenient and allowing a game to
12488      * start with an unnumbered move.  Does that break anything?
12489      */
12490     cm = lastLoadGameStart = EndOfFile;
12491     while (gn > 0) {
12492         yyboardindex = forwardMostMove;
12493         cm = (ChessMove) Myylex();
12494         switch (cm) {
12495           case EndOfFile:
12496             if (cmailMsgLoaded) {
12497                 nCmailGames = CMAIL_MAX_GAMES - gn;
12498             } else {
12499                 Reset(TRUE, TRUE);
12500                 DisplayError(_("Game not found in file"), 0);
12501             }
12502             return FALSE;
12503
12504           case GNUChessGame:
12505           case XBoardGame:
12506             gn--;
12507             lastLoadGameStart = cm;
12508             break;
12509
12510           case MoveNumberOne:
12511             switch (lastLoadGameStart) {
12512               case GNUChessGame:
12513               case XBoardGame:
12514               case PGNTag:
12515                 break;
12516               case MoveNumberOne:
12517               case EndOfFile:
12518                 gn--;           /* count this game */
12519                 lastLoadGameStart = cm;
12520                 break;
12521               default:
12522                 /* impossible */
12523                 break;
12524             }
12525             break;
12526
12527           case PGNTag:
12528             switch (lastLoadGameStart) {
12529               case GNUChessGame:
12530               case PGNTag:
12531               case MoveNumberOne:
12532               case EndOfFile:
12533                 gn--;           /* count this game */
12534                 lastLoadGameStart = cm;
12535                 break;
12536               case XBoardGame:
12537                 lastLoadGameStart = cm; /* game counted already */
12538                 break;
12539               default:
12540                 /* impossible */
12541                 break;
12542             }
12543             if (gn > 0) {
12544                 do {
12545                     yyboardindex = forwardMostMove;
12546                     cm = (ChessMove) Myylex();
12547                 } while (cm == PGNTag || cm == Comment);
12548             }
12549             break;
12550
12551           case WhiteWins:
12552           case BlackWins:
12553           case GameIsDrawn:
12554             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12555                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12556                     != CMAIL_OLD_RESULT) {
12557                     nCmailResults ++ ;
12558                     cmailResult[  CMAIL_MAX_GAMES
12559                                 - gn - 1] = CMAIL_OLD_RESULT;
12560                 }
12561             }
12562             break;
12563
12564           case NormalMove:
12565           case FirstLeg:
12566             /* Only a NormalMove can be at the start of a game
12567              * without a position diagram. */
12568             if (lastLoadGameStart == EndOfFile ) {
12569               gn--;
12570               lastLoadGameStart = MoveNumberOne;
12571             }
12572             break;
12573
12574           default:
12575             break;
12576         }
12577     }
12578
12579     if (appData.debugMode)
12580       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12581
12582     if (cm == XBoardGame) {
12583         /* Skip any header junk before position diagram and/or move 1 */
12584         for (;;) {
12585             yyboardindex = forwardMostMove;
12586             cm = (ChessMove) Myylex();
12587
12588             if (cm == EndOfFile ||
12589                 cm == GNUChessGame || cm == XBoardGame) {
12590                 /* Empty game; pretend end-of-file and handle later */
12591                 cm = EndOfFile;
12592                 break;
12593             }
12594
12595             if (cm == MoveNumberOne || cm == PositionDiagram ||
12596                 cm == PGNTag || cm == Comment)
12597               break;
12598         }
12599     } else if (cm == GNUChessGame) {
12600         if (gameInfo.event != NULL) {
12601             free(gameInfo.event);
12602         }
12603         gameInfo.event = StrSave(yy_text);
12604     }
12605
12606     startedFromSetupPosition = FALSE;
12607     while (cm == PGNTag) {
12608         if (appData.debugMode)
12609           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12610         err = ParsePGNTag(yy_text, &gameInfo);
12611         if (!err) numPGNTags++;
12612
12613         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12614         if(gameInfo.variant != oldVariant) {
12615             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12616             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12617             InitPosition(TRUE);
12618             oldVariant = gameInfo.variant;
12619             if (appData.debugMode)
12620               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12621         }
12622
12623
12624         if (gameInfo.fen != NULL) {
12625           Board initial_position;
12626           startedFromSetupPosition = TRUE;
12627           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12628             Reset(TRUE, TRUE);
12629             DisplayError(_("Bad FEN position in file"), 0);
12630             return FALSE;
12631           }
12632           CopyBoard(boards[0], initial_position);
12633           if (blackPlaysFirst) {
12634             currentMove = forwardMostMove = backwardMostMove = 1;
12635             CopyBoard(boards[1], initial_position);
12636             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12637             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12638             timeRemaining[0][1] = whiteTimeRemaining;
12639             timeRemaining[1][1] = blackTimeRemaining;
12640             if (commentList[0] != NULL) {
12641               commentList[1] = commentList[0];
12642               commentList[0] = NULL;
12643             }
12644           } else {
12645             currentMove = forwardMostMove = backwardMostMove = 0;
12646           }
12647           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12648           {   int i;
12649               initialRulePlies = FENrulePlies;
12650               for( i=0; i< nrCastlingRights; i++ )
12651                   initialRights[i] = initial_position[CASTLING][i];
12652           }
12653           yyboardindex = forwardMostMove;
12654           free(gameInfo.fen);
12655           gameInfo.fen = NULL;
12656         }
12657
12658         yyboardindex = forwardMostMove;
12659         cm = (ChessMove) Myylex();
12660
12661         /* Handle comments interspersed among the tags */
12662         while (cm == Comment) {
12663             char *p;
12664             if (appData.debugMode)
12665               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12666             p = yy_text;
12667             AppendComment(currentMove, p, FALSE);
12668             yyboardindex = forwardMostMove;
12669             cm = (ChessMove) Myylex();
12670         }
12671     }
12672
12673     /* don't rely on existence of Event tag since if game was
12674      * pasted from clipboard the Event tag may not exist
12675      */
12676     if (numPGNTags > 0){
12677         char *tags;
12678         if (gameInfo.variant == VariantNormal) {
12679           VariantClass v = StringToVariant(gameInfo.event);
12680           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12681           if(v < VariantShogi) gameInfo.variant = v;
12682         }
12683         if (!matchMode) {
12684           if( appData.autoDisplayTags ) {
12685             tags = PGNTags(&gameInfo);
12686             TagsPopUp(tags, CmailMsg());
12687             free(tags);
12688           }
12689         }
12690     } else {
12691         /* Make something up, but don't display it now */
12692         SetGameInfo();
12693         TagsPopDown();
12694     }
12695
12696     if (cm == PositionDiagram) {
12697         int i, j;
12698         char *p;
12699         Board initial_position;
12700
12701         if (appData.debugMode)
12702           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12703
12704         if (!startedFromSetupPosition) {
12705             p = yy_text;
12706             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12707               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12708                 switch (*p) {
12709                   case '{':
12710                   case '[':
12711                   case '-':
12712                   case ' ':
12713                   case '\t':
12714                   case '\n':
12715                   case '\r':
12716                     break;
12717                   default:
12718                     initial_position[i][j++] = CharToPiece(*p);
12719                     break;
12720                 }
12721             while (*p == ' ' || *p == '\t' ||
12722                    *p == '\n' || *p == '\r') p++;
12723
12724             if (strncmp(p, "black", strlen("black"))==0)
12725               blackPlaysFirst = TRUE;
12726             else
12727               blackPlaysFirst = FALSE;
12728             startedFromSetupPosition = TRUE;
12729
12730             CopyBoard(boards[0], initial_position);
12731             if (blackPlaysFirst) {
12732                 currentMove = forwardMostMove = backwardMostMove = 1;
12733                 CopyBoard(boards[1], initial_position);
12734                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12735                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12736                 timeRemaining[0][1] = whiteTimeRemaining;
12737                 timeRemaining[1][1] = blackTimeRemaining;
12738                 if (commentList[0] != NULL) {
12739                     commentList[1] = commentList[0];
12740                     commentList[0] = NULL;
12741                 }
12742             } else {
12743                 currentMove = forwardMostMove = backwardMostMove = 0;
12744             }
12745         }
12746         yyboardindex = forwardMostMove;
12747         cm = (ChessMove) Myylex();
12748     }
12749
12750   if(!creatingBook) {
12751     if (first.pr == NoProc) {
12752         StartChessProgram(&first);
12753     }
12754     InitChessProgram(&first, FALSE);
12755     SendToProgram("force\n", &first);
12756     if (startedFromSetupPosition) {
12757         SendBoard(&first, forwardMostMove);
12758     if (appData.debugMode) {
12759         fprintf(debugFP, "Load Game\n");
12760     }
12761         DisplayBothClocks();
12762     }
12763   }
12764
12765     /* [HGM] server: flag to write setup moves in broadcast file as one */
12766     loadFlag = appData.suppressLoadMoves;
12767
12768     while (cm == Comment) {
12769         char *p;
12770         if (appData.debugMode)
12771           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12772         p = yy_text;
12773         AppendComment(currentMove, p, FALSE);
12774         yyboardindex = forwardMostMove;
12775         cm = (ChessMove) Myylex();
12776     }
12777
12778     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12779         cm == WhiteWins || cm == BlackWins ||
12780         cm == GameIsDrawn || cm == GameUnfinished) {
12781         DisplayMessage("", _("No moves in game"));
12782         if (cmailMsgLoaded) {
12783             if (appData.debugMode)
12784               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12785             ClearHighlights();
12786             flipView = FALSE;
12787         }
12788         DrawPosition(FALSE, boards[currentMove]);
12789         DisplayBothClocks();
12790         gameMode = EditGame;
12791         ModeHighlight();
12792         gameFileFP = NULL;
12793         cmailOldMove = 0;
12794         return TRUE;
12795     }
12796
12797     // [HGM] PV info: routine tests if comment empty
12798     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12799         DisplayComment(currentMove - 1, commentList[currentMove]);
12800     }
12801     if (!matchMode && appData.timeDelay != 0)
12802       DrawPosition(FALSE, boards[currentMove]);
12803
12804     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12805       programStats.ok_to_send = 1;
12806     }
12807
12808     /* if the first token after the PGN tags is a move
12809      * and not move number 1, retrieve it from the parser
12810      */
12811     if (cm != MoveNumberOne)
12812         LoadGameOneMove(cm);
12813
12814     /* load the remaining moves from the file */
12815     while (LoadGameOneMove(EndOfFile)) {
12816       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12817       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12818     }
12819
12820     /* rewind to the start of the game */
12821     currentMove = backwardMostMove;
12822
12823     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12824
12825     if (oldGameMode == AnalyzeFile) {
12826       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12827       AnalyzeFileEvent();
12828     } else
12829     if (oldGameMode == AnalyzeMode) {
12830       AnalyzeFileEvent();
12831     }
12832
12833     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12834         long int w, b; // [HGM] adjourn: restore saved clock times
12835         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12836         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12837             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12838             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12839         }
12840     }
12841
12842     if(creatingBook) return TRUE;
12843     if (!matchMode && pos > 0) {
12844         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12845     } else
12846     if (matchMode || appData.timeDelay == 0) {
12847       ToEndEvent();
12848     } else if (appData.timeDelay > 0) {
12849       AutoPlayGameLoop();
12850     }
12851
12852     if (appData.debugMode)
12853         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12854
12855     loadFlag = 0; /* [HGM] true game starts */
12856     return TRUE;
12857 }
12858
12859 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12860 int
12861 ReloadPosition (int offset)
12862 {
12863     int positionNumber = lastLoadPositionNumber + offset;
12864     if (lastLoadPositionFP == NULL) {
12865         DisplayError(_("No position has been loaded yet"), 0);
12866         return FALSE;
12867     }
12868     if (positionNumber <= 0) {
12869         DisplayError(_("Can't back up any further"), 0);
12870         return FALSE;
12871     }
12872     return LoadPosition(lastLoadPositionFP, positionNumber,
12873                         lastLoadPositionTitle);
12874 }
12875
12876 /* Load the nth position from the given file */
12877 int
12878 LoadPositionFromFile (char *filename, int n, char *title)
12879 {
12880     FILE *f;
12881     char buf[MSG_SIZ];
12882
12883     if (strcmp(filename, "-") == 0) {
12884         return LoadPosition(stdin, n, "stdin");
12885     } else {
12886         f = fopen(filename, "rb");
12887         if (f == NULL) {
12888             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12889             DisplayError(buf, errno);
12890             return FALSE;
12891         } else {
12892             return LoadPosition(f, n, title);
12893         }
12894     }
12895 }
12896
12897 /* Load the nth position from the given open file, and close it */
12898 int
12899 LoadPosition (FILE *f, int positionNumber, char *title)
12900 {
12901     char *p, line[MSG_SIZ];
12902     Board initial_position;
12903     int i, j, fenMode, pn;
12904
12905     if (gameMode == Training )
12906         SetTrainingModeOff();
12907
12908     if (gameMode != BeginningOfGame) {
12909         Reset(FALSE, TRUE);
12910     }
12911     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12912         fclose(lastLoadPositionFP);
12913     }
12914     if (positionNumber == 0) positionNumber = 1;
12915     lastLoadPositionFP = f;
12916     lastLoadPositionNumber = positionNumber;
12917     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12918     if (first.pr == NoProc && !appData.noChessProgram) {
12919       StartChessProgram(&first);
12920       InitChessProgram(&first, FALSE);
12921     }
12922     pn = positionNumber;
12923     if (positionNumber < 0) {
12924         /* Negative position number means to seek to that byte offset */
12925         if (fseek(f, -positionNumber, 0) == -1) {
12926             DisplayError(_("Can't seek on position file"), 0);
12927             return FALSE;
12928         };
12929         pn = 1;
12930     } else {
12931         if (fseek(f, 0, 0) == -1) {
12932             if (f == lastLoadPositionFP ?
12933                 positionNumber == lastLoadPositionNumber + 1 :
12934                 positionNumber == 1) {
12935                 pn = 1;
12936             } else {
12937                 DisplayError(_("Can't seek on position file"), 0);
12938                 return FALSE;
12939             }
12940         }
12941     }
12942     /* See if this file is FEN or old-style xboard */
12943     if (fgets(line, MSG_SIZ, f) == NULL) {
12944         DisplayError(_("Position not found in file"), 0);
12945         return FALSE;
12946     }
12947     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12948     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12949
12950     if (pn >= 2) {
12951         if (fenMode || line[0] == '#') pn--;
12952         while (pn > 0) {
12953             /* skip positions before number pn */
12954             if (fgets(line, MSG_SIZ, f) == NULL) {
12955                 Reset(TRUE, TRUE);
12956                 DisplayError(_("Position not found in file"), 0);
12957                 return FALSE;
12958             }
12959             if (fenMode || line[0] == '#') pn--;
12960         }
12961     }
12962
12963     if (fenMode) {
12964         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12965             DisplayError(_("Bad FEN position in file"), 0);
12966             return FALSE;
12967         }
12968     } else {
12969         (void) fgets(line, MSG_SIZ, f);
12970         (void) fgets(line, MSG_SIZ, f);
12971
12972         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12973             (void) fgets(line, MSG_SIZ, f);
12974             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12975                 if (*p == ' ')
12976                   continue;
12977                 initial_position[i][j++] = CharToPiece(*p);
12978             }
12979         }
12980
12981         blackPlaysFirst = FALSE;
12982         if (!feof(f)) {
12983             (void) fgets(line, MSG_SIZ, f);
12984             if (strncmp(line, "black", strlen("black"))==0)
12985               blackPlaysFirst = TRUE;
12986         }
12987     }
12988     startedFromSetupPosition = TRUE;
12989
12990     CopyBoard(boards[0], initial_position);
12991     if (blackPlaysFirst) {
12992         currentMove = forwardMostMove = backwardMostMove = 1;
12993         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12994         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12995         CopyBoard(boards[1], initial_position);
12996         DisplayMessage("", _("Black to play"));
12997     } else {
12998         currentMove = forwardMostMove = backwardMostMove = 0;
12999         DisplayMessage("", _("White to play"));
13000     }
13001     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13002     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13003         SendToProgram("force\n", &first);
13004         SendBoard(&first, forwardMostMove);
13005     }
13006     if (appData.debugMode) {
13007 int i, j;
13008   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13009   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13010         fprintf(debugFP, "Load Position\n");
13011     }
13012
13013     if (positionNumber > 1) {
13014       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13015         DisplayTitle(line);
13016     } else {
13017         DisplayTitle(title);
13018     }
13019     gameMode = EditGame;
13020     ModeHighlight();
13021     ResetClocks();
13022     timeRemaining[0][1] = whiteTimeRemaining;
13023     timeRemaining[1][1] = blackTimeRemaining;
13024     DrawPosition(FALSE, boards[currentMove]);
13025
13026     return TRUE;
13027 }
13028
13029
13030 void
13031 CopyPlayerNameIntoFileName (char **dest, char *src)
13032 {
13033     while (*src != NULLCHAR && *src != ',') {
13034         if (*src == ' ') {
13035             *(*dest)++ = '_';
13036             src++;
13037         } else {
13038             *(*dest)++ = *src++;
13039         }
13040     }
13041 }
13042
13043 char *
13044 DefaultFileName (char *ext)
13045 {
13046     static char def[MSG_SIZ];
13047     char *p;
13048
13049     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13050         p = def;
13051         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13052         *p++ = '-';
13053         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13054         *p++ = '.';
13055         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13056     } else {
13057         def[0] = NULLCHAR;
13058     }
13059     return def;
13060 }
13061
13062 /* Save the current game to the given file */
13063 int
13064 SaveGameToFile (char *filename, int append)
13065 {
13066     FILE *f;
13067     char buf[MSG_SIZ];
13068     int result, i, t,tot=0;
13069
13070     if (strcmp(filename, "-") == 0) {
13071         return SaveGame(stdout, 0, NULL);
13072     } else {
13073         for(i=0; i<10; i++) { // upto 10 tries
13074              f = fopen(filename, append ? "a" : "w");
13075              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13076              if(f || errno != 13) break;
13077              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13078              tot += t;
13079         }
13080         if (f == NULL) {
13081             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13082             DisplayError(buf, errno);
13083             return FALSE;
13084         } else {
13085             safeStrCpy(buf, lastMsg, MSG_SIZ);
13086             DisplayMessage(_("Waiting for access to save file"), "");
13087             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13088             DisplayMessage(_("Saving game"), "");
13089             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13090             result = SaveGame(f, 0, NULL);
13091             DisplayMessage(buf, "");
13092             return result;
13093         }
13094     }
13095 }
13096
13097 char *
13098 SavePart (char *str)
13099 {
13100     static char buf[MSG_SIZ];
13101     char *p;
13102
13103     p = strchr(str, ' ');
13104     if (p == NULL) return str;
13105     strncpy(buf, str, p - str);
13106     buf[p - str] = NULLCHAR;
13107     return buf;
13108 }
13109
13110 #define PGN_MAX_LINE 75
13111
13112 #define PGN_SIDE_WHITE  0
13113 #define PGN_SIDE_BLACK  1
13114
13115 static int
13116 FindFirstMoveOutOfBook (int side)
13117 {
13118     int result = -1;
13119
13120     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13121         int index = backwardMostMove;
13122         int has_book_hit = 0;
13123
13124         if( (index % 2) != side ) {
13125             index++;
13126         }
13127
13128         while( index < forwardMostMove ) {
13129             /* Check to see if engine is in book */
13130             int depth = pvInfoList[index].depth;
13131             int score = pvInfoList[index].score;
13132             int in_book = 0;
13133
13134             if( depth <= 2 ) {
13135                 in_book = 1;
13136             }
13137             else if( score == 0 && depth == 63 ) {
13138                 in_book = 1; /* Zappa */
13139             }
13140             else if( score == 2 && depth == 99 ) {
13141                 in_book = 1; /* Abrok */
13142             }
13143
13144             has_book_hit += in_book;
13145
13146             if( ! in_book ) {
13147                 result = index;
13148
13149                 break;
13150             }
13151
13152             index += 2;
13153         }
13154     }
13155
13156     return result;
13157 }
13158
13159 void
13160 GetOutOfBookInfo (char * buf)
13161 {
13162     int oob[2];
13163     int i;
13164     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13165
13166     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13167     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13168
13169     *buf = '\0';
13170
13171     if( oob[0] >= 0 || oob[1] >= 0 ) {
13172         for( i=0; i<2; i++ ) {
13173             int idx = oob[i];
13174
13175             if( idx >= 0 ) {
13176                 if( i > 0 && oob[0] >= 0 ) {
13177                     strcat( buf, "   " );
13178                 }
13179
13180                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13181                 sprintf( buf+strlen(buf), "%s%.2f",
13182                     pvInfoList[idx].score >= 0 ? "+" : "",
13183                     pvInfoList[idx].score / 100.0 );
13184             }
13185         }
13186     }
13187 }
13188
13189 /* Save game in PGN style and close the file */
13190 int
13191 SaveGamePGN (FILE *f)
13192 {
13193     int i, offset, linelen, newblock;
13194 //    char *movetext;
13195     char numtext[32];
13196     int movelen, numlen, blank;
13197     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13198
13199     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13200
13201     PrintPGNTags(f, &gameInfo);
13202
13203     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13204
13205     if (backwardMostMove > 0 || startedFromSetupPosition) {
13206         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13207         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13208         fprintf(f, "\n{--------------\n");
13209         PrintPosition(f, backwardMostMove);
13210         fprintf(f, "--------------}\n");
13211         free(fen);
13212     }
13213     else {
13214         /* [AS] Out of book annotation */
13215         if( appData.saveOutOfBookInfo ) {
13216             char buf[64];
13217
13218             GetOutOfBookInfo( buf );
13219
13220             if( buf[0] != '\0' ) {
13221                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13222             }
13223         }
13224
13225         fprintf(f, "\n");
13226     }
13227
13228     i = backwardMostMove;
13229     linelen = 0;
13230     newblock = TRUE;
13231
13232     while (i < forwardMostMove) {
13233         /* Print comments preceding this move */
13234         if (commentList[i] != NULL) {
13235             if (linelen > 0) fprintf(f, "\n");
13236             fprintf(f, "%s", commentList[i]);
13237             linelen = 0;
13238             newblock = TRUE;
13239         }
13240
13241         /* Format move number */
13242         if ((i % 2) == 0)
13243           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13244         else
13245           if (newblock)
13246             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13247           else
13248             numtext[0] = NULLCHAR;
13249
13250         numlen = strlen(numtext);
13251         newblock = FALSE;
13252
13253         /* Print move number */
13254         blank = linelen > 0 && numlen > 0;
13255         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13256             fprintf(f, "\n");
13257             linelen = 0;
13258             blank = 0;
13259         }
13260         if (blank) {
13261             fprintf(f, " ");
13262             linelen++;
13263         }
13264         fprintf(f, "%s", numtext);
13265         linelen += numlen;
13266
13267         /* Get move */
13268         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13269         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13270
13271         /* Print move */
13272         blank = linelen > 0 && movelen > 0;
13273         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13274             fprintf(f, "\n");
13275             linelen = 0;
13276             blank = 0;
13277         }
13278         if (blank) {
13279             fprintf(f, " ");
13280             linelen++;
13281         }
13282         fprintf(f, "%s", move_buffer);
13283         linelen += movelen;
13284
13285         /* [AS] Add PV info if present */
13286         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13287             /* [HGM] add time */
13288             char buf[MSG_SIZ]; int seconds;
13289
13290             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13291
13292             if( seconds <= 0)
13293               buf[0] = 0;
13294             else
13295               if( seconds < 30 )
13296                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13297               else
13298                 {
13299                   seconds = (seconds + 4)/10; // round to full seconds
13300                   if( seconds < 60 )
13301                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13302                   else
13303                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13304                 }
13305
13306             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13307                       pvInfoList[i].score >= 0 ? "+" : "",
13308                       pvInfoList[i].score / 100.0,
13309                       pvInfoList[i].depth,
13310                       buf );
13311
13312             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13313
13314             /* Print score/depth */
13315             blank = linelen > 0 && movelen > 0;
13316             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13317                 fprintf(f, "\n");
13318                 linelen = 0;
13319                 blank = 0;
13320             }
13321             if (blank) {
13322                 fprintf(f, " ");
13323                 linelen++;
13324             }
13325             fprintf(f, "%s", move_buffer);
13326             linelen += movelen;
13327         }
13328
13329         i++;
13330     }
13331
13332     /* Start a new line */
13333     if (linelen > 0) fprintf(f, "\n");
13334
13335     /* Print comments after last move */
13336     if (commentList[i] != NULL) {
13337         fprintf(f, "%s\n", commentList[i]);
13338     }
13339
13340     /* Print result */
13341     if (gameInfo.resultDetails != NULL &&
13342         gameInfo.resultDetails[0] != NULLCHAR) {
13343         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13344         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13345            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13346             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13347         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13348     } else {
13349         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13350     }
13351
13352     fclose(f);
13353     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13354     return TRUE;
13355 }
13356
13357 /* Save game in old style and close the file */
13358 int
13359 SaveGameOldStyle (FILE *f)
13360 {
13361     int i, offset;
13362     time_t tm;
13363
13364     tm = time((time_t *) NULL);
13365
13366     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13367     PrintOpponents(f);
13368
13369     if (backwardMostMove > 0 || startedFromSetupPosition) {
13370         fprintf(f, "\n[--------------\n");
13371         PrintPosition(f, backwardMostMove);
13372         fprintf(f, "--------------]\n");
13373     } else {
13374         fprintf(f, "\n");
13375     }
13376
13377     i = backwardMostMove;
13378     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13379
13380     while (i < forwardMostMove) {
13381         if (commentList[i] != NULL) {
13382             fprintf(f, "[%s]\n", commentList[i]);
13383         }
13384
13385         if ((i % 2) == 1) {
13386             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13387             i++;
13388         } else {
13389             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13390             i++;
13391             if (commentList[i] != NULL) {
13392                 fprintf(f, "\n");
13393                 continue;
13394             }
13395             if (i >= forwardMostMove) {
13396                 fprintf(f, "\n");
13397                 break;
13398             }
13399             fprintf(f, "%s\n", parseList[i]);
13400             i++;
13401         }
13402     }
13403
13404     if (commentList[i] != NULL) {
13405         fprintf(f, "[%s]\n", commentList[i]);
13406     }
13407
13408     /* This isn't really the old style, but it's close enough */
13409     if (gameInfo.resultDetails != NULL &&
13410         gameInfo.resultDetails[0] != NULLCHAR) {
13411         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13412                 gameInfo.resultDetails);
13413     } else {
13414         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13415     }
13416
13417     fclose(f);
13418     return TRUE;
13419 }
13420
13421 /* Save the current game to open file f and close the file */
13422 int
13423 SaveGame (FILE *f, int dummy, char *dummy2)
13424 {
13425     if (gameMode == EditPosition) EditPositionDone(TRUE);
13426     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13427     if (appData.oldSaveStyle)
13428       return SaveGameOldStyle(f);
13429     else
13430       return SaveGamePGN(f);
13431 }
13432
13433 /* Save the current position to the given file */
13434 int
13435 SavePositionToFile (char *filename)
13436 {
13437     FILE *f;
13438     char buf[MSG_SIZ];
13439
13440     if (strcmp(filename, "-") == 0) {
13441         return SavePosition(stdout, 0, NULL);
13442     } else {
13443         f = fopen(filename, "a");
13444         if (f == NULL) {
13445             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13446             DisplayError(buf, errno);
13447             return FALSE;
13448         } else {
13449             safeStrCpy(buf, lastMsg, MSG_SIZ);
13450             DisplayMessage(_("Waiting for access to save file"), "");
13451             flock(fileno(f), LOCK_EX); // [HGM] lock
13452             DisplayMessage(_("Saving position"), "");
13453             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13454             SavePosition(f, 0, NULL);
13455             DisplayMessage(buf, "");
13456             return TRUE;
13457         }
13458     }
13459 }
13460
13461 /* Save the current position to the given open file and close the file */
13462 int
13463 SavePosition (FILE *f, int dummy, char *dummy2)
13464 {
13465     time_t tm;
13466     char *fen;
13467
13468     if (gameMode == EditPosition) EditPositionDone(TRUE);
13469     if (appData.oldSaveStyle) {
13470         tm = time((time_t *) NULL);
13471
13472         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13473         PrintOpponents(f);
13474         fprintf(f, "[--------------\n");
13475         PrintPosition(f, currentMove);
13476         fprintf(f, "--------------]\n");
13477     } else {
13478         fen = PositionToFEN(currentMove, NULL, 1);
13479         fprintf(f, "%s\n", fen);
13480         free(fen);
13481     }
13482     fclose(f);
13483     return TRUE;
13484 }
13485
13486 void
13487 ReloadCmailMsgEvent (int unregister)
13488 {
13489 #if !WIN32
13490     static char *inFilename = NULL;
13491     static char *outFilename;
13492     int i;
13493     struct stat inbuf, outbuf;
13494     int status;
13495
13496     /* Any registered moves are unregistered if unregister is set, */
13497     /* i.e. invoked by the signal handler */
13498     if (unregister) {
13499         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13500             cmailMoveRegistered[i] = FALSE;
13501             if (cmailCommentList[i] != NULL) {
13502                 free(cmailCommentList[i]);
13503                 cmailCommentList[i] = NULL;
13504             }
13505         }
13506         nCmailMovesRegistered = 0;
13507     }
13508
13509     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13510         cmailResult[i] = CMAIL_NOT_RESULT;
13511     }
13512     nCmailResults = 0;
13513
13514     if (inFilename == NULL) {
13515         /* Because the filenames are static they only get malloced once  */
13516         /* and they never get freed                                      */
13517         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13518         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13519
13520         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13521         sprintf(outFilename, "%s.out", appData.cmailGameName);
13522     }
13523
13524     status = stat(outFilename, &outbuf);
13525     if (status < 0) {
13526         cmailMailedMove = FALSE;
13527     } else {
13528         status = stat(inFilename, &inbuf);
13529         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13530     }
13531
13532     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13533        counts the games, notes how each one terminated, etc.
13534
13535        It would be nice to remove this kludge and instead gather all
13536        the information while building the game list.  (And to keep it
13537        in the game list nodes instead of having a bunch of fixed-size
13538        parallel arrays.)  Note this will require getting each game's
13539        termination from the PGN tags, as the game list builder does
13540        not process the game moves.  --mann
13541        */
13542     cmailMsgLoaded = TRUE;
13543     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13544
13545     /* Load first game in the file or popup game menu */
13546     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13547
13548 #endif /* !WIN32 */
13549     return;
13550 }
13551
13552 int
13553 RegisterMove ()
13554 {
13555     FILE *f;
13556     char string[MSG_SIZ];
13557
13558     if (   cmailMailedMove
13559         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13560         return TRUE;            /* Allow free viewing  */
13561     }
13562
13563     /* Unregister move to ensure that we don't leave RegisterMove        */
13564     /* with the move registered when the conditions for registering no   */
13565     /* longer hold                                                       */
13566     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13567         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13568         nCmailMovesRegistered --;
13569
13570         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13571           {
13572               free(cmailCommentList[lastLoadGameNumber - 1]);
13573               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13574           }
13575     }
13576
13577     if (cmailOldMove == -1) {
13578         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13579         return FALSE;
13580     }
13581
13582     if (currentMove > cmailOldMove + 1) {
13583         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13584         return FALSE;
13585     }
13586
13587     if (currentMove < cmailOldMove) {
13588         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13589         return FALSE;
13590     }
13591
13592     if (forwardMostMove > currentMove) {
13593         /* Silently truncate extra moves */
13594         TruncateGame();
13595     }
13596
13597     if (   (currentMove == cmailOldMove + 1)
13598         || (   (currentMove == cmailOldMove)
13599             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13600                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13601         if (gameInfo.result != GameUnfinished) {
13602             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13603         }
13604
13605         if (commentList[currentMove] != NULL) {
13606             cmailCommentList[lastLoadGameNumber - 1]
13607               = StrSave(commentList[currentMove]);
13608         }
13609         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13610
13611         if (appData.debugMode)
13612           fprintf(debugFP, "Saving %s for game %d\n",
13613                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13614
13615         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13616
13617         f = fopen(string, "w");
13618         if (appData.oldSaveStyle) {
13619             SaveGameOldStyle(f); /* also closes the file */
13620
13621             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13622             f = fopen(string, "w");
13623             SavePosition(f, 0, NULL); /* also closes the file */
13624         } else {
13625             fprintf(f, "{--------------\n");
13626             PrintPosition(f, currentMove);
13627             fprintf(f, "--------------}\n\n");
13628
13629             SaveGame(f, 0, NULL); /* also closes the file*/
13630         }
13631
13632         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13633         nCmailMovesRegistered ++;
13634     } else if (nCmailGames == 1) {
13635         DisplayError(_("You have not made a move yet"), 0);
13636         return FALSE;
13637     }
13638
13639     return TRUE;
13640 }
13641
13642 void
13643 MailMoveEvent ()
13644 {
13645 #if !WIN32
13646     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13647     FILE *commandOutput;
13648     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13649     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13650     int nBuffers;
13651     int i;
13652     int archived;
13653     char *arcDir;
13654
13655     if (! cmailMsgLoaded) {
13656         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13657         return;
13658     }
13659
13660     if (nCmailGames == nCmailResults) {
13661         DisplayError(_("No unfinished games"), 0);
13662         return;
13663     }
13664
13665 #if CMAIL_PROHIBIT_REMAIL
13666     if (cmailMailedMove) {
13667       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);
13668         DisplayError(msg, 0);
13669         return;
13670     }
13671 #endif
13672
13673     if (! (cmailMailedMove || RegisterMove())) return;
13674
13675     if (   cmailMailedMove
13676         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13677       snprintf(string, MSG_SIZ, partCommandString,
13678                appData.debugMode ? " -v" : "", appData.cmailGameName);
13679         commandOutput = popen(string, "r");
13680
13681         if (commandOutput == NULL) {
13682             DisplayError(_("Failed to invoke cmail"), 0);
13683         } else {
13684             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13685                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13686             }
13687             if (nBuffers > 1) {
13688                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13689                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13690                 nBytes = MSG_SIZ - 1;
13691             } else {
13692                 (void) memcpy(msg, buffer, nBytes);
13693             }
13694             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13695
13696             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13697                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13698
13699                 archived = TRUE;
13700                 for (i = 0; i < nCmailGames; i ++) {
13701                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13702                         archived = FALSE;
13703                     }
13704                 }
13705                 if (   archived
13706                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13707                         != NULL)) {
13708                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13709                            arcDir,
13710                            appData.cmailGameName,
13711                            gameInfo.date);
13712                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13713                     cmailMsgLoaded = FALSE;
13714                 }
13715             }
13716
13717             DisplayInformation(msg);
13718             pclose(commandOutput);
13719         }
13720     } else {
13721         if ((*cmailMsg) != '\0') {
13722             DisplayInformation(cmailMsg);
13723         }
13724     }
13725
13726     return;
13727 #endif /* !WIN32 */
13728 }
13729
13730 char *
13731 CmailMsg ()
13732 {
13733 #if WIN32
13734     return NULL;
13735 #else
13736     int  prependComma = 0;
13737     char number[5];
13738     char string[MSG_SIZ];       /* Space for game-list */
13739     int  i;
13740
13741     if (!cmailMsgLoaded) return "";
13742
13743     if (cmailMailedMove) {
13744       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13745     } else {
13746         /* Create a list of games left */
13747       snprintf(string, MSG_SIZ, "[");
13748         for (i = 0; i < nCmailGames; i ++) {
13749             if (! (   cmailMoveRegistered[i]
13750                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13751                 if (prependComma) {
13752                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13753                 } else {
13754                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13755                     prependComma = 1;
13756                 }
13757
13758                 strcat(string, number);
13759             }
13760         }
13761         strcat(string, "]");
13762
13763         if (nCmailMovesRegistered + nCmailResults == 0) {
13764             switch (nCmailGames) {
13765               case 1:
13766                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13767                 break;
13768
13769               case 2:
13770                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13771                 break;
13772
13773               default:
13774                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13775                          nCmailGames);
13776                 break;
13777             }
13778         } else {
13779             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13780               case 1:
13781                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13782                          string);
13783                 break;
13784
13785               case 0:
13786                 if (nCmailResults == nCmailGames) {
13787                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13788                 } else {
13789                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13790                 }
13791                 break;
13792
13793               default:
13794                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13795                          string);
13796             }
13797         }
13798     }
13799     return cmailMsg;
13800 #endif /* WIN32 */
13801 }
13802
13803 void
13804 ResetGameEvent ()
13805 {
13806     if (gameMode == Training)
13807       SetTrainingModeOff();
13808
13809     Reset(TRUE, TRUE);
13810     cmailMsgLoaded = FALSE;
13811     if (appData.icsActive) {
13812       SendToICS(ics_prefix);
13813       SendToICS("refresh\n");
13814     }
13815 }
13816
13817 void
13818 ExitEvent (int status)
13819 {
13820     exiting++;
13821     if (exiting > 2) {
13822       /* Give up on clean exit */
13823       exit(status);
13824     }
13825     if (exiting > 1) {
13826       /* Keep trying for clean exit */
13827       return;
13828     }
13829
13830     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13831
13832     if (telnetISR != NULL) {
13833       RemoveInputSource(telnetISR);
13834     }
13835     if (icsPR != NoProc) {
13836       DestroyChildProcess(icsPR, TRUE);
13837     }
13838
13839     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13840     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13841
13842     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13843     /* make sure this other one finishes before killing it!                  */
13844     if(endingGame) { int count = 0;
13845         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13846         while(endingGame && count++ < 10) DoSleep(1);
13847         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13848     }
13849
13850     /* Kill off chess programs */
13851     if (first.pr != NoProc) {
13852         ExitAnalyzeMode();
13853
13854         DoSleep( appData.delayBeforeQuit );
13855         SendToProgram("quit\n", &first);
13856         DoSleep( appData.delayAfterQuit );
13857         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13858     }
13859     if (second.pr != NoProc) {
13860         DoSleep( appData.delayBeforeQuit );
13861         SendToProgram("quit\n", &second);
13862         DoSleep( appData.delayAfterQuit );
13863         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13864     }
13865     if (first.isr != NULL) {
13866         RemoveInputSource(first.isr);
13867     }
13868     if (second.isr != NULL) {
13869         RemoveInputSource(second.isr);
13870     }
13871
13872     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13873     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13874
13875     ShutDownFrontEnd();
13876     exit(status);
13877 }
13878
13879 void
13880 PauseEngine (ChessProgramState *cps)
13881 {
13882     SendToProgram("pause\n", cps);
13883     cps->pause = 2;
13884 }
13885
13886 void
13887 UnPauseEngine (ChessProgramState *cps)
13888 {
13889     SendToProgram("resume\n", cps);
13890     cps->pause = 1;
13891 }
13892
13893 void
13894 PauseEvent ()
13895 {
13896     if (appData.debugMode)
13897         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13898     if (pausing) {
13899         pausing = FALSE;
13900         ModeHighlight();
13901         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13902             StartClocks();
13903             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13904                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13905                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13906             }
13907             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13908             HandleMachineMove(stashedInputMove, stalledEngine);
13909             stalledEngine = NULL;
13910             return;
13911         }
13912         if (gameMode == MachinePlaysWhite ||
13913             gameMode == TwoMachinesPlay   ||
13914             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13915             if(first.pause)  UnPauseEngine(&first);
13916             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13917             if(second.pause) UnPauseEngine(&second);
13918             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13919             StartClocks();
13920         } else {
13921             DisplayBothClocks();
13922         }
13923         if (gameMode == PlayFromGameFile) {
13924             if (appData.timeDelay >= 0)
13925                 AutoPlayGameLoop();
13926         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13927             Reset(FALSE, TRUE);
13928             SendToICS(ics_prefix);
13929             SendToICS("refresh\n");
13930         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13931             ForwardInner(forwardMostMove);
13932         }
13933         pauseExamInvalid = FALSE;
13934     } else {
13935         switch (gameMode) {
13936           default:
13937             return;
13938           case IcsExamining:
13939             pauseExamForwardMostMove = forwardMostMove;
13940             pauseExamInvalid = FALSE;
13941             /* fall through */
13942           case IcsObserving:
13943           case IcsPlayingWhite:
13944           case IcsPlayingBlack:
13945             pausing = TRUE;
13946             ModeHighlight();
13947             return;
13948           case PlayFromGameFile:
13949             (void) StopLoadGameTimer();
13950             pausing = TRUE;
13951             ModeHighlight();
13952             break;
13953           case BeginningOfGame:
13954             if (appData.icsActive) return;
13955             /* else fall through */
13956           case MachinePlaysWhite:
13957           case MachinePlaysBlack:
13958           case TwoMachinesPlay:
13959             if (forwardMostMove == 0)
13960               return;           /* don't pause if no one has moved */
13961             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13962                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13963                 if(onMove->pause) {           // thinking engine can be paused
13964                     PauseEngine(onMove);      // do it
13965                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13966                         PauseEngine(onMove->other);
13967                     else
13968                         SendToProgram("easy\n", onMove->other);
13969                     StopClocks();
13970                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13971             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13972                 if(first.pause) {
13973                     PauseEngine(&first);
13974                     StopClocks();
13975                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13976             } else { // human on move, pause pondering by either method
13977                 if(first.pause)
13978                     PauseEngine(&first);
13979                 else if(appData.ponderNextMove)
13980                     SendToProgram("easy\n", &first);
13981                 StopClocks();
13982             }
13983             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13984           case AnalyzeMode:
13985             pausing = TRUE;
13986             ModeHighlight();
13987             break;
13988         }
13989     }
13990 }
13991
13992 void
13993 EditCommentEvent ()
13994 {
13995     char title[MSG_SIZ];
13996
13997     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13998       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13999     } else {
14000       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14001                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14002                parseList[currentMove - 1]);
14003     }
14004
14005     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14006 }
14007
14008
14009 void
14010 EditTagsEvent ()
14011 {
14012     char *tags = PGNTags(&gameInfo);
14013     bookUp = FALSE;
14014     EditTagsPopUp(tags, NULL);
14015     free(tags);
14016 }
14017
14018 void
14019 ToggleSecond ()
14020 {
14021   if(second.analyzing) {
14022     SendToProgram("exit\n", &second);
14023     second.analyzing = FALSE;
14024   } else {
14025     if (second.pr == NoProc) StartChessProgram(&second);
14026     InitChessProgram(&second, FALSE);
14027     FeedMovesToProgram(&second, currentMove);
14028
14029     SendToProgram("analyze\n", &second);
14030     second.analyzing = TRUE;
14031   }
14032 }
14033
14034 /* Toggle ShowThinking */
14035 void
14036 ToggleShowThinking()
14037 {
14038   appData.showThinking = !appData.showThinking;
14039   ShowThinkingEvent();
14040 }
14041
14042 int
14043 AnalyzeModeEvent ()
14044 {
14045     char buf[MSG_SIZ];
14046
14047     if (!first.analysisSupport) {
14048       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14049       DisplayError(buf, 0);
14050       return 0;
14051     }
14052     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14053     if (appData.icsActive) {
14054         if (gameMode != IcsObserving) {
14055           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14056             DisplayError(buf, 0);
14057             /* secure check */
14058             if (appData.icsEngineAnalyze) {
14059                 if (appData.debugMode)
14060                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14061                 ExitAnalyzeMode();
14062                 ModeHighlight();
14063             }
14064             return 0;
14065         }
14066         /* if enable, user wants to disable icsEngineAnalyze */
14067         if (appData.icsEngineAnalyze) {
14068                 ExitAnalyzeMode();
14069                 ModeHighlight();
14070                 return 0;
14071         }
14072         appData.icsEngineAnalyze = TRUE;
14073         if (appData.debugMode)
14074             fprintf(debugFP, "ICS engine analyze starting... \n");
14075     }
14076
14077     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14078     if (appData.noChessProgram || gameMode == AnalyzeMode)
14079       return 0;
14080
14081     if (gameMode != AnalyzeFile) {
14082         if (!appData.icsEngineAnalyze) {
14083                EditGameEvent();
14084                if (gameMode != EditGame) return 0;
14085         }
14086         if (!appData.showThinking) ToggleShowThinking();
14087         ResurrectChessProgram();
14088         SendToProgram("analyze\n", &first);
14089         first.analyzing = TRUE;
14090         /*first.maybeThinking = TRUE;*/
14091         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14092         EngineOutputPopUp();
14093     }
14094     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14095     pausing = FALSE;
14096     ModeHighlight();
14097     SetGameInfo();
14098
14099     StartAnalysisClock();
14100     GetTimeMark(&lastNodeCountTime);
14101     lastNodeCount = 0;
14102     return 1;
14103 }
14104
14105 void
14106 AnalyzeFileEvent ()
14107 {
14108     if (appData.noChessProgram || gameMode == AnalyzeFile)
14109       return;
14110
14111     if (!first.analysisSupport) {
14112       char buf[MSG_SIZ];
14113       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14114       DisplayError(buf, 0);
14115       return;
14116     }
14117
14118     if (gameMode != AnalyzeMode) {
14119         keepInfo = 1; // mere annotating should not alter PGN tags
14120         EditGameEvent();
14121         keepInfo = 0;
14122         if (gameMode != EditGame) return;
14123         if (!appData.showThinking) ToggleShowThinking();
14124         ResurrectChessProgram();
14125         SendToProgram("analyze\n", &first);
14126         first.analyzing = TRUE;
14127         /*first.maybeThinking = TRUE;*/
14128         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14129         EngineOutputPopUp();
14130     }
14131     gameMode = AnalyzeFile;
14132     pausing = FALSE;
14133     ModeHighlight();
14134
14135     StartAnalysisClock();
14136     GetTimeMark(&lastNodeCountTime);
14137     lastNodeCount = 0;
14138     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14139     AnalysisPeriodicEvent(1);
14140 }
14141
14142 void
14143 MachineWhiteEvent ()
14144 {
14145     char buf[MSG_SIZ];
14146     char *bookHit = NULL;
14147
14148     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14149       return;
14150
14151
14152     if (gameMode == PlayFromGameFile ||
14153         gameMode == TwoMachinesPlay  ||
14154         gameMode == Training         ||
14155         gameMode == AnalyzeMode      ||
14156         gameMode == EndOfGame)
14157         EditGameEvent();
14158
14159     if (gameMode == EditPosition)
14160         EditPositionDone(TRUE);
14161
14162     if (!WhiteOnMove(currentMove)) {
14163         DisplayError(_("It is not White's turn"), 0);
14164         return;
14165     }
14166
14167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14168       ExitAnalyzeMode();
14169
14170     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14171         gameMode == AnalyzeFile)
14172         TruncateGame();
14173
14174     ResurrectChessProgram();    /* in case it isn't running */
14175     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14176         gameMode = MachinePlaysWhite;
14177         ResetClocks();
14178     } else
14179     gameMode = MachinePlaysWhite;
14180     pausing = FALSE;
14181     ModeHighlight();
14182     SetGameInfo();
14183     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14184     DisplayTitle(buf);
14185     if (first.sendName) {
14186       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14187       SendToProgram(buf, &first);
14188     }
14189     if (first.sendTime) {
14190       if (first.useColors) {
14191         SendToProgram("black\n", &first); /*gnu kludge*/
14192       }
14193       SendTimeRemaining(&first, TRUE);
14194     }
14195     if (first.useColors) {
14196       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14197     }
14198     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14199     SetMachineThinkingEnables();
14200     first.maybeThinking = TRUE;
14201     StartClocks();
14202     firstMove = FALSE;
14203
14204     if (appData.autoFlipView && !flipView) {
14205       flipView = !flipView;
14206       DrawPosition(FALSE, NULL);
14207       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14208     }
14209
14210     if(bookHit) { // [HGM] book: simulate book reply
14211         static char bookMove[MSG_SIZ]; // a bit generous?
14212
14213         programStats.nodes = programStats.depth = programStats.time =
14214         programStats.score = programStats.got_only_move = 0;
14215         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14216
14217         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14218         strcat(bookMove, bookHit);
14219         HandleMachineMove(bookMove, &first);
14220     }
14221 }
14222
14223 void
14224 MachineBlackEvent ()
14225 {
14226   char buf[MSG_SIZ];
14227   char *bookHit = NULL;
14228
14229     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14230         return;
14231
14232
14233     if (gameMode == PlayFromGameFile ||
14234         gameMode == TwoMachinesPlay  ||
14235         gameMode == Training         ||
14236         gameMode == AnalyzeMode      ||
14237         gameMode == EndOfGame)
14238         EditGameEvent();
14239
14240     if (gameMode == EditPosition)
14241         EditPositionDone(TRUE);
14242
14243     if (WhiteOnMove(currentMove)) {
14244         DisplayError(_("It is not Black's turn"), 0);
14245         return;
14246     }
14247
14248     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14249       ExitAnalyzeMode();
14250
14251     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14252         gameMode == AnalyzeFile)
14253         TruncateGame();
14254
14255     ResurrectChessProgram();    /* in case it isn't running */
14256     gameMode = MachinePlaysBlack;
14257     pausing = FALSE;
14258     ModeHighlight();
14259     SetGameInfo();
14260     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14261     DisplayTitle(buf);
14262     if (first.sendName) {
14263       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14264       SendToProgram(buf, &first);
14265     }
14266     if (first.sendTime) {
14267       if (first.useColors) {
14268         SendToProgram("white\n", &first); /*gnu kludge*/
14269       }
14270       SendTimeRemaining(&first, FALSE);
14271     }
14272     if (first.useColors) {
14273       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14274     }
14275     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14276     SetMachineThinkingEnables();
14277     first.maybeThinking = TRUE;
14278     StartClocks();
14279
14280     if (appData.autoFlipView && flipView) {
14281       flipView = !flipView;
14282       DrawPosition(FALSE, NULL);
14283       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14284     }
14285     if(bookHit) { // [HGM] book: simulate book reply
14286         static char bookMove[MSG_SIZ]; // a bit generous?
14287
14288         programStats.nodes = programStats.depth = programStats.time =
14289         programStats.score = programStats.got_only_move = 0;
14290         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14291
14292         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14293         strcat(bookMove, bookHit);
14294         HandleMachineMove(bookMove, &first);
14295     }
14296 }
14297
14298
14299 void
14300 DisplayTwoMachinesTitle ()
14301 {
14302     char buf[MSG_SIZ];
14303     if (appData.matchGames > 0) {
14304         if(appData.tourneyFile[0]) {
14305           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14306                    gameInfo.white, _("vs."), gameInfo.black,
14307                    nextGame+1, appData.matchGames+1,
14308                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14309         } else
14310         if (first.twoMachinesColor[0] == 'w') {
14311           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14312                    gameInfo.white, _("vs."),  gameInfo.black,
14313                    first.matchWins, second.matchWins,
14314                    matchGame - 1 - (first.matchWins + second.matchWins));
14315         } else {
14316           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14317                    gameInfo.white, _("vs."), gameInfo.black,
14318                    second.matchWins, first.matchWins,
14319                    matchGame - 1 - (first.matchWins + second.matchWins));
14320         }
14321     } else {
14322       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14323     }
14324     DisplayTitle(buf);
14325 }
14326
14327 void
14328 SettingsMenuIfReady ()
14329 {
14330   if (second.lastPing != second.lastPong) {
14331     DisplayMessage("", _("Waiting for second chess program"));
14332     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14333     return;
14334   }
14335   ThawUI();
14336   DisplayMessage("", "");
14337   SettingsPopUp(&second);
14338 }
14339
14340 int
14341 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14342 {
14343     char buf[MSG_SIZ];
14344     if (cps->pr == NoProc) {
14345         StartChessProgram(cps);
14346         if (cps->protocolVersion == 1) {
14347           retry();
14348           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14349         } else {
14350           /* kludge: allow timeout for initial "feature" command */
14351           if(retry != TwoMachinesEventIfReady) FreezeUI();
14352           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14353           DisplayMessage("", buf);
14354           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14355         }
14356         return 1;
14357     }
14358     return 0;
14359 }
14360
14361 void
14362 TwoMachinesEvent P((void))
14363 {
14364     int i;
14365     char buf[MSG_SIZ];
14366     ChessProgramState *onmove;
14367     char *bookHit = NULL;
14368     static int stalling = 0;
14369     TimeMark now;
14370     long wait;
14371
14372     if (appData.noChessProgram) return;
14373
14374     switch (gameMode) {
14375       case TwoMachinesPlay:
14376         return;
14377       case MachinePlaysWhite:
14378       case MachinePlaysBlack:
14379         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14380             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14381             return;
14382         }
14383         /* fall through */
14384       case BeginningOfGame:
14385       case PlayFromGameFile:
14386       case EndOfGame:
14387         EditGameEvent();
14388         if (gameMode != EditGame) return;
14389         break;
14390       case EditPosition:
14391         EditPositionDone(TRUE);
14392         break;
14393       case AnalyzeMode:
14394       case AnalyzeFile:
14395         ExitAnalyzeMode();
14396         break;
14397       case EditGame:
14398       default:
14399         break;
14400     }
14401
14402 //    forwardMostMove = currentMove;
14403     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14404     startingEngine = TRUE;
14405
14406     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14407
14408     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14409     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14410       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14411       return;
14412     }
14413     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14414
14415     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14416                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14417         startingEngine = FALSE;
14418         DisplayError("second engine does not play this", 0);
14419         return;
14420     }
14421
14422     if(!stalling) {
14423       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14424       SendToProgram("force\n", &second);
14425       stalling = 1;
14426       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14427       return;
14428     }
14429     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14430     if(appData.matchPause>10000 || appData.matchPause<10)
14431                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14432     wait = SubtractTimeMarks(&now, &pauseStart);
14433     if(wait < appData.matchPause) {
14434         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14435         return;
14436     }
14437     // we are now committed to starting the game
14438     stalling = 0;
14439     DisplayMessage("", "");
14440     if (startedFromSetupPosition) {
14441         SendBoard(&second, backwardMostMove);
14442     if (appData.debugMode) {
14443         fprintf(debugFP, "Two Machines\n");
14444     }
14445     }
14446     for (i = backwardMostMove; i < forwardMostMove; i++) {
14447         SendMoveToProgram(i, &second);
14448     }
14449
14450     gameMode = TwoMachinesPlay;
14451     pausing = startingEngine = FALSE;
14452     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14453     SetGameInfo();
14454     DisplayTwoMachinesTitle();
14455     firstMove = TRUE;
14456     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14457         onmove = &first;
14458     } else {
14459         onmove = &second;
14460     }
14461     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14462     SendToProgram(first.computerString, &first);
14463     if (first.sendName) {
14464       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14465       SendToProgram(buf, &first);
14466     }
14467     SendToProgram(second.computerString, &second);
14468     if (second.sendName) {
14469       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14470       SendToProgram(buf, &second);
14471     }
14472
14473     ResetClocks();
14474     if (!first.sendTime || !second.sendTime) {
14475         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14476         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14477     }
14478     if (onmove->sendTime) {
14479       if (onmove->useColors) {
14480         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14481       }
14482       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14483     }
14484     if (onmove->useColors) {
14485       SendToProgram(onmove->twoMachinesColor, onmove);
14486     }
14487     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14488 //    SendToProgram("go\n", onmove);
14489     onmove->maybeThinking = TRUE;
14490     SetMachineThinkingEnables();
14491
14492     StartClocks();
14493
14494     if(bookHit) { // [HGM] book: simulate book reply
14495         static char bookMove[MSG_SIZ]; // a bit generous?
14496
14497         programStats.nodes = programStats.depth = programStats.time =
14498         programStats.score = programStats.got_only_move = 0;
14499         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14500
14501         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14502         strcat(bookMove, bookHit);
14503         savedMessage = bookMove; // args for deferred call
14504         savedState = onmove;
14505         ScheduleDelayedEvent(DeferredBookMove, 1);
14506     }
14507 }
14508
14509 void
14510 TrainingEvent ()
14511 {
14512     if (gameMode == Training) {
14513       SetTrainingModeOff();
14514       gameMode = PlayFromGameFile;
14515       DisplayMessage("", _("Training mode off"));
14516     } else {
14517       gameMode = Training;
14518       animateTraining = appData.animate;
14519
14520       /* make sure we are not already at the end of the game */
14521       if (currentMove < forwardMostMove) {
14522         SetTrainingModeOn();
14523         DisplayMessage("", _("Training mode on"));
14524       } else {
14525         gameMode = PlayFromGameFile;
14526         DisplayError(_("Already at end of game"), 0);
14527       }
14528     }
14529     ModeHighlight();
14530 }
14531
14532 void
14533 IcsClientEvent ()
14534 {
14535     if (!appData.icsActive) return;
14536     switch (gameMode) {
14537       case IcsPlayingWhite:
14538       case IcsPlayingBlack:
14539       case IcsObserving:
14540       case IcsIdle:
14541       case BeginningOfGame:
14542       case IcsExamining:
14543         return;
14544
14545       case EditGame:
14546         break;
14547
14548       case EditPosition:
14549         EditPositionDone(TRUE);
14550         break;
14551
14552       case AnalyzeMode:
14553       case AnalyzeFile:
14554         ExitAnalyzeMode();
14555         break;
14556
14557       default:
14558         EditGameEvent();
14559         break;
14560     }
14561
14562     gameMode = IcsIdle;
14563     ModeHighlight();
14564     return;
14565 }
14566
14567 void
14568 EditGameEvent ()
14569 {
14570     int i;
14571
14572     switch (gameMode) {
14573       case Training:
14574         SetTrainingModeOff();
14575         break;
14576       case MachinePlaysWhite:
14577       case MachinePlaysBlack:
14578       case BeginningOfGame:
14579         SendToProgram("force\n", &first);
14580         SetUserThinkingEnables();
14581         break;
14582       case PlayFromGameFile:
14583         (void) StopLoadGameTimer();
14584         if (gameFileFP != NULL) {
14585             gameFileFP = NULL;
14586         }
14587         break;
14588       case EditPosition:
14589         EditPositionDone(TRUE);
14590         break;
14591       case AnalyzeMode:
14592       case AnalyzeFile:
14593         ExitAnalyzeMode();
14594         SendToProgram("force\n", &first);
14595         break;
14596       case TwoMachinesPlay:
14597         GameEnds(EndOfFile, NULL, GE_PLAYER);
14598         ResurrectChessProgram();
14599         SetUserThinkingEnables();
14600         break;
14601       case EndOfGame:
14602         ResurrectChessProgram();
14603         break;
14604       case IcsPlayingBlack:
14605       case IcsPlayingWhite:
14606         DisplayError(_("Warning: You are still playing a game"), 0);
14607         break;
14608       case IcsObserving:
14609         DisplayError(_("Warning: You are still observing a game"), 0);
14610         break;
14611       case IcsExamining:
14612         DisplayError(_("Warning: You are still examining a game"), 0);
14613         break;
14614       case IcsIdle:
14615         break;
14616       case EditGame:
14617       default:
14618         return;
14619     }
14620
14621     pausing = FALSE;
14622     StopClocks();
14623     first.offeredDraw = second.offeredDraw = 0;
14624
14625     if (gameMode == PlayFromGameFile) {
14626         whiteTimeRemaining = timeRemaining[0][currentMove];
14627         blackTimeRemaining = timeRemaining[1][currentMove];
14628         DisplayTitle("");
14629     }
14630
14631     if (gameMode == MachinePlaysWhite ||
14632         gameMode == MachinePlaysBlack ||
14633         gameMode == TwoMachinesPlay ||
14634         gameMode == EndOfGame) {
14635         i = forwardMostMove;
14636         while (i > currentMove) {
14637             SendToProgram("undo\n", &first);
14638             i--;
14639         }
14640         if(!adjustedClock) {
14641         whiteTimeRemaining = timeRemaining[0][currentMove];
14642         blackTimeRemaining = timeRemaining[1][currentMove];
14643         DisplayBothClocks();
14644         }
14645         if (whiteFlag || blackFlag) {
14646             whiteFlag = blackFlag = 0;
14647         }
14648         DisplayTitle("");
14649     }
14650
14651     gameMode = EditGame;
14652     ModeHighlight();
14653     SetGameInfo();
14654 }
14655
14656
14657 void
14658 EditPositionEvent ()
14659 {
14660     if (gameMode == EditPosition) {
14661         EditGameEvent();
14662         return;
14663     }
14664
14665     EditGameEvent();
14666     if (gameMode != EditGame) return;
14667
14668     gameMode = EditPosition;
14669     ModeHighlight();
14670     SetGameInfo();
14671     if (currentMove > 0)
14672       CopyBoard(boards[0], boards[currentMove]);
14673
14674     blackPlaysFirst = !WhiteOnMove(currentMove);
14675     ResetClocks();
14676     currentMove = forwardMostMove = backwardMostMove = 0;
14677     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14678     DisplayMove(-1);
14679     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14680 }
14681
14682 void
14683 ExitAnalyzeMode ()
14684 {
14685     /* [DM] icsEngineAnalyze - possible call from other functions */
14686     if (appData.icsEngineAnalyze) {
14687         appData.icsEngineAnalyze = FALSE;
14688
14689         DisplayMessage("",_("Close ICS engine analyze..."));
14690     }
14691     if (first.analysisSupport && first.analyzing) {
14692       SendToBoth("exit\n");
14693       first.analyzing = second.analyzing = FALSE;
14694     }
14695     thinkOutput[0] = NULLCHAR;
14696 }
14697
14698 void
14699 EditPositionDone (Boolean fakeRights)
14700 {
14701     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14702
14703     startedFromSetupPosition = TRUE;
14704     InitChessProgram(&first, FALSE);
14705     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14706       boards[0][EP_STATUS] = EP_NONE;
14707       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14708       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14709         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14710         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14711       } else boards[0][CASTLING][2] = NoRights;
14712       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14713         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14714         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14715       } else boards[0][CASTLING][5] = NoRights;
14716       if(gameInfo.variant == VariantSChess) {
14717         int i;
14718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14719           boards[0][VIRGIN][i] = 0;
14720           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14721           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14722         }
14723       }
14724     }
14725     SendToProgram("force\n", &first);
14726     if (blackPlaysFirst) {
14727         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14728         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14729         currentMove = forwardMostMove = backwardMostMove = 1;
14730         CopyBoard(boards[1], boards[0]);
14731     } else {
14732         currentMove = forwardMostMove = backwardMostMove = 0;
14733     }
14734     SendBoard(&first, forwardMostMove);
14735     if (appData.debugMode) {
14736         fprintf(debugFP, "EditPosDone\n");
14737     }
14738     DisplayTitle("");
14739     DisplayMessage("", "");
14740     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14741     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14742     gameMode = EditGame;
14743     ModeHighlight();
14744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14745     ClearHighlights(); /* [AS] */
14746 }
14747
14748 /* Pause for `ms' milliseconds */
14749 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14750 void
14751 TimeDelay (long ms)
14752 {
14753     TimeMark m1, m2;
14754
14755     GetTimeMark(&m1);
14756     do {
14757         GetTimeMark(&m2);
14758     } while (SubtractTimeMarks(&m2, &m1) < ms);
14759 }
14760
14761 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14762 void
14763 SendMultiLineToICS (char *buf)
14764 {
14765     char temp[MSG_SIZ+1], *p;
14766     int len;
14767
14768     len = strlen(buf);
14769     if (len > MSG_SIZ)
14770       len = MSG_SIZ;
14771
14772     strncpy(temp, buf, len);
14773     temp[len] = 0;
14774
14775     p = temp;
14776     while (*p) {
14777         if (*p == '\n' || *p == '\r')
14778           *p = ' ';
14779         ++p;
14780     }
14781
14782     strcat(temp, "\n");
14783     SendToICS(temp);
14784     SendToPlayer(temp, strlen(temp));
14785 }
14786
14787 void
14788 SetWhiteToPlayEvent ()
14789 {
14790     if (gameMode == EditPosition) {
14791         blackPlaysFirst = FALSE;
14792         DisplayBothClocks();    /* works because currentMove is 0 */
14793     } else if (gameMode == IcsExamining) {
14794         SendToICS(ics_prefix);
14795         SendToICS("tomove white\n");
14796     }
14797 }
14798
14799 void
14800 SetBlackToPlayEvent ()
14801 {
14802     if (gameMode == EditPosition) {
14803         blackPlaysFirst = TRUE;
14804         currentMove = 1;        /* kludge */
14805         DisplayBothClocks();
14806         currentMove = 0;
14807     } else if (gameMode == IcsExamining) {
14808         SendToICS(ics_prefix);
14809         SendToICS("tomove black\n");
14810     }
14811 }
14812
14813 void
14814 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14815 {
14816     char buf[MSG_SIZ];
14817     ChessSquare piece = boards[0][y][x];
14818     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14819     static int lastVariant;
14820
14821     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14822
14823     switch (selection) {
14824       case ClearBoard:
14825         CopyBoard(currentBoard, boards[0]);
14826         CopyBoard(menuBoard, initialPosition);
14827         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14828             SendToICS(ics_prefix);
14829             SendToICS("bsetup clear\n");
14830         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14831             SendToICS(ics_prefix);
14832             SendToICS("clearboard\n");
14833         } else {
14834             int nonEmpty = 0;
14835             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14836                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14837                 for (y = 0; y < BOARD_HEIGHT; y++) {
14838                     if (gameMode == IcsExamining) {
14839                         if (boards[currentMove][y][x] != EmptySquare) {
14840                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14841                                     AAA + x, ONE + y);
14842                             SendToICS(buf);
14843                         }
14844                     } else {
14845                         if(boards[0][y][x] != p) nonEmpty++;
14846                         boards[0][y][x] = p;
14847                     }
14848                 }
14849                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14850             }
14851             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14852                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14853                     ChessSquare p = menuBoard[0][x];
14854                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14855                     p = menuBoard[BOARD_HEIGHT-1][x];
14856                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14857                 }
14858                 DisplayMessage("Clicking clock again restores position", "");
14859                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14860                 if(!nonEmpty) { // asked to clear an empty board
14861                     CopyBoard(boards[0], menuBoard);
14862                 } else
14863                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14864                     CopyBoard(boards[0], initialPosition);
14865                 } else
14866                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14867                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14868                     CopyBoard(boards[0], erasedBoard);
14869                 } else
14870                     CopyBoard(erasedBoard, currentBoard);
14871
14872             }
14873         }
14874         if (gameMode == EditPosition) {
14875             DrawPosition(FALSE, boards[0]);
14876         }
14877         break;
14878
14879       case WhitePlay:
14880         SetWhiteToPlayEvent();
14881         break;
14882
14883       case BlackPlay:
14884         SetBlackToPlayEvent();
14885         break;
14886
14887       case EmptySquare:
14888         if (gameMode == IcsExamining) {
14889             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14890             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14891             SendToICS(buf);
14892         } else {
14893             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14894                 if(x == BOARD_LEFT-2) {
14895                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14896                     boards[0][y][1] = 0;
14897                 } else
14898                 if(x == BOARD_RGHT+1) {
14899                     if(y >= gameInfo.holdingsSize) break;
14900                     boards[0][y][BOARD_WIDTH-2] = 0;
14901                 } else break;
14902             }
14903             boards[0][y][x] = EmptySquare;
14904             DrawPosition(FALSE, boards[0]);
14905         }
14906         break;
14907
14908       case PromotePiece:
14909         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14910            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14911             selection = (ChessSquare) (PROMOTED piece);
14912         } else if(piece == EmptySquare) selection = WhiteSilver;
14913         else selection = (ChessSquare)((int)piece - 1);
14914         goto defaultlabel;
14915
14916       case DemotePiece:
14917         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14918            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14919             selection = (ChessSquare) (DEMOTED piece);
14920         } else if(piece == EmptySquare) selection = BlackSilver;
14921         else selection = (ChessSquare)((int)piece + 1);
14922         goto defaultlabel;
14923
14924       case WhiteQueen:
14925       case BlackQueen:
14926         if(gameInfo.variant == VariantShatranj ||
14927            gameInfo.variant == VariantXiangqi  ||
14928            gameInfo.variant == VariantCourier  ||
14929            gameInfo.variant == VariantASEAN    ||
14930            gameInfo.variant == VariantMakruk     )
14931             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14932         goto defaultlabel;
14933
14934       case WhiteKing:
14935       case BlackKing:
14936         if(gameInfo.variant == VariantXiangqi)
14937             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14938         if(gameInfo.variant == VariantKnightmate)
14939             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14940       default:
14941         defaultlabel:
14942         if (gameMode == IcsExamining) {
14943             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14944             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14945                      PieceToChar(selection), AAA + x, ONE + y);
14946             SendToICS(buf);
14947         } else {
14948             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14949                 int n;
14950                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14951                     n = PieceToNumber(selection - BlackPawn);
14952                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14953                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14954                     boards[0][BOARD_HEIGHT-1-n][1]++;
14955                 } else
14956                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14957                     n = PieceToNumber(selection);
14958                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14959                     boards[0][n][BOARD_WIDTH-1] = selection;
14960                     boards[0][n][BOARD_WIDTH-2]++;
14961                 }
14962             } else
14963             boards[0][y][x] = selection;
14964             DrawPosition(TRUE, boards[0]);
14965             ClearHighlights();
14966             fromX = fromY = -1;
14967         }
14968         break;
14969     }
14970 }
14971
14972
14973 void
14974 DropMenuEvent (ChessSquare selection, int x, int y)
14975 {
14976     ChessMove moveType;
14977
14978     switch (gameMode) {
14979       case IcsPlayingWhite:
14980       case MachinePlaysBlack:
14981         if (!WhiteOnMove(currentMove)) {
14982             DisplayMoveError(_("It is Black's turn"));
14983             return;
14984         }
14985         moveType = WhiteDrop;
14986         break;
14987       case IcsPlayingBlack:
14988       case MachinePlaysWhite:
14989         if (WhiteOnMove(currentMove)) {
14990             DisplayMoveError(_("It is White's turn"));
14991             return;
14992         }
14993         moveType = BlackDrop;
14994         break;
14995       case EditGame:
14996         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14997         break;
14998       default:
14999         return;
15000     }
15001
15002     if (moveType == BlackDrop && selection < BlackPawn) {
15003       selection = (ChessSquare) ((int) selection
15004                                  + (int) BlackPawn - (int) WhitePawn);
15005     }
15006     if (boards[currentMove][y][x] != EmptySquare) {
15007         DisplayMoveError(_("That square is occupied"));
15008         return;
15009     }
15010
15011     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15012 }
15013
15014 void
15015 AcceptEvent ()
15016 {
15017     /* Accept a pending offer of any kind from opponent */
15018
15019     if (appData.icsActive) {
15020         SendToICS(ics_prefix);
15021         SendToICS("accept\n");
15022     } else if (cmailMsgLoaded) {
15023         if (currentMove == cmailOldMove &&
15024             commentList[cmailOldMove] != NULL &&
15025             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15026                    "Black offers a draw" : "White offers a draw")) {
15027             TruncateGame();
15028             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15029             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15030         } else {
15031             DisplayError(_("There is no pending offer on this move"), 0);
15032             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15033         }
15034     } else {
15035         /* Not used for offers from chess program */
15036     }
15037 }
15038
15039 void
15040 DeclineEvent ()
15041 {
15042     /* Decline a pending offer of any kind from opponent */
15043
15044     if (appData.icsActive) {
15045         SendToICS(ics_prefix);
15046         SendToICS("decline\n");
15047     } else if (cmailMsgLoaded) {
15048         if (currentMove == cmailOldMove &&
15049             commentList[cmailOldMove] != NULL &&
15050             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15051                    "Black offers a draw" : "White offers a draw")) {
15052 #ifdef NOTDEF
15053             AppendComment(cmailOldMove, "Draw declined", TRUE);
15054             DisplayComment(cmailOldMove - 1, "Draw declined");
15055 #endif /*NOTDEF*/
15056         } else {
15057             DisplayError(_("There is no pending offer on this move"), 0);
15058         }
15059     } else {
15060         /* Not used for offers from chess program */
15061     }
15062 }
15063
15064 void
15065 RematchEvent ()
15066 {
15067     /* Issue ICS rematch command */
15068     if (appData.icsActive) {
15069         SendToICS(ics_prefix);
15070         SendToICS("rematch\n");
15071     }
15072 }
15073
15074 void
15075 CallFlagEvent ()
15076 {
15077     /* Call your opponent's flag (claim a win on time) */
15078     if (appData.icsActive) {
15079         SendToICS(ics_prefix);
15080         SendToICS("flag\n");
15081     } else {
15082         switch (gameMode) {
15083           default:
15084             return;
15085           case MachinePlaysWhite:
15086             if (whiteFlag) {
15087                 if (blackFlag)
15088                   GameEnds(GameIsDrawn, "Both players ran out of time",
15089                            GE_PLAYER);
15090                 else
15091                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15092             } else {
15093                 DisplayError(_("Your opponent is not out of time"), 0);
15094             }
15095             break;
15096           case MachinePlaysBlack:
15097             if (blackFlag) {
15098                 if (whiteFlag)
15099                   GameEnds(GameIsDrawn, "Both players ran out of time",
15100                            GE_PLAYER);
15101                 else
15102                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15103             } else {
15104                 DisplayError(_("Your opponent is not out of time"), 0);
15105             }
15106             break;
15107         }
15108     }
15109 }
15110
15111 void
15112 ClockClick (int which)
15113 {       // [HGM] code moved to back-end from winboard.c
15114         if(which) { // black clock
15115           if (gameMode == EditPosition || gameMode == IcsExamining) {
15116             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15117             SetBlackToPlayEvent();
15118           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15119           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15120           } else if (shiftKey) {
15121             AdjustClock(which, -1);
15122           } else if (gameMode == IcsPlayingWhite ||
15123                      gameMode == MachinePlaysBlack) {
15124             CallFlagEvent();
15125           }
15126         } else { // white clock
15127           if (gameMode == EditPosition || gameMode == IcsExamining) {
15128             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15129             SetWhiteToPlayEvent();
15130           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15131           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15132           } else if (shiftKey) {
15133             AdjustClock(which, -1);
15134           } else if (gameMode == IcsPlayingBlack ||
15135                    gameMode == MachinePlaysWhite) {
15136             CallFlagEvent();
15137           }
15138         }
15139 }
15140
15141 void
15142 DrawEvent ()
15143 {
15144     /* Offer draw or accept pending draw offer from opponent */
15145
15146     if (appData.icsActive) {
15147         /* Note: tournament rules require draw offers to be
15148            made after you make your move but before you punch
15149            your clock.  Currently ICS doesn't let you do that;
15150            instead, you immediately punch your clock after making
15151            a move, but you can offer a draw at any time. */
15152
15153         SendToICS(ics_prefix);
15154         SendToICS("draw\n");
15155         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15156     } else if (cmailMsgLoaded) {
15157         if (currentMove == cmailOldMove &&
15158             commentList[cmailOldMove] != NULL &&
15159             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15160                    "Black offers a draw" : "White offers a draw")) {
15161             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15162             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15163         } else if (currentMove == cmailOldMove + 1) {
15164             char *offer = WhiteOnMove(cmailOldMove) ?
15165               "White offers a draw" : "Black offers a draw";
15166             AppendComment(currentMove, offer, TRUE);
15167             DisplayComment(currentMove - 1, offer);
15168             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15169         } else {
15170             DisplayError(_("You must make your move before offering a draw"), 0);
15171             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15172         }
15173     } else if (first.offeredDraw) {
15174         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15175     } else {
15176         if (first.sendDrawOffers) {
15177             SendToProgram("draw\n", &first);
15178             userOfferedDraw = TRUE;
15179         }
15180     }
15181 }
15182
15183 void
15184 AdjournEvent ()
15185 {
15186     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15187
15188     if (appData.icsActive) {
15189         SendToICS(ics_prefix);
15190         SendToICS("adjourn\n");
15191     } else {
15192         /* Currently GNU Chess doesn't offer or accept Adjourns */
15193     }
15194 }
15195
15196
15197 void
15198 AbortEvent ()
15199 {
15200     /* Offer Abort or accept pending Abort offer from opponent */
15201
15202     if (appData.icsActive) {
15203         SendToICS(ics_prefix);
15204         SendToICS("abort\n");
15205     } else {
15206         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15207     }
15208 }
15209
15210 void
15211 ResignEvent ()
15212 {
15213     /* Resign.  You can do this even if it's not your turn. */
15214
15215     if (appData.icsActive) {
15216         SendToICS(ics_prefix);
15217         SendToICS("resign\n");
15218     } else {
15219         switch (gameMode) {
15220           case MachinePlaysWhite:
15221             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15222             break;
15223           case MachinePlaysBlack:
15224             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15225             break;
15226           case EditGame:
15227             if (cmailMsgLoaded) {
15228                 TruncateGame();
15229                 if (WhiteOnMove(cmailOldMove)) {
15230                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15231                 } else {
15232                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15233                 }
15234                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15235             }
15236             break;
15237           default:
15238             break;
15239         }
15240     }
15241 }
15242
15243
15244 void
15245 StopObservingEvent ()
15246 {
15247     /* Stop observing current games */
15248     SendToICS(ics_prefix);
15249     SendToICS("unobserve\n");
15250 }
15251
15252 void
15253 StopExaminingEvent ()
15254 {
15255     /* Stop observing current game */
15256     SendToICS(ics_prefix);
15257     SendToICS("unexamine\n");
15258 }
15259
15260 void
15261 ForwardInner (int target)
15262 {
15263     int limit; int oldSeekGraphUp = seekGraphUp;
15264
15265     if (appData.debugMode)
15266         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15267                 target, currentMove, forwardMostMove);
15268
15269     if (gameMode == EditPosition)
15270       return;
15271
15272     seekGraphUp = FALSE;
15273     MarkTargetSquares(1);
15274
15275     if (gameMode == PlayFromGameFile && !pausing)
15276       PauseEvent();
15277
15278     if (gameMode == IcsExamining && pausing)
15279       limit = pauseExamForwardMostMove;
15280     else
15281       limit = forwardMostMove;
15282
15283     if (target > limit) target = limit;
15284
15285     if (target > 0 && moveList[target - 1][0]) {
15286         int fromX, fromY, toX, toY;
15287         toX = moveList[target - 1][2] - AAA;
15288         toY = moveList[target - 1][3] - ONE;
15289         if (moveList[target - 1][1] == '@') {
15290             if (appData.highlightLastMove) {
15291                 SetHighlights(-1, -1, toX, toY);
15292             }
15293         } else {
15294             fromX = moveList[target - 1][0] - AAA;
15295             fromY = moveList[target - 1][1] - ONE;
15296             if (target == currentMove + 1) {
15297                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15298             }
15299             if (appData.highlightLastMove) {
15300                 SetHighlights(fromX, fromY, toX, toY);
15301             }
15302         }
15303     }
15304     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15305         gameMode == Training || gameMode == PlayFromGameFile ||
15306         gameMode == AnalyzeFile) {
15307         while (currentMove < target) {
15308             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15309             SendMoveToProgram(currentMove++, &first);
15310         }
15311     } else {
15312         currentMove = target;
15313     }
15314
15315     if (gameMode == EditGame || gameMode == EndOfGame) {
15316         whiteTimeRemaining = timeRemaining[0][currentMove];
15317         blackTimeRemaining = timeRemaining[1][currentMove];
15318     }
15319     DisplayBothClocks();
15320     DisplayMove(currentMove - 1);
15321     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15322     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15323     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15324         DisplayComment(currentMove - 1, commentList[currentMove]);
15325     }
15326     ClearMap(); // [HGM] exclude: invalidate map
15327 }
15328
15329
15330 void
15331 ForwardEvent ()
15332 {
15333     if (gameMode == IcsExamining && !pausing) {
15334         SendToICS(ics_prefix);
15335         SendToICS("forward\n");
15336     } else {
15337         ForwardInner(currentMove + 1);
15338     }
15339 }
15340
15341 void
15342 ToEndEvent ()
15343 {
15344     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15345         /* to optimze, we temporarily turn off analysis mode while we feed
15346          * the remaining moves to the engine. Otherwise we get analysis output
15347          * after each move.
15348          */
15349         if (first.analysisSupport) {
15350           SendToProgram("exit\nforce\n", &first);
15351           first.analyzing = FALSE;
15352         }
15353     }
15354
15355     if (gameMode == IcsExamining && !pausing) {
15356         SendToICS(ics_prefix);
15357         SendToICS("forward 999999\n");
15358     } else {
15359         ForwardInner(forwardMostMove);
15360     }
15361
15362     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15363         /* we have fed all the moves, so reactivate analysis mode */
15364         SendToProgram("analyze\n", &first);
15365         first.analyzing = TRUE;
15366         /*first.maybeThinking = TRUE;*/
15367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15368     }
15369 }
15370
15371 void
15372 BackwardInner (int target)
15373 {
15374     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15375
15376     if (appData.debugMode)
15377         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15378                 target, currentMove, forwardMostMove);
15379
15380     if (gameMode == EditPosition) return;
15381     seekGraphUp = FALSE;
15382     MarkTargetSquares(1);
15383     if (currentMove <= backwardMostMove) {
15384         ClearHighlights();
15385         DrawPosition(full_redraw, boards[currentMove]);
15386         return;
15387     }
15388     if (gameMode == PlayFromGameFile && !pausing)
15389       PauseEvent();
15390
15391     if (moveList[target][0]) {
15392         int fromX, fromY, toX, toY;
15393         toX = moveList[target][2] - AAA;
15394         toY = moveList[target][3] - ONE;
15395         if (moveList[target][1] == '@') {
15396             if (appData.highlightLastMove) {
15397                 SetHighlights(-1, -1, toX, toY);
15398             }
15399         } else {
15400             fromX = moveList[target][0] - AAA;
15401             fromY = moveList[target][1] - ONE;
15402             if (target == currentMove - 1) {
15403                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15404             }
15405             if (appData.highlightLastMove) {
15406                 SetHighlights(fromX, fromY, toX, toY);
15407             }
15408         }
15409     }
15410     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15411         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15412         while (currentMove > target) {
15413             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15414                 // null move cannot be undone. Reload program with move history before it.
15415                 int i;
15416                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15417                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15418                 }
15419                 SendBoard(&first, i);
15420               if(second.analyzing) SendBoard(&second, i);
15421                 for(currentMove=i; currentMove<target; currentMove++) {
15422                     SendMoveToProgram(currentMove, &first);
15423                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15424                 }
15425                 break;
15426             }
15427             SendToBoth("undo\n");
15428             currentMove--;
15429         }
15430     } else {
15431         currentMove = target;
15432     }
15433
15434     if (gameMode == EditGame || gameMode == EndOfGame) {
15435         whiteTimeRemaining = timeRemaining[0][currentMove];
15436         blackTimeRemaining = timeRemaining[1][currentMove];
15437     }
15438     DisplayBothClocks();
15439     DisplayMove(currentMove - 1);
15440     DrawPosition(full_redraw, boards[currentMove]);
15441     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15442     // [HGM] PV info: routine tests if comment empty
15443     DisplayComment(currentMove - 1, commentList[currentMove]);
15444     ClearMap(); // [HGM] exclude: invalidate map
15445 }
15446
15447 void
15448 BackwardEvent ()
15449 {
15450     if (gameMode == IcsExamining && !pausing) {
15451         SendToICS(ics_prefix);
15452         SendToICS("backward\n");
15453     } else {
15454         BackwardInner(currentMove - 1);
15455     }
15456 }
15457
15458 void
15459 ToStartEvent ()
15460 {
15461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15462         /* to optimize, we temporarily turn off analysis mode while we undo
15463          * all the moves. Otherwise we get analysis output after each undo.
15464          */
15465         if (first.analysisSupport) {
15466           SendToProgram("exit\nforce\n", &first);
15467           first.analyzing = FALSE;
15468         }
15469     }
15470
15471     if (gameMode == IcsExamining && !pausing) {
15472         SendToICS(ics_prefix);
15473         SendToICS("backward 999999\n");
15474     } else {
15475         BackwardInner(backwardMostMove);
15476     }
15477
15478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15479         /* we have fed all the moves, so reactivate analysis mode */
15480         SendToProgram("analyze\n", &first);
15481         first.analyzing = TRUE;
15482         /*first.maybeThinking = TRUE;*/
15483         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15484     }
15485 }
15486
15487 void
15488 ToNrEvent (int to)
15489 {
15490   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15491   if (to >= forwardMostMove) to = forwardMostMove;
15492   if (to <= backwardMostMove) to = backwardMostMove;
15493   if (to < currentMove) {
15494     BackwardInner(to);
15495   } else {
15496     ForwardInner(to);
15497   }
15498 }
15499
15500 void
15501 RevertEvent (Boolean annotate)
15502 {
15503     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15504         return;
15505     }
15506     if (gameMode != IcsExamining) {
15507         DisplayError(_("You are not examining a game"), 0);
15508         return;
15509     }
15510     if (pausing) {
15511         DisplayError(_("You can't revert while pausing"), 0);
15512         return;
15513     }
15514     SendToICS(ics_prefix);
15515     SendToICS("revert\n");
15516 }
15517
15518 void
15519 RetractMoveEvent ()
15520 {
15521     switch (gameMode) {
15522       case MachinePlaysWhite:
15523       case MachinePlaysBlack:
15524         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15525             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15526             return;
15527         }
15528         if (forwardMostMove < 2) return;
15529         currentMove = forwardMostMove = forwardMostMove - 2;
15530         whiteTimeRemaining = timeRemaining[0][currentMove];
15531         blackTimeRemaining = timeRemaining[1][currentMove];
15532         DisplayBothClocks();
15533         DisplayMove(currentMove - 1);
15534         ClearHighlights();/*!! could figure this out*/
15535         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15536         SendToProgram("remove\n", &first);
15537         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15538         break;
15539
15540       case BeginningOfGame:
15541       default:
15542         break;
15543
15544       case IcsPlayingWhite:
15545       case IcsPlayingBlack:
15546         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15547             SendToICS(ics_prefix);
15548             SendToICS("takeback 2\n");
15549         } else {
15550             SendToICS(ics_prefix);
15551             SendToICS("takeback 1\n");
15552         }
15553         break;
15554     }
15555 }
15556
15557 void
15558 MoveNowEvent ()
15559 {
15560     ChessProgramState *cps;
15561
15562     switch (gameMode) {
15563       case MachinePlaysWhite:
15564         if (!WhiteOnMove(forwardMostMove)) {
15565             DisplayError(_("It is your turn"), 0);
15566             return;
15567         }
15568         cps = &first;
15569         break;
15570       case MachinePlaysBlack:
15571         if (WhiteOnMove(forwardMostMove)) {
15572             DisplayError(_("It is your turn"), 0);
15573             return;
15574         }
15575         cps = &first;
15576         break;
15577       case TwoMachinesPlay:
15578         if (WhiteOnMove(forwardMostMove) ==
15579             (first.twoMachinesColor[0] == 'w')) {
15580             cps = &first;
15581         } else {
15582             cps = &second;
15583         }
15584         break;
15585       case BeginningOfGame:
15586       default:
15587         return;
15588     }
15589     SendToProgram("?\n", cps);
15590 }
15591
15592 void
15593 TruncateGameEvent ()
15594 {
15595     EditGameEvent();
15596     if (gameMode != EditGame) return;
15597     TruncateGame();
15598 }
15599
15600 void
15601 TruncateGame ()
15602 {
15603     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15604     if (forwardMostMove > currentMove) {
15605         if (gameInfo.resultDetails != NULL) {
15606             free(gameInfo.resultDetails);
15607             gameInfo.resultDetails = NULL;
15608             gameInfo.result = GameUnfinished;
15609         }
15610         forwardMostMove = currentMove;
15611         HistorySet(parseList, backwardMostMove, forwardMostMove,
15612                    currentMove-1);
15613     }
15614 }
15615
15616 void
15617 HintEvent ()
15618 {
15619     if (appData.noChessProgram) return;
15620     switch (gameMode) {
15621       case MachinePlaysWhite:
15622         if (WhiteOnMove(forwardMostMove)) {
15623             DisplayError(_("Wait until your turn."), 0);
15624             return;
15625         }
15626         break;
15627       case BeginningOfGame:
15628       case MachinePlaysBlack:
15629         if (!WhiteOnMove(forwardMostMove)) {
15630             DisplayError(_("Wait until your turn."), 0);
15631             return;
15632         }
15633         break;
15634       default:
15635         DisplayError(_("No hint available"), 0);
15636         return;
15637     }
15638     SendToProgram("hint\n", &first);
15639     hintRequested = TRUE;
15640 }
15641
15642 void
15643 CreateBookEvent ()
15644 {
15645     ListGame * lg = (ListGame *) gameList.head;
15646     FILE *f, *g;
15647     int nItem;
15648     static int secondTime = FALSE;
15649
15650     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15651         DisplayError(_("Game list not loaded or empty"), 0);
15652         return;
15653     }
15654
15655     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15656         fclose(g);
15657         secondTime++;
15658         DisplayNote(_("Book file exists! Try again for overwrite."));
15659         return;
15660     }
15661
15662     creatingBook = TRUE;
15663     secondTime = FALSE;
15664
15665     /* Get list size */
15666     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15667         LoadGame(f, nItem, "", TRUE);
15668         AddGameToBook(TRUE);
15669         lg = (ListGame *) lg->node.succ;
15670     }
15671
15672     creatingBook = FALSE;
15673     FlushBook();
15674 }
15675
15676 void
15677 BookEvent ()
15678 {
15679     if (appData.noChessProgram) return;
15680     switch (gameMode) {
15681       case MachinePlaysWhite:
15682         if (WhiteOnMove(forwardMostMove)) {
15683             DisplayError(_("Wait until your turn."), 0);
15684             return;
15685         }
15686         break;
15687       case BeginningOfGame:
15688       case MachinePlaysBlack:
15689         if (!WhiteOnMove(forwardMostMove)) {
15690             DisplayError(_("Wait until your turn."), 0);
15691             return;
15692         }
15693         break;
15694       case EditPosition:
15695         EditPositionDone(TRUE);
15696         break;
15697       case TwoMachinesPlay:
15698         return;
15699       default:
15700         break;
15701     }
15702     SendToProgram("bk\n", &first);
15703     bookOutput[0] = NULLCHAR;
15704     bookRequested = TRUE;
15705 }
15706
15707 void
15708 AboutGameEvent ()
15709 {
15710     char *tags = PGNTags(&gameInfo);
15711     TagsPopUp(tags, CmailMsg());
15712     free(tags);
15713 }
15714
15715 /* end button procedures */
15716
15717 void
15718 PrintPosition (FILE *fp, int move)
15719 {
15720     int i, j;
15721
15722     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15723         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15724             char c = PieceToChar(boards[move][i][j]);
15725             fputc(c == 'x' ? '.' : c, fp);
15726             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15727         }
15728     }
15729     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15730       fprintf(fp, "white to play\n");
15731     else
15732       fprintf(fp, "black to play\n");
15733 }
15734
15735 void
15736 PrintOpponents (FILE *fp)
15737 {
15738     if (gameInfo.white != NULL) {
15739         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15740     } else {
15741         fprintf(fp, "\n");
15742     }
15743 }
15744
15745 /* Find last component of program's own name, using some heuristics */
15746 void
15747 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15748 {
15749     char *p, *q, c;
15750     int local = (strcmp(host, "localhost") == 0);
15751     while (!local && (p = strchr(prog, ';')) != NULL) {
15752         p++;
15753         while (*p == ' ') p++;
15754         prog = p;
15755     }
15756     if (*prog == '"' || *prog == '\'') {
15757         q = strchr(prog + 1, *prog);
15758     } else {
15759         q = strchr(prog, ' ');
15760     }
15761     if (q == NULL) q = prog + strlen(prog);
15762     p = q;
15763     while (p >= prog && *p != '/' && *p != '\\') p--;
15764     p++;
15765     if(p == prog && *p == '"') p++;
15766     c = *q; *q = 0;
15767     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15768     memcpy(buf, p, q - p);
15769     buf[q - p] = NULLCHAR;
15770     if (!local) {
15771         strcat(buf, "@");
15772         strcat(buf, host);
15773     }
15774 }
15775
15776 char *
15777 TimeControlTagValue ()
15778 {
15779     char buf[MSG_SIZ];
15780     if (!appData.clockMode) {
15781       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15782     } else if (movesPerSession > 0) {
15783       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15784     } else if (timeIncrement == 0) {
15785       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15786     } else {
15787       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15788     }
15789     return StrSave(buf);
15790 }
15791
15792 void
15793 SetGameInfo ()
15794 {
15795     /* This routine is used only for certain modes */
15796     VariantClass v = gameInfo.variant;
15797     ChessMove r = GameUnfinished;
15798     char *p = NULL;
15799
15800     if(keepInfo) return;
15801
15802     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15803         r = gameInfo.result;
15804         p = gameInfo.resultDetails;
15805         gameInfo.resultDetails = NULL;
15806     }
15807     ClearGameInfo(&gameInfo);
15808     gameInfo.variant = v;
15809
15810     switch (gameMode) {
15811       case MachinePlaysWhite:
15812         gameInfo.event = StrSave( appData.pgnEventHeader );
15813         gameInfo.site = StrSave(HostName());
15814         gameInfo.date = PGNDate();
15815         gameInfo.round = StrSave("-");
15816         gameInfo.white = StrSave(first.tidy);
15817         gameInfo.black = StrSave(UserName());
15818         gameInfo.timeControl = TimeControlTagValue();
15819         break;
15820
15821       case MachinePlaysBlack:
15822         gameInfo.event = StrSave( appData.pgnEventHeader );
15823         gameInfo.site = StrSave(HostName());
15824         gameInfo.date = PGNDate();
15825         gameInfo.round = StrSave("-");
15826         gameInfo.white = StrSave(UserName());
15827         gameInfo.black = StrSave(first.tidy);
15828         gameInfo.timeControl = TimeControlTagValue();
15829         break;
15830
15831       case TwoMachinesPlay:
15832         gameInfo.event = StrSave( appData.pgnEventHeader );
15833         gameInfo.site = StrSave(HostName());
15834         gameInfo.date = PGNDate();
15835         if (roundNr > 0) {
15836             char buf[MSG_SIZ];
15837             snprintf(buf, MSG_SIZ, "%d", roundNr);
15838             gameInfo.round = StrSave(buf);
15839         } else {
15840             gameInfo.round = StrSave("-");
15841         }
15842         if (first.twoMachinesColor[0] == 'w') {
15843             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15844             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15845         } else {
15846             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15847             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15848         }
15849         gameInfo.timeControl = TimeControlTagValue();
15850         break;
15851
15852       case EditGame:
15853         gameInfo.event = StrSave("Edited game");
15854         gameInfo.site = StrSave(HostName());
15855         gameInfo.date = PGNDate();
15856         gameInfo.round = StrSave("-");
15857         gameInfo.white = StrSave("-");
15858         gameInfo.black = StrSave("-");
15859         gameInfo.result = r;
15860         gameInfo.resultDetails = p;
15861         break;
15862
15863       case EditPosition:
15864         gameInfo.event = StrSave("Edited position");
15865         gameInfo.site = StrSave(HostName());
15866         gameInfo.date = PGNDate();
15867         gameInfo.round = StrSave("-");
15868         gameInfo.white = StrSave("-");
15869         gameInfo.black = StrSave("-");
15870         break;
15871
15872       case IcsPlayingWhite:
15873       case IcsPlayingBlack:
15874       case IcsObserving:
15875       case IcsExamining:
15876         break;
15877
15878       case PlayFromGameFile:
15879         gameInfo.event = StrSave("Game from non-PGN file");
15880         gameInfo.site = StrSave(HostName());
15881         gameInfo.date = PGNDate();
15882         gameInfo.round = StrSave("-");
15883         gameInfo.white = StrSave("?");
15884         gameInfo.black = StrSave("?");
15885         break;
15886
15887       default:
15888         break;
15889     }
15890 }
15891
15892 void
15893 ReplaceComment (int index, char *text)
15894 {
15895     int len;
15896     char *p;
15897     float score;
15898
15899     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15900        pvInfoList[index-1].depth == len &&
15901        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15902        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15903     while (*text == '\n') text++;
15904     len = strlen(text);
15905     while (len > 0 && text[len - 1] == '\n') len--;
15906
15907     if (commentList[index] != NULL)
15908       free(commentList[index]);
15909
15910     if (len == 0) {
15911         commentList[index] = NULL;
15912         return;
15913     }
15914   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15915       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15916       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15917     commentList[index] = (char *) malloc(len + 2);
15918     strncpy(commentList[index], text, len);
15919     commentList[index][len] = '\n';
15920     commentList[index][len + 1] = NULLCHAR;
15921   } else {
15922     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15923     char *p;
15924     commentList[index] = (char *) malloc(len + 7);
15925     safeStrCpy(commentList[index], "{\n", 3);
15926     safeStrCpy(commentList[index]+2, text, len+1);
15927     commentList[index][len+2] = NULLCHAR;
15928     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15929     strcat(commentList[index], "\n}\n");
15930   }
15931 }
15932
15933 void
15934 CrushCRs (char *text)
15935 {
15936   char *p = text;
15937   char *q = text;
15938   char ch;
15939
15940   do {
15941     ch = *p++;
15942     if (ch == '\r') continue;
15943     *q++ = ch;
15944   } while (ch != '\0');
15945 }
15946
15947 void
15948 AppendComment (int index, char *text, Boolean addBraces)
15949 /* addBraces  tells if we should add {} */
15950 {
15951     int oldlen, len;
15952     char *old;
15953
15954 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15955     if(addBraces == 3) addBraces = 0; else // force appending literally
15956     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15957
15958     CrushCRs(text);
15959     while (*text == '\n') text++;
15960     len = strlen(text);
15961     while (len > 0 && text[len - 1] == '\n') len--;
15962     text[len] = NULLCHAR;
15963
15964     if (len == 0) return;
15965
15966     if (commentList[index] != NULL) {
15967       Boolean addClosingBrace = addBraces;
15968         old = commentList[index];
15969         oldlen = strlen(old);
15970         while(commentList[index][oldlen-1] ==  '\n')
15971           commentList[index][--oldlen] = NULLCHAR;
15972         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15973         safeStrCpy(commentList[index], old, oldlen + len + 6);
15974         free(old);
15975         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15976         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15977           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15978           while (*text == '\n') { text++; len--; }
15979           commentList[index][--oldlen] = NULLCHAR;
15980       }
15981         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15982         else          strcat(commentList[index], "\n");
15983         strcat(commentList[index], text);
15984         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15985         else          strcat(commentList[index], "\n");
15986     } else {
15987         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15988         if(addBraces)
15989           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15990         else commentList[index][0] = NULLCHAR;
15991         strcat(commentList[index], text);
15992         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15993         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15994     }
15995 }
15996
15997 static char *
15998 FindStr (char * text, char * sub_text)
15999 {
16000     char * result = strstr( text, sub_text );
16001
16002     if( result != NULL ) {
16003         result += strlen( sub_text );
16004     }
16005
16006     return result;
16007 }
16008
16009 /* [AS] Try to extract PV info from PGN comment */
16010 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16011 char *
16012 GetInfoFromComment (int index, char * text)
16013 {
16014     char * sep = text, *p;
16015
16016     if( text != NULL && index > 0 ) {
16017         int score = 0;
16018         int depth = 0;
16019         int time = -1, sec = 0, deci;
16020         char * s_eval = FindStr( text, "[%eval " );
16021         char * s_emt = FindStr( text, "[%emt " );
16022 #if 0
16023         if( s_eval != NULL || s_emt != NULL ) {
16024 #else
16025         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16026 #endif
16027             /* New style */
16028             char delim;
16029
16030             if( s_eval != NULL ) {
16031                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16032                     return text;
16033                 }
16034
16035                 if( delim != ']' ) {
16036                     return text;
16037                 }
16038             }
16039
16040             if( s_emt != NULL ) {
16041             }
16042                 return text;
16043         }
16044         else {
16045             /* We expect something like: [+|-]nnn.nn/dd */
16046             int score_lo = 0;
16047
16048             if(*text != '{') return text; // [HGM] braces: must be normal comment
16049
16050             sep = strchr( text, '/' );
16051             if( sep == NULL || sep < (text+4) ) {
16052                 return text;
16053             }
16054
16055             p = text;
16056             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16057             if(p[1] == '(') { // comment starts with PV
16058                p = strchr(p, ')'); // locate end of PV
16059                if(p == NULL || sep < p+5) return text;
16060                // at this point we have something like "{(.*) +0.23/6 ..."
16061                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16062                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16063                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16064             }
16065             time = -1; sec = -1; deci = -1;
16066             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16067                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16068                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16069                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16070                 return text;
16071             }
16072
16073             if( score_lo < 0 || score_lo >= 100 ) {
16074                 return text;
16075             }
16076
16077             if(sec >= 0) time = 600*time + 10*sec; else
16078             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16079
16080             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16081
16082             /* [HGM] PV time: now locate end of PV info */
16083             while( *++sep >= '0' && *sep <= '9'); // strip depth
16084             if(time >= 0)
16085             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16086             if(sec >= 0)
16087             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16088             if(deci >= 0)
16089             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16090             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16091         }
16092
16093         if( depth <= 0 ) {
16094             return text;
16095         }
16096
16097         if( time < 0 ) {
16098             time = -1;
16099         }
16100
16101         pvInfoList[index-1].depth = depth;
16102         pvInfoList[index-1].score = score;
16103         pvInfoList[index-1].time  = 10*time; // centi-sec
16104         if(*sep == '}') *sep = 0; else *--sep = '{';
16105         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16106     }
16107     return sep;
16108 }
16109
16110 void
16111 SendToProgram (char *message, ChessProgramState *cps)
16112 {
16113     int count, outCount, error;
16114     char buf[MSG_SIZ];
16115
16116     if (cps->pr == NoProc) return;
16117     Attention(cps);
16118
16119     if (appData.debugMode) {
16120         TimeMark now;
16121         GetTimeMark(&now);
16122         fprintf(debugFP, "%ld >%-6s: %s",
16123                 SubtractTimeMarks(&now, &programStartTime),
16124                 cps->which, message);
16125         if(serverFP)
16126             fprintf(serverFP, "%ld >%-6s: %s",
16127                 SubtractTimeMarks(&now, &programStartTime),
16128                 cps->which, message), fflush(serverFP);
16129     }
16130
16131     count = strlen(message);
16132     outCount = OutputToProcess(cps->pr, message, count, &error);
16133     if (outCount < count && !exiting
16134                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16135       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16136       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16137         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16138             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16139                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16140                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16141                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16142             } else {
16143                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16144                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16145                 gameInfo.result = res;
16146             }
16147             gameInfo.resultDetails = StrSave(buf);
16148         }
16149         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16150         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16151     }
16152 }
16153
16154 void
16155 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16156 {
16157     char *end_str;
16158     char buf[MSG_SIZ];
16159     ChessProgramState *cps = (ChessProgramState *)closure;
16160
16161     if (isr != cps->isr) return; /* Killed intentionally */
16162     if (count <= 0) {
16163         if (count == 0) {
16164             RemoveInputSource(cps->isr);
16165             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16166                     _(cps->which), cps->program);
16167             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16168             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16169                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16170                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16171                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16172                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16173                 } else {
16174                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16175                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16176                     gameInfo.result = res;
16177                 }
16178                 gameInfo.resultDetails = StrSave(buf);
16179             }
16180             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16181             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16182         } else {
16183             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16184                     _(cps->which), cps->program);
16185             RemoveInputSource(cps->isr);
16186
16187             /* [AS] Program is misbehaving badly... kill it */
16188             if( count == -2 ) {
16189                 DestroyChildProcess( cps->pr, 9 );
16190                 cps->pr = NoProc;
16191             }
16192
16193             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16194         }
16195         return;
16196     }
16197
16198     if ((end_str = strchr(message, '\r')) != NULL)
16199       *end_str = NULLCHAR;
16200     if ((end_str = strchr(message, '\n')) != NULL)
16201       *end_str = NULLCHAR;
16202
16203     if (appData.debugMode) {
16204         TimeMark now; int print = 1;
16205         char *quote = ""; char c; int i;
16206
16207         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16208                 char start = message[0];
16209                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16210                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16211                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16212                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16213                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16214                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16215                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16216                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16217                    sscanf(message, "hint: %c", &c)!=1 &&
16218                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16219                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16220                     print = (appData.engineComments >= 2);
16221                 }
16222                 message[0] = start; // restore original message
16223         }
16224         if(print) {
16225                 GetTimeMark(&now);
16226                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16227                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16228                         quote,
16229                         message);
16230                 if(serverFP)
16231                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16232                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16233                         quote,
16234                         message), fflush(serverFP);
16235         }
16236     }
16237
16238     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16239     if (appData.icsEngineAnalyze) {
16240         if (strstr(message, "whisper") != NULL ||
16241              strstr(message, "kibitz") != NULL ||
16242             strstr(message, "tellics") != NULL) return;
16243     }
16244
16245     HandleMachineMove(message, cps);
16246 }
16247
16248
16249 void
16250 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16251 {
16252     char buf[MSG_SIZ];
16253     int seconds;
16254
16255     if( timeControl_2 > 0 ) {
16256         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16257             tc = timeControl_2;
16258         }
16259     }
16260     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16261     inc /= cps->timeOdds;
16262     st  /= cps->timeOdds;
16263
16264     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16265
16266     if (st > 0) {
16267       /* Set exact time per move, normally using st command */
16268       if (cps->stKludge) {
16269         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16270         seconds = st % 60;
16271         if (seconds == 0) {
16272           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16273         } else {
16274           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16275         }
16276       } else {
16277         snprintf(buf, MSG_SIZ, "st %d\n", st);
16278       }
16279     } else {
16280       /* Set conventional or incremental time control, using level command */
16281       if (seconds == 0) {
16282         /* Note old gnuchess bug -- minutes:seconds used to not work.
16283            Fixed in later versions, but still avoid :seconds
16284            when seconds is 0. */
16285         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16286       } else {
16287         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16288                  seconds, inc/1000.);
16289       }
16290     }
16291     SendToProgram(buf, cps);
16292
16293     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16294     /* Orthogonally, limit search to given depth */
16295     if (sd > 0) {
16296       if (cps->sdKludge) {
16297         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16298       } else {
16299         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16300       }
16301       SendToProgram(buf, cps);
16302     }
16303
16304     if(cps->nps >= 0) { /* [HGM] nps */
16305         if(cps->supportsNPS == FALSE)
16306           cps->nps = -1; // don't use if engine explicitly says not supported!
16307         else {
16308           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16309           SendToProgram(buf, cps);
16310         }
16311     }
16312 }
16313
16314 ChessProgramState *
16315 WhitePlayer ()
16316 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16317 {
16318     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16319        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16320         return &second;
16321     return &first;
16322 }
16323
16324 void
16325 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16326 {
16327     char message[MSG_SIZ];
16328     long time, otime;
16329
16330     /* Note: this routine must be called when the clocks are stopped
16331        or when they have *just* been set or switched; otherwise
16332        it will be off by the time since the current tick started.
16333     */
16334     if (machineWhite) {
16335         time = whiteTimeRemaining / 10;
16336         otime = blackTimeRemaining / 10;
16337     } else {
16338         time = blackTimeRemaining / 10;
16339         otime = whiteTimeRemaining / 10;
16340     }
16341     /* [HGM] translate opponent's time by time-odds factor */
16342     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16343
16344     if (time <= 0) time = 1;
16345     if (otime <= 0) otime = 1;
16346
16347     snprintf(message, MSG_SIZ, "time %ld\n", time);
16348     SendToProgram(message, cps);
16349
16350     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16351     SendToProgram(message, cps);
16352 }
16353
16354 char *
16355 EngineDefinedVariant (ChessProgramState *cps, int n)
16356 {   // return name of n-th unknown variant that engine supports
16357     static char buf[MSG_SIZ];
16358     char *p, *s = cps->variants;
16359     if(!s) return NULL;
16360     do { // parse string from variants feature
16361       VariantClass v;
16362         p = strchr(s, ',');
16363         if(p) *p = NULLCHAR;
16364       v = StringToVariant(s);
16365       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16366         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16367             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16368         }
16369         if(p) *p++ = ',';
16370         if(n < 0) return buf;
16371     } while(s = p);
16372     return NULL;
16373 }
16374
16375 int
16376 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16377 {
16378   char buf[MSG_SIZ];
16379   int len = strlen(name);
16380   int val;
16381
16382   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16383     (*p) += len + 1;
16384     sscanf(*p, "%d", &val);
16385     *loc = (val != 0);
16386     while (**p && **p != ' ')
16387       (*p)++;
16388     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16389     SendToProgram(buf, cps);
16390     return TRUE;
16391   }
16392   return FALSE;
16393 }
16394
16395 int
16396 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16397 {
16398   char buf[MSG_SIZ];
16399   int len = strlen(name);
16400   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16401     (*p) += len + 1;
16402     sscanf(*p, "%d", loc);
16403     while (**p && **p != ' ') (*p)++;
16404     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16405     SendToProgram(buf, cps);
16406     return TRUE;
16407   }
16408   return FALSE;
16409 }
16410
16411 int
16412 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16413 {
16414   char buf[MSG_SIZ];
16415   int len = strlen(name);
16416   if (strncmp((*p), name, len) == 0
16417       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16418     (*p) += len + 2;
16419     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16420     sscanf(*p, "%[^\"]", *loc);
16421     while (**p && **p != '\"') (*p)++;
16422     if (**p == '\"') (*p)++;
16423     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16424     SendToProgram(buf, cps);
16425     return TRUE;
16426   }
16427   return FALSE;
16428 }
16429
16430 int
16431 ParseOption (Option *opt, ChessProgramState *cps)
16432 // [HGM] options: process the string that defines an engine option, and determine
16433 // name, type, default value, and allowed value range
16434 {
16435         char *p, *q, buf[MSG_SIZ];
16436         int n, min = (-1)<<31, max = 1<<31, def;
16437
16438         if(p = strstr(opt->name, " -spin ")) {
16439             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16440             if(max < min) max = min; // enforce consistency
16441             if(def < min) def = min;
16442             if(def > max) def = max;
16443             opt->value = def;
16444             opt->min = min;
16445             opt->max = max;
16446             opt->type = Spin;
16447         } else if((p = strstr(opt->name, " -slider "))) {
16448             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16449             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16450             if(max < min) max = min; // enforce consistency
16451             if(def < min) def = min;
16452             if(def > max) def = max;
16453             opt->value = def;
16454             opt->min = min;
16455             opt->max = max;
16456             opt->type = Spin; // Slider;
16457         } else if((p = strstr(opt->name, " -string "))) {
16458             opt->textValue = p+9;
16459             opt->type = TextBox;
16460         } else if((p = strstr(opt->name, " -file "))) {
16461             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16462             opt->textValue = p+7;
16463             opt->type = FileName; // FileName;
16464         } else if((p = strstr(opt->name, " -path "))) {
16465             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16466             opt->textValue = p+7;
16467             opt->type = PathName; // PathName;
16468         } else if(p = strstr(opt->name, " -check ")) {
16469             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16470             opt->value = (def != 0);
16471             opt->type = CheckBox;
16472         } else if(p = strstr(opt->name, " -combo ")) {
16473             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16474             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16475             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16476             opt->value = n = 0;
16477             while(q = StrStr(q, " /// ")) {
16478                 n++; *q = 0;    // count choices, and null-terminate each of them
16479                 q += 5;
16480                 if(*q == '*') { // remember default, which is marked with * prefix
16481                     q++;
16482                     opt->value = n;
16483                 }
16484                 cps->comboList[cps->comboCnt++] = q;
16485             }
16486             cps->comboList[cps->comboCnt++] = NULL;
16487             opt->max = n + 1;
16488             opt->type = ComboBox;
16489         } else if(p = strstr(opt->name, " -button")) {
16490             opt->type = Button;
16491         } else if(p = strstr(opt->name, " -save")) {
16492             opt->type = SaveButton;
16493         } else return FALSE;
16494         *p = 0; // terminate option name
16495         // now look if the command-line options define a setting for this engine option.
16496         if(cps->optionSettings && cps->optionSettings[0])
16497             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16498         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16499           snprintf(buf, MSG_SIZ, "option %s", p);
16500                 if(p = strstr(buf, ",")) *p = 0;
16501                 if(q = strchr(buf, '=')) switch(opt->type) {
16502                     case ComboBox:
16503                         for(n=0; n<opt->max; n++)
16504                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16505                         break;
16506                     case TextBox:
16507                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16508                         break;
16509                     case Spin:
16510                     case CheckBox:
16511                         opt->value = atoi(q+1);
16512                     default:
16513                         break;
16514                 }
16515                 strcat(buf, "\n");
16516                 SendToProgram(buf, cps);
16517         }
16518         return TRUE;
16519 }
16520
16521 void
16522 FeatureDone (ChessProgramState *cps, int val)
16523 {
16524   DelayedEventCallback cb = GetDelayedEvent();
16525   if ((cb == InitBackEnd3 && cps == &first) ||
16526       (cb == SettingsMenuIfReady && cps == &second) ||
16527       (cb == LoadEngine) ||
16528       (cb == TwoMachinesEventIfReady)) {
16529     CancelDelayedEvent();
16530     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16531   }
16532   cps->initDone = val;
16533   if(val) cps->reload = FALSE;
16534 }
16535
16536 /* Parse feature command from engine */
16537 void
16538 ParseFeatures (char *args, ChessProgramState *cps)
16539 {
16540   char *p = args;
16541   char *q = NULL;
16542   int val;
16543   char buf[MSG_SIZ];
16544
16545   for (;;) {
16546     while (*p == ' ') p++;
16547     if (*p == NULLCHAR) return;
16548
16549     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16550     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16551     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16552     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16553     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16554     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16555     if (BoolFeature(&p, "reuse", &val, cps)) {
16556       /* Engine can disable reuse, but can't enable it if user said no */
16557       if (!val) cps->reuse = FALSE;
16558       continue;
16559     }
16560     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16561     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16562       if (gameMode == TwoMachinesPlay) {
16563         DisplayTwoMachinesTitle();
16564       } else {
16565         DisplayTitle("");
16566       }
16567       continue;
16568     }
16569     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16570     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16571     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16572     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16573     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16574     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16575     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16576     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16577     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16578     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16579     if (IntFeature(&p, "done", &val, cps)) {
16580       FeatureDone(cps, val);
16581       continue;
16582     }
16583     /* Added by Tord: */
16584     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16585     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16586     /* End of additions by Tord */
16587
16588     /* [HGM] added features: */
16589     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16590     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16591     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16592     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16593     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16594     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16595     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16596     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16597         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16598         FREE(cps->option[cps->nrOptions].name);
16599         cps->option[cps->nrOptions].name = q; q = NULL;
16600         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16601           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16602             SendToProgram(buf, cps);
16603             continue;
16604         }
16605         if(cps->nrOptions >= MAX_OPTIONS) {
16606             cps->nrOptions--;
16607             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16608             DisplayError(buf, 0);
16609         }
16610         continue;
16611     }
16612     /* End of additions by HGM */
16613
16614     /* unknown feature: complain and skip */
16615     q = p;
16616     while (*q && *q != '=') q++;
16617     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16618     SendToProgram(buf, cps);
16619     p = q;
16620     if (*p == '=') {
16621       p++;
16622       if (*p == '\"') {
16623         p++;
16624         while (*p && *p != '\"') p++;
16625         if (*p == '\"') p++;
16626       } else {
16627         while (*p && *p != ' ') p++;
16628       }
16629     }
16630   }
16631
16632 }
16633
16634 void
16635 PeriodicUpdatesEvent (int newState)
16636 {
16637     if (newState == appData.periodicUpdates)
16638       return;
16639
16640     appData.periodicUpdates=newState;
16641
16642     /* Display type changes, so update it now */
16643 //    DisplayAnalysis();
16644
16645     /* Get the ball rolling again... */
16646     if (newState) {
16647         AnalysisPeriodicEvent(1);
16648         StartAnalysisClock();
16649     }
16650 }
16651
16652 void
16653 PonderNextMoveEvent (int newState)
16654 {
16655     if (newState == appData.ponderNextMove) return;
16656     if (gameMode == EditPosition) EditPositionDone(TRUE);
16657     if (newState) {
16658         SendToProgram("hard\n", &first);
16659         if (gameMode == TwoMachinesPlay) {
16660             SendToProgram("hard\n", &second);
16661         }
16662     } else {
16663         SendToProgram("easy\n", &first);
16664         thinkOutput[0] = NULLCHAR;
16665         if (gameMode == TwoMachinesPlay) {
16666             SendToProgram("easy\n", &second);
16667         }
16668     }
16669     appData.ponderNextMove = newState;
16670 }
16671
16672 void
16673 NewSettingEvent (int option, int *feature, char *command, int value)
16674 {
16675     char buf[MSG_SIZ];
16676
16677     if (gameMode == EditPosition) EditPositionDone(TRUE);
16678     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16679     if(feature == NULL || *feature) SendToProgram(buf, &first);
16680     if (gameMode == TwoMachinesPlay) {
16681         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16682     }
16683 }
16684
16685 void
16686 ShowThinkingEvent ()
16687 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16688 {
16689     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16690     int newState = appData.showThinking
16691         // [HGM] thinking: other features now need thinking output as well
16692         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16693
16694     if (oldState == newState) return;
16695     oldState = newState;
16696     if (gameMode == EditPosition) EditPositionDone(TRUE);
16697     if (oldState) {
16698         SendToProgram("post\n", &first);
16699         if (gameMode == TwoMachinesPlay) {
16700             SendToProgram("post\n", &second);
16701         }
16702     } else {
16703         SendToProgram("nopost\n", &first);
16704         thinkOutput[0] = NULLCHAR;
16705         if (gameMode == TwoMachinesPlay) {
16706             SendToProgram("nopost\n", &second);
16707         }
16708     }
16709 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16710 }
16711
16712 void
16713 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16714 {
16715   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16716   if (pr == NoProc) return;
16717   AskQuestion(title, question, replyPrefix, pr);
16718 }
16719
16720 void
16721 TypeInEvent (char firstChar)
16722 {
16723     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16724         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16725         gameMode == AnalyzeMode || gameMode == EditGame ||
16726         gameMode == EditPosition || gameMode == IcsExamining ||
16727         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16728         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16729                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16730                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16731         gameMode == Training) PopUpMoveDialog(firstChar);
16732 }
16733
16734 void
16735 TypeInDoneEvent (char *move)
16736 {
16737         Board board;
16738         int n, fromX, fromY, toX, toY;
16739         char promoChar;
16740         ChessMove moveType;
16741
16742         // [HGM] FENedit
16743         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16744                 EditPositionPasteFEN(move);
16745                 return;
16746         }
16747         // [HGM] movenum: allow move number to be typed in any mode
16748         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16749           ToNrEvent(2*n-1);
16750           return;
16751         }
16752         // undocumented kludge: allow command-line option to be typed in!
16753         // (potentially fatal, and does not implement the effect of the option.)
16754         // should only be used for options that are values on which future decisions will be made,
16755         // and definitely not on options that would be used during initialization.
16756         if(strstr(move, "!!! -") == move) {
16757             ParseArgsFromString(move+4);
16758             return;
16759         }
16760
16761       if (gameMode != EditGame && currentMove != forwardMostMove &&
16762         gameMode != Training) {
16763         DisplayMoveError(_("Displayed move is not current"));
16764       } else {
16765         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16766           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16767         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16768         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16769           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16770           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16771         } else {
16772           DisplayMoveError(_("Could not parse move"));
16773         }
16774       }
16775 }
16776
16777 void
16778 DisplayMove (int moveNumber)
16779 {
16780     char message[MSG_SIZ];
16781     char res[MSG_SIZ];
16782     char cpThinkOutput[MSG_SIZ];
16783
16784     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16785
16786     if (moveNumber == forwardMostMove - 1 ||
16787         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16788
16789         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16790
16791         if (strchr(cpThinkOutput, '\n')) {
16792             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16793         }
16794     } else {
16795         *cpThinkOutput = NULLCHAR;
16796     }
16797
16798     /* [AS] Hide thinking from human user */
16799     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16800         *cpThinkOutput = NULLCHAR;
16801         if( thinkOutput[0] != NULLCHAR ) {
16802             int i;
16803
16804             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16805                 cpThinkOutput[i] = '.';
16806             }
16807             cpThinkOutput[i] = NULLCHAR;
16808             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16809         }
16810     }
16811
16812     if (moveNumber == forwardMostMove - 1 &&
16813         gameInfo.resultDetails != NULL) {
16814         if (gameInfo.resultDetails[0] == NULLCHAR) {
16815           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16816         } else {
16817           snprintf(res, MSG_SIZ, " {%s} %s",
16818                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16819         }
16820     } else {
16821         res[0] = NULLCHAR;
16822     }
16823
16824     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16825         DisplayMessage(res, cpThinkOutput);
16826     } else {
16827       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16828                 WhiteOnMove(moveNumber) ? " " : ".. ",
16829                 parseList[moveNumber], res);
16830         DisplayMessage(message, cpThinkOutput);
16831     }
16832 }
16833
16834 void
16835 DisplayComment (int moveNumber, char *text)
16836 {
16837     char title[MSG_SIZ];
16838
16839     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16840       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16841     } else {
16842       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16843               WhiteOnMove(moveNumber) ? " " : ".. ",
16844               parseList[moveNumber]);
16845     }
16846     if (text != NULL && (appData.autoDisplayComment || commentUp))
16847         CommentPopUp(title, text);
16848 }
16849
16850 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16851  * might be busy thinking or pondering.  It can be omitted if your
16852  * gnuchess is configured to stop thinking immediately on any user
16853  * input.  However, that gnuchess feature depends on the FIONREAD
16854  * ioctl, which does not work properly on some flavors of Unix.
16855  */
16856 void
16857 Attention (ChessProgramState *cps)
16858 {
16859 #if ATTENTION
16860     if (!cps->useSigint) return;
16861     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16862     switch (gameMode) {
16863       case MachinePlaysWhite:
16864       case MachinePlaysBlack:
16865       case TwoMachinesPlay:
16866       case IcsPlayingWhite:
16867       case IcsPlayingBlack:
16868       case AnalyzeMode:
16869       case AnalyzeFile:
16870         /* Skip if we know it isn't thinking */
16871         if (!cps->maybeThinking) return;
16872         if (appData.debugMode)
16873           fprintf(debugFP, "Interrupting %s\n", cps->which);
16874         InterruptChildProcess(cps->pr);
16875         cps->maybeThinking = FALSE;
16876         break;
16877       default:
16878         break;
16879     }
16880 #endif /*ATTENTION*/
16881 }
16882
16883 int
16884 CheckFlags ()
16885 {
16886     if (whiteTimeRemaining <= 0) {
16887         if (!whiteFlag) {
16888             whiteFlag = TRUE;
16889             if (appData.icsActive) {
16890                 if (appData.autoCallFlag &&
16891                     gameMode == IcsPlayingBlack && !blackFlag) {
16892                   SendToICS(ics_prefix);
16893                   SendToICS("flag\n");
16894                 }
16895             } else {
16896                 if (blackFlag) {
16897                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16898                 } else {
16899                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16900                     if (appData.autoCallFlag) {
16901                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16902                         return TRUE;
16903                     }
16904                 }
16905             }
16906         }
16907     }
16908     if (blackTimeRemaining <= 0) {
16909         if (!blackFlag) {
16910             blackFlag = TRUE;
16911             if (appData.icsActive) {
16912                 if (appData.autoCallFlag &&
16913                     gameMode == IcsPlayingWhite && !whiteFlag) {
16914                   SendToICS(ics_prefix);
16915                   SendToICS("flag\n");
16916                 }
16917             } else {
16918                 if (whiteFlag) {
16919                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16920                 } else {
16921                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16922                     if (appData.autoCallFlag) {
16923                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16924                         return TRUE;
16925                     }
16926                 }
16927             }
16928         }
16929     }
16930     return FALSE;
16931 }
16932
16933 void
16934 CheckTimeControl ()
16935 {
16936     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16937         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16938
16939     /*
16940      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16941      */
16942     if ( !WhiteOnMove(forwardMostMove) ) {
16943         /* White made time control */
16944         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16945         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16946         /* [HGM] time odds: correct new time quota for time odds! */
16947                                             / WhitePlayer()->timeOdds;
16948         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16949     } else {
16950         lastBlack -= blackTimeRemaining;
16951         /* Black made time control */
16952         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16953                                             / WhitePlayer()->other->timeOdds;
16954         lastWhite = whiteTimeRemaining;
16955     }
16956 }
16957
16958 void
16959 DisplayBothClocks ()
16960 {
16961     int wom = gameMode == EditPosition ?
16962       !blackPlaysFirst : WhiteOnMove(currentMove);
16963     DisplayWhiteClock(whiteTimeRemaining, wom);
16964     DisplayBlackClock(blackTimeRemaining, !wom);
16965 }
16966
16967
16968 /* Timekeeping seems to be a portability nightmare.  I think everyone
16969    has ftime(), but I'm really not sure, so I'm including some ifdefs
16970    to use other calls if you don't.  Clocks will be less accurate if
16971    you have neither ftime nor gettimeofday.
16972 */
16973
16974 /* VS 2008 requires the #include outside of the function */
16975 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16976 #include <sys/timeb.h>
16977 #endif
16978
16979 /* Get the current time as a TimeMark */
16980 void
16981 GetTimeMark (TimeMark *tm)
16982 {
16983 #if HAVE_GETTIMEOFDAY
16984
16985     struct timeval timeVal;
16986     struct timezone timeZone;
16987
16988     gettimeofday(&timeVal, &timeZone);
16989     tm->sec = (long) timeVal.tv_sec;
16990     tm->ms = (int) (timeVal.tv_usec / 1000L);
16991
16992 #else /*!HAVE_GETTIMEOFDAY*/
16993 #if HAVE_FTIME
16994
16995 // include <sys/timeb.h> / moved to just above start of function
16996     struct timeb timeB;
16997
16998     ftime(&timeB);
16999     tm->sec = (long) timeB.time;
17000     tm->ms = (int) timeB.millitm;
17001
17002 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17003     tm->sec = (long) time(NULL);
17004     tm->ms = 0;
17005 #endif
17006 #endif
17007 }
17008
17009 /* Return the difference in milliseconds between two
17010    time marks.  We assume the difference will fit in a long!
17011 */
17012 long
17013 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17014 {
17015     return 1000L*(tm2->sec - tm1->sec) +
17016            (long) (tm2->ms - tm1->ms);
17017 }
17018
17019
17020 /*
17021  * Code to manage the game clocks.
17022  *
17023  * In tournament play, black starts the clock and then white makes a move.
17024  * We give the human user a slight advantage if he is playing white---the
17025  * clocks don't run until he makes his first move, so it takes zero time.
17026  * Also, we don't account for network lag, so we could get out of sync
17027  * with GNU Chess's clock -- but then, referees are always right.
17028  */
17029
17030 static TimeMark tickStartTM;
17031 static long intendedTickLength;
17032
17033 long
17034 NextTickLength (long timeRemaining)
17035 {
17036     long nominalTickLength, nextTickLength;
17037
17038     if (timeRemaining > 0L && timeRemaining <= 10000L)
17039       nominalTickLength = 100L;
17040     else
17041       nominalTickLength = 1000L;
17042     nextTickLength = timeRemaining % nominalTickLength;
17043     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17044
17045     return nextTickLength;
17046 }
17047
17048 /* Adjust clock one minute up or down */
17049 void
17050 AdjustClock (Boolean which, int dir)
17051 {
17052     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17053     if(which) blackTimeRemaining += 60000*dir;
17054     else      whiteTimeRemaining += 60000*dir;
17055     DisplayBothClocks();
17056     adjustedClock = TRUE;
17057 }
17058
17059 /* Stop clocks and reset to a fresh time control */
17060 void
17061 ResetClocks ()
17062 {
17063     (void) StopClockTimer();
17064     if (appData.icsActive) {
17065         whiteTimeRemaining = blackTimeRemaining = 0;
17066     } else if (searchTime) {
17067         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17068         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17069     } else { /* [HGM] correct new time quote for time odds */
17070         whiteTC = blackTC = fullTimeControlString;
17071         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17072         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17073     }
17074     if (whiteFlag || blackFlag) {
17075         DisplayTitle("");
17076         whiteFlag = blackFlag = FALSE;
17077     }
17078     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17079     DisplayBothClocks();
17080     adjustedClock = FALSE;
17081 }
17082
17083 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17084
17085 /* Decrement running clock by amount of time that has passed */
17086 void
17087 DecrementClocks ()
17088 {
17089     long timeRemaining;
17090     long lastTickLength, fudge;
17091     TimeMark now;
17092
17093     if (!appData.clockMode) return;
17094     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17095
17096     GetTimeMark(&now);
17097
17098     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17099
17100     /* Fudge if we woke up a little too soon */
17101     fudge = intendedTickLength - lastTickLength;
17102     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17103
17104     if (WhiteOnMove(forwardMostMove)) {
17105         if(whiteNPS >= 0) lastTickLength = 0;
17106         timeRemaining = whiteTimeRemaining -= lastTickLength;
17107         if(timeRemaining < 0 && !appData.icsActive) {
17108             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17109             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17110                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17111                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17112             }
17113         }
17114         DisplayWhiteClock(whiteTimeRemaining - fudge,
17115                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17116     } else {
17117         if(blackNPS >= 0) lastTickLength = 0;
17118         timeRemaining = blackTimeRemaining -= lastTickLength;
17119         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17120             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17121             if(suddenDeath) {
17122                 blackStartMove = forwardMostMove;
17123                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17124             }
17125         }
17126         DisplayBlackClock(blackTimeRemaining - fudge,
17127                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17128     }
17129     if (CheckFlags()) return;
17130
17131     if(twoBoards) { // count down secondary board's clocks as well
17132         activePartnerTime -= lastTickLength;
17133         partnerUp = 1;
17134         if(activePartner == 'W')
17135             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17136         else
17137             DisplayBlackClock(activePartnerTime, TRUE);
17138         partnerUp = 0;
17139     }
17140
17141     tickStartTM = now;
17142     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17143     StartClockTimer(intendedTickLength);
17144
17145     /* if the time remaining has fallen below the alarm threshold, sound the
17146      * alarm. if the alarm has sounded and (due to a takeback or time control
17147      * with increment) the time remaining has increased to a level above the
17148      * threshold, reset the alarm so it can sound again.
17149      */
17150
17151     if (appData.icsActive && appData.icsAlarm) {
17152
17153         /* make sure we are dealing with the user's clock */
17154         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17155                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17156            )) return;
17157
17158         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17159             alarmSounded = FALSE;
17160         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17161             PlayAlarmSound();
17162             alarmSounded = TRUE;
17163         }
17164     }
17165 }
17166
17167
17168 /* A player has just moved, so stop the previously running
17169    clock and (if in clock mode) start the other one.
17170    We redisplay both clocks in case we're in ICS mode, because
17171    ICS gives us an update to both clocks after every move.
17172    Note that this routine is called *after* forwardMostMove
17173    is updated, so the last fractional tick must be subtracted
17174    from the color that is *not* on move now.
17175 */
17176 void
17177 SwitchClocks (int newMoveNr)
17178 {
17179     long lastTickLength;
17180     TimeMark now;
17181     int flagged = FALSE;
17182
17183     GetTimeMark(&now);
17184
17185     if (StopClockTimer() && appData.clockMode) {
17186         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17187         if (!WhiteOnMove(forwardMostMove)) {
17188             if(blackNPS >= 0) lastTickLength = 0;
17189             blackTimeRemaining -= lastTickLength;
17190            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17191 //         if(pvInfoList[forwardMostMove].time == -1)
17192                  pvInfoList[forwardMostMove].time =               // use GUI time
17193                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17194         } else {
17195            if(whiteNPS >= 0) lastTickLength = 0;
17196            whiteTimeRemaining -= lastTickLength;
17197            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17198 //         if(pvInfoList[forwardMostMove].time == -1)
17199                  pvInfoList[forwardMostMove].time =
17200                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17201         }
17202         flagged = CheckFlags();
17203     }
17204     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17205     CheckTimeControl();
17206
17207     if (flagged || !appData.clockMode) return;
17208
17209     switch (gameMode) {
17210       case MachinePlaysBlack:
17211       case MachinePlaysWhite:
17212       case BeginningOfGame:
17213         if (pausing) return;
17214         break;
17215
17216       case EditGame:
17217       case PlayFromGameFile:
17218       case IcsExamining:
17219         return;
17220
17221       default:
17222         break;
17223     }
17224
17225     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17226         if(WhiteOnMove(forwardMostMove))
17227              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17228         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17229     }
17230
17231     tickStartTM = now;
17232     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17233       whiteTimeRemaining : blackTimeRemaining);
17234     StartClockTimer(intendedTickLength);
17235 }
17236
17237
17238 /* Stop both clocks */
17239 void
17240 StopClocks ()
17241 {
17242     long lastTickLength;
17243     TimeMark now;
17244
17245     if (!StopClockTimer()) return;
17246     if (!appData.clockMode) return;
17247
17248     GetTimeMark(&now);
17249
17250     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17251     if (WhiteOnMove(forwardMostMove)) {
17252         if(whiteNPS >= 0) lastTickLength = 0;
17253         whiteTimeRemaining -= lastTickLength;
17254         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17255     } else {
17256         if(blackNPS >= 0) lastTickLength = 0;
17257         blackTimeRemaining -= lastTickLength;
17258         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17259     }
17260     CheckFlags();
17261 }
17262
17263 /* Start clock of player on move.  Time may have been reset, so
17264    if clock is already running, stop and restart it. */
17265 void
17266 StartClocks ()
17267 {
17268     (void) StopClockTimer(); /* in case it was running already */
17269     DisplayBothClocks();
17270     if (CheckFlags()) return;
17271
17272     if (!appData.clockMode) return;
17273     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17274
17275     GetTimeMark(&tickStartTM);
17276     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17277       whiteTimeRemaining : blackTimeRemaining);
17278
17279    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17280     whiteNPS = blackNPS = -1;
17281     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17282        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17283         whiteNPS = first.nps;
17284     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17285        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17286         blackNPS = first.nps;
17287     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17288         whiteNPS = second.nps;
17289     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17290         blackNPS = second.nps;
17291     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17292
17293     StartClockTimer(intendedTickLength);
17294 }
17295
17296 char *
17297 TimeString (long ms)
17298 {
17299     long second, minute, hour, day;
17300     char *sign = "";
17301     static char buf[32];
17302
17303     if (ms > 0 && ms <= 9900) {
17304       /* convert milliseconds to tenths, rounding up */
17305       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17306
17307       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17308       return buf;
17309     }
17310
17311     /* convert milliseconds to seconds, rounding up */
17312     /* use floating point to avoid strangeness of integer division
17313        with negative dividends on many machines */
17314     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17315
17316     if (second < 0) {
17317         sign = "-";
17318         second = -second;
17319     }
17320
17321     day = second / (60 * 60 * 24);
17322     second = second % (60 * 60 * 24);
17323     hour = second / (60 * 60);
17324     second = second % (60 * 60);
17325     minute = second / 60;
17326     second = second % 60;
17327
17328     if (day > 0)
17329       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17330               sign, day, hour, minute, second);
17331     else if (hour > 0)
17332       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17333     else
17334       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17335
17336     return buf;
17337 }
17338
17339
17340 /*
17341  * This is necessary because some C libraries aren't ANSI C compliant yet.
17342  */
17343 char *
17344 StrStr (char *string, char *match)
17345 {
17346     int i, length;
17347
17348     length = strlen(match);
17349
17350     for (i = strlen(string) - length; i >= 0; i--, string++)
17351       if (!strncmp(match, string, length))
17352         return string;
17353
17354     return NULL;
17355 }
17356
17357 char *
17358 StrCaseStr (char *string, char *match)
17359 {
17360     int i, j, length;
17361
17362     length = strlen(match);
17363
17364     for (i = strlen(string) - length; i >= 0; i--, string++) {
17365         for (j = 0; j < length; j++) {
17366             if (ToLower(match[j]) != ToLower(string[j]))
17367               break;
17368         }
17369         if (j == length) return string;
17370     }
17371
17372     return NULL;
17373 }
17374
17375 #ifndef _amigados
17376 int
17377 StrCaseCmp (char *s1, char *s2)
17378 {
17379     char c1, c2;
17380
17381     for (;;) {
17382         c1 = ToLower(*s1++);
17383         c2 = ToLower(*s2++);
17384         if (c1 > c2) return 1;
17385         if (c1 < c2) return -1;
17386         if (c1 == NULLCHAR) return 0;
17387     }
17388 }
17389
17390
17391 int
17392 ToLower (int c)
17393 {
17394     return isupper(c) ? tolower(c) : c;
17395 }
17396
17397
17398 int
17399 ToUpper (int c)
17400 {
17401     return islower(c) ? toupper(c) : c;
17402 }
17403 #endif /* !_amigados    */
17404
17405 char *
17406 StrSave (char *s)
17407 {
17408   char *ret;
17409
17410   if ((ret = (char *) malloc(strlen(s) + 1)))
17411     {
17412       safeStrCpy(ret, s, strlen(s)+1);
17413     }
17414   return ret;
17415 }
17416
17417 char *
17418 StrSavePtr (char *s, char **savePtr)
17419 {
17420     if (*savePtr) {
17421         free(*savePtr);
17422     }
17423     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17424       safeStrCpy(*savePtr, s, strlen(s)+1);
17425     }
17426     return(*savePtr);
17427 }
17428
17429 char *
17430 PGNDate ()
17431 {
17432     time_t clock;
17433     struct tm *tm;
17434     char buf[MSG_SIZ];
17435
17436     clock = time((time_t *)NULL);
17437     tm = localtime(&clock);
17438     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17439             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17440     return StrSave(buf);
17441 }
17442
17443
17444 char *
17445 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17446 {
17447     int i, j, fromX, fromY, toX, toY;
17448     int whiteToPlay;
17449     char buf[MSG_SIZ];
17450     char *p, *q;
17451     int emptycount;
17452     ChessSquare piece;
17453
17454     whiteToPlay = (gameMode == EditPosition) ?
17455       !blackPlaysFirst : (move % 2 == 0);
17456     p = buf;
17457
17458     /* Piece placement data */
17459     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17460         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17461         emptycount = 0;
17462         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17463             if (boards[move][i][j] == EmptySquare) {
17464                 emptycount++;
17465             } else { ChessSquare piece = boards[move][i][j];
17466                 if (emptycount > 0) {
17467                     if(emptycount<10) /* [HGM] can be >= 10 */
17468                         *p++ = '0' + emptycount;
17469                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17470                     emptycount = 0;
17471                 }
17472                 if(PieceToChar(piece) == '+') {
17473                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17474                     *p++ = '+';
17475                     piece = (ChessSquare)(DEMOTED piece);
17476                 }
17477                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17478                 if(p[-1] == '~') {
17479                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17480                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17481                     *p++ = '~';
17482                 }
17483             }
17484         }
17485         if (emptycount > 0) {
17486             if(emptycount<10) /* [HGM] can be >= 10 */
17487                 *p++ = '0' + emptycount;
17488             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17489             emptycount = 0;
17490         }
17491         *p++ = '/';
17492     }
17493     *(p - 1) = ' ';
17494
17495     /* [HGM] print Crazyhouse or Shogi holdings */
17496     if( gameInfo.holdingsWidth ) {
17497         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17498         q = p;
17499         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17500             piece = boards[move][i][BOARD_WIDTH-1];
17501             if( piece != EmptySquare )
17502               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17503                   *p++ = PieceToChar(piece);
17504         }
17505         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17506             piece = boards[move][BOARD_HEIGHT-i-1][0];
17507             if( piece != EmptySquare )
17508               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17509                   *p++ = PieceToChar(piece);
17510         }
17511
17512         if( q == p ) *p++ = '-';
17513         *p++ = ']';
17514         *p++ = ' ';
17515     }
17516
17517     /* Active color */
17518     *p++ = whiteToPlay ? 'w' : 'b';
17519     *p++ = ' ';
17520
17521   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17522     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17523   } else {
17524   if(nrCastlingRights) {
17525      q = p;
17526      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17527        /* [HGM] write directly from rights */
17528            if(boards[move][CASTLING][2] != NoRights &&
17529               boards[move][CASTLING][0] != NoRights   )
17530                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17531            if(boards[move][CASTLING][2] != NoRights &&
17532               boards[move][CASTLING][1] != NoRights   )
17533                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17534            if(boards[move][CASTLING][5] != NoRights &&
17535               boards[move][CASTLING][3] != NoRights   )
17536                 *p++ = boards[move][CASTLING][3] + AAA;
17537            if(boards[move][CASTLING][5] != NoRights &&
17538               boards[move][CASTLING][4] != NoRights   )
17539                 *p++ = boards[move][CASTLING][4] + AAA;
17540      } else {
17541
17542         /* [HGM] write true castling rights */
17543         if( nrCastlingRights == 6 ) {
17544             int q, k=0;
17545             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17546                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17547             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17548                  boards[move][CASTLING][2] != NoRights  );
17549             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17550                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17551                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17552                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17553                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17554             }
17555             if(q) *p++ = 'Q';
17556             k = 0;
17557             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17558                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17559             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17560                  boards[move][CASTLING][5] != NoRights  );
17561             if(gameInfo.variant == VariantSChess) {
17562                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17563                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17564                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17565                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17566             }
17567             if(q) *p++ = 'q';
17568         }
17569      }
17570      if (q == p) *p++ = '-'; /* No castling rights */
17571      *p++ = ' ';
17572   }
17573
17574   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17575      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17576      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17577     /* En passant target square */
17578     if (move > backwardMostMove) {
17579         fromX = moveList[move - 1][0] - AAA;
17580         fromY = moveList[move - 1][1] - ONE;
17581         toX = moveList[move - 1][2] - AAA;
17582         toY = moveList[move - 1][3] - ONE;
17583         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17584             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17585             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17586             fromX == toX) {
17587             /* 2-square pawn move just happened */
17588             *p++ = toX + AAA;
17589             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17590         } else {
17591             *p++ = '-';
17592         }
17593     } else if(move == backwardMostMove) {
17594         // [HGM] perhaps we should always do it like this, and forget the above?
17595         if((signed char)boards[move][EP_STATUS] >= 0) {
17596             *p++ = boards[move][EP_STATUS] + AAA;
17597             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17598         } else {
17599             *p++ = '-';
17600         }
17601     } else {
17602         *p++ = '-';
17603     }
17604     *p++ = ' ';
17605   }
17606   }
17607
17608     if(moveCounts)
17609     {   int i = 0, j=move;
17610
17611         /* [HGM] find reversible plies */
17612         if (appData.debugMode) { int k;
17613             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17614             for(k=backwardMostMove; k<=forwardMostMove; k++)
17615                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17616
17617         }
17618
17619         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17620         if( j == backwardMostMove ) i += initialRulePlies;
17621         sprintf(p, "%d ", i);
17622         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17623
17624         /* Fullmove number */
17625         sprintf(p, "%d", (move / 2) + 1);
17626     } else *--p = NULLCHAR;
17627
17628     return StrSave(buf);
17629 }
17630
17631 Boolean
17632 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17633 {
17634     int i, j, k, w=0;
17635     char *p, c;
17636     int emptycount, virgin[BOARD_FILES];
17637     ChessSquare piece;
17638
17639     p = fen;
17640
17641     /* Piece placement data */
17642     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17643         j = 0;
17644         for (;;) {
17645             if (*p == '/' || *p == ' ' || *p == '[' ) {
17646                 if(j > w) w = j;
17647                 emptycount = gameInfo.boardWidth - j;
17648                 while (emptycount--)
17649                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17650                 if (*p == '/') p++;
17651                 else if(autoSize) { // we stumbled unexpectedly into end of board
17652                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17653                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17654                     }
17655                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17656                 }
17657                 break;
17658 #if(BOARD_FILES >= 10)
17659             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17660                 p++; emptycount=10;
17661                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17662                 while (emptycount--)
17663                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17664 #endif
17665             } else if (*p == '*') {
17666                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17667             } else if (isdigit(*p)) {
17668                 emptycount = *p++ - '0';
17669                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17670                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17671                 while (emptycount--)
17672                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17673             } else if (*p == '+' || isalpha(*p)) {
17674                 if (j >= gameInfo.boardWidth) return FALSE;
17675                 if(*p=='+') {
17676                     piece = CharToPiece(*++p);
17677                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17678                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17679                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17680                 } else piece = CharToPiece(*p++);
17681
17682                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17683                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17684                     piece = (ChessSquare) (PROMOTED piece);
17685                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17686                     p++;
17687                 }
17688                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17689             } else {
17690                 return FALSE;
17691             }
17692         }
17693     }
17694     while (*p == '/' || *p == ' ') p++;
17695
17696     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17697
17698     /* [HGM] by default clear Crazyhouse holdings, if present */
17699     if(gameInfo.holdingsWidth) {
17700        for(i=0; i<BOARD_HEIGHT; i++) {
17701            board[i][0]             = EmptySquare; /* black holdings */
17702            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17703            board[i][1]             = (ChessSquare) 0; /* black counts */
17704            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17705        }
17706     }
17707
17708     /* [HGM] look for Crazyhouse holdings here */
17709     while(*p==' ') p++;
17710     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17711         if(*p == '[') p++;
17712         if(*p == '-' ) p++; /* empty holdings */ else {
17713             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17714             /* if we would allow FEN reading to set board size, we would   */
17715             /* have to add holdings and shift the board read so far here   */
17716             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17717                 p++;
17718                 if((int) piece >= (int) BlackPawn ) {
17719                     i = (int)piece - (int)BlackPawn;
17720                     i = PieceToNumber((ChessSquare)i);
17721                     if( i >= gameInfo.holdingsSize ) return FALSE;
17722                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17723                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17724                 } else {
17725                     i = (int)piece - (int)WhitePawn;
17726                     i = PieceToNumber((ChessSquare)i);
17727                     if( i >= gameInfo.holdingsSize ) return FALSE;
17728                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17729                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17730                 }
17731             }
17732         }
17733         if(*p == ']') p++;
17734     }
17735
17736     while(*p == ' ') p++;
17737
17738     /* Active color */
17739     c = *p++;
17740     if(appData.colorNickNames) {
17741       if( c == appData.colorNickNames[0] ) c = 'w'; else
17742       if( c == appData.colorNickNames[1] ) c = 'b';
17743     }
17744     switch (c) {
17745       case 'w':
17746         *blackPlaysFirst = FALSE;
17747         break;
17748       case 'b':
17749         *blackPlaysFirst = TRUE;
17750         break;
17751       default:
17752         return FALSE;
17753     }
17754
17755     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17756     /* return the extra info in global variiables             */
17757
17758     /* set defaults in case FEN is incomplete */
17759     board[EP_STATUS] = EP_UNKNOWN;
17760     for(i=0; i<nrCastlingRights; i++ ) {
17761         board[CASTLING][i] =
17762             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17763     }   /* assume possible unless obviously impossible */
17764     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17765     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17766     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17767                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17768     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17769     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17770     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17771                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17772     FENrulePlies = 0;
17773
17774     while(*p==' ') p++;
17775     if(nrCastlingRights) {
17776       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17777       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17778           /* castling indicator present, so default becomes no castlings */
17779           for(i=0; i<nrCastlingRights; i++ ) {
17780                  board[CASTLING][i] = NoRights;
17781           }
17782       }
17783       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17784              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17785              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17786              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17787         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17788
17789         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17790             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17791             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17792         }
17793         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17794             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17795         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17796                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17797         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17798                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17799         switch(c) {
17800           case'K':
17801               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17802               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17803               board[CASTLING][2] = whiteKingFile;
17804               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17805               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17806               break;
17807           case'Q':
17808               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17809               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17810               board[CASTLING][2] = whiteKingFile;
17811               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17812               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17813               break;
17814           case'k':
17815               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17816               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17817               board[CASTLING][5] = blackKingFile;
17818               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17819               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17820               break;
17821           case'q':
17822               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17823               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17824               board[CASTLING][5] = blackKingFile;
17825               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17826               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17827           case '-':
17828               break;
17829           default: /* FRC castlings */
17830               if(c >= 'a') { /* black rights */
17831                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17832                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17833                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17834                   if(i == BOARD_RGHT) break;
17835                   board[CASTLING][5] = i;
17836                   c -= AAA;
17837                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17838                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17839                   if(c > i)
17840                       board[CASTLING][3] = c;
17841                   else
17842                       board[CASTLING][4] = c;
17843               } else { /* white rights */
17844                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17845                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17846                     if(board[0][i] == WhiteKing) break;
17847                   if(i == BOARD_RGHT) break;
17848                   board[CASTLING][2] = i;
17849                   c -= AAA - 'a' + 'A';
17850                   if(board[0][c] >= WhiteKing) break;
17851                   if(c > i)
17852                       board[CASTLING][0] = c;
17853                   else
17854                       board[CASTLING][1] = c;
17855               }
17856         }
17857       }
17858       for(i=0; i<nrCastlingRights; i++)
17859         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17860       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17861     if (appData.debugMode) {
17862         fprintf(debugFP, "FEN castling rights:");
17863         for(i=0; i<nrCastlingRights; i++)
17864         fprintf(debugFP, " %d", board[CASTLING][i]);
17865         fprintf(debugFP, "\n");
17866     }
17867
17868       while(*p==' ') p++;
17869     }
17870
17871     /* read e.p. field in games that know e.p. capture */
17872     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17873        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17874        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17875       if(*p=='-') {
17876         p++; board[EP_STATUS] = EP_NONE;
17877       } else {
17878          char c = *p++ - AAA;
17879
17880          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17881          if(*p >= '0' && *p <='9') p++;
17882          board[EP_STATUS] = c;
17883       }
17884     }
17885
17886
17887     if(sscanf(p, "%d", &i) == 1) {
17888         FENrulePlies = i; /* 50-move ply counter */
17889         /* (The move number is still ignored)    */
17890     }
17891
17892     return TRUE;
17893 }
17894
17895 void
17896 EditPositionPasteFEN (char *fen)
17897 {
17898   if (fen != NULL) {
17899     Board initial_position;
17900
17901     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17902       DisplayError(_("Bad FEN position in clipboard"), 0);
17903       return ;
17904     } else {
17905       int savedBlackPlaysFirst = blackPlaysFirst;
17906       EditPositionEvent();
17907       blackPlaysFirst = savedBlackPlaysFirst;
17908       CopyBoard(boards[0], initial_position);
17909       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17910       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17911       DisplayBothClocks();
17912       DrawPosition(FALSE, boards[currentMove]);
17913     }
17914   }
17915 }
17916
17917 static char cseq[12] = "\\   ";
17918
17919 Boolean
17920 set_cont_sequence (char *new_seq)
17921 {
17922     int len;
17923     Boolean ret;
17924
17925     // handle bad attempts to set the sequence
17926         if (!new_seq)
17927                 return 0; // acceptable error - no debug
17928
17929     len = strlen(new_seq);
17930     ret = (len > 0) && (len < sizeof(cseq));
17931     if (ret)
17932       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17933     else if (appData.debugMode)
17934       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17935     return ret;
17936 }
17937
17938 /*
17939     reformat a source message so words don't cross the width boundary.  internal
17940     newlines are not removed.  returns the wrapped size (no null character unless
17941     included in source message).  If dest is NULL, only calculate the size required
17942     for the dest buffer.  lp argument indicats line position upon entry, and it's
17943     passed back upon exit.
17944 */
17945 int
17946 wrap (char *dest, char *src, int count, int width, int *lp)
17947 {
17948     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17949
17950     cseq_len = strlen(cseq);
17951     old_line = line = *lp;
17952     ansi = len = clen = 0;
17953
17954     for (i=0; i < count; i++)
17955     {
17956         if (src[i] == '\033')
17957             ansi = 1;
17958
17959         // if we hit the width, back up
17960         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17961         {
17962             // store i & len in case the word is too long
17963             old_i = i, old_len = len;
17964
17965             // find the end of the last word
17966             while (i && src[i] != ' ' && src[i] != '\n')
17967             {
17968                 i--;
17969                 len--;
17970             }
17971
17972             // word too long?  restore i & len before splitting it
17973             if ((old_i-i+clen) >= width)
17974             {
17975                 i = old_i;
17976                 len = old_len;
17977             }
17978
17979             // extra space?
17980             if (i && src[i-1] == ' ')
17981                 len--;
17982
17983             if (src[i] != ' ' && src[i] != '\n')
17984             {
17985                 i--;
17986                 if (len)
17987                     len--;
17988             }
17989
17990             // now append the newline and continuation sequence
17991             if (dest)
17992                 dest[len] = '\n';
17993             len++;
17994             if (dest)
17995                 strncpy(dest+len, cseq, cseq_len);
17996             len += cseq_len;
17997             line = cseq_len;
17998             clen = cseq_len;
17999             continue;
18000         }
18001
18002         if (dest)
18003             dest[len] = src[i];
18004         len++;
18005         if (!ansi)
18006             line++;
18007         if (src[i] == '\n')
18008             line = 0;
18009         if (src[i] == 'm')
18010             ansi = 0;
18011     }
18012     if (dest && appData.debugMode)
18013     {
18014         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18015             count, width, line, len, *lp);
18016         show_bytes(debugFP, src, count);
18017         fprintf(debugFP, "\ndest: ");
18018         show_bytes(debugFP, dest, len);
18019         fprintf(debugFP, "\n");
18020     }
18021     *lp = dest ? line : old_line;
18022
18023     return len;
18024 }
18025
18026 // [HGM] vari: routines for shelving variations
18027 Boolean modeRestore = FALSE;
18028
18029 void
18030 PushInner (int firstMove, int lastMove)
18031 {
18032         int i, j, nrMoves = lastMove - firstMove;
18033
18034         // push current tail of game on stack
18035         savedResult[storedGames] = gameInfo.result;
18036         savedDetails[storedGames] = gameInfo.resultDetails;
18037         gameInfo.resultDetails = NULL;
18038         savedFirst[storedGames] = firstMove;
18039         savedLast [storedGames] = lastMove;
18040         savedFramePtr[storedGames] = framePtr;
18041         framePtr -= nrMoves; // reserve space for the boards
18042         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18043             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18044             for(j=0; j<MOVE_LEN; j++)
18045                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18046             for(j=0; j<2*MOVE_LEN; j++)
18047                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18048             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18049             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18050             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18051             pvInfoList[firstMove+i-1].depth = 0;
18052             commentList[framePtr+i] = commentList[firstMove+i];
18053             commentList[firstMove+i] = NULL;
18054         }
18055
18056         storedGames++;
18057         forwardMostMove = firstMove; // truncate game so we can start variation
18058 }
18059
18060 void
18061 PushTail (int firstMove, int lastMove)
18062 {
18063         if(appData.icsActive) { // only in local mode
18064                 forwardMostMove = currentMove; // mimic old ICS behavior
18065                 return;
18066         }
18067         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18068
18069         PushInner(firstMove, lastMove);
18070         if(storedGames == 1) GreyRevert(FALSE);
18071         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18072 }
18073
18074 void
18075 PopInner (Boolean annotate)
18076 {
18077         int i, j, nrMoves;
18078         char buf[8000], moveBuf[20];
18079
18080         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18081         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18082         nrMoves = savedLast[storedGames] - currentMove;
18083         if(annotate) {
18084                 int cnt = 10;
18085                 if(!WhiteOnMove(currentMove))
18086                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18087                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18088                 for(i=currentMove; i<forwardMostMove; i++) {
18089                         if(WhiteOnMove(i))
18090                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18091                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18092                         strcat(buf, moveBuf);
18093                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18094                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18095                 }
18096                 strcat(buf, ")");
18097         }
18098         for(i=1; i<=nrMoves; i++) { // copy last variation back
18099             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18100             for(j=0; j<MOVE_LEN; j++)
18101                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18102             for(j=0; j<2*MOVE_LEN; j++)
18103                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18104             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18105             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18106             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18107             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18108             commentList[currentMove+i] = commentList[framePtr+i];
18109             commentList[framePtr+i] = NULL;
18110         }
18111         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18112         framePtr = savedFramePtr[storedGames];
18113         gameInfo.result = savedResult[storedGames];
18114         if(gameInfo.resultDetails != NULL) {
18115             free(gameInfo.resultDetails);
18116       }
18117         gameInfo.resultDetails = savedDetails[storedGames];
18118         forwardMostMove = currentMove + nrMoves;
18119 }
18120
18121 Boolean
18122 PopTail (Boolean annotate)
18123 {
18124         if(appData.icsActive) return FALSE; // only in local mode
18125         if(!storedGames) return FALSE; // sanity
18126         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18127
18128         PopInner(annotate);
18129         if(currentMove < forwardMostMove) ForwardEvent(); else
18130         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18131
18132         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18133         return TRUE;
18134 }
18135
18136 void
18137 CleanupTail ()
18138 {       // remove all shelved variations
18139         int i;
18140         for(i=0; i<storedGames; i++) {
18141             if(savedDetails[i])
18142                 free(savedDetails[i]);
18143             savedDetails[i] = NULL;
18144         }
18145         for(i=framePtr; i<MAX_MOVES; i++) {
18146                 if(commentList[i]) free(commentList[i]);
18147                 commentList[i] = NULL;
18148         }
18149         framePtr = MAX_MOVES-1;
18150         storedGames = 0;
18151 }
18152
18153 void
18154 LoadVariation (int index, char *text)
18155 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18156         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18157         int level = 0, move;
18158
18159         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18160         // first find outermost bracketing variation
18161         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18162             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18163                 if(*p == '{') wait = '}'; else
18164                 if(*p == '[') wait = ']'; else
18165                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18166                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18167             }
18168             if(*p == wait) wait = NULLCHAR; // closing ]} found
18169             p++;
18170         }
18171         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18172         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18173         end[1] = NULLCHAR; // clip off comment beyond variation
18174         ToNrEvent(currentMove-1);
18175         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18176         // kludge: use ParsePV() to append variation to game
18177         move = currentMove;
18178         ParsePV(start, TRUE, TRUE);
18179         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18180         ClearPremoveHighlights();
18181         CommentPopDown();
18182         ToNrEvent(currentMove+1);
18183 }
18184
18185 void
18186 LoadTheme ()
18187 {
18188     char *p, *q, buf[MSG_SIZ];
18189     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18190         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18191         ParseArgsFromString(buf);
18192         ActivateTheme(TRUE); // also redo colors
18193         return;
18194     }
18195     p = nickName;
18196     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18197     {
18198         int len;
18199         q = appData.themeNames;
18200         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18201       if(appData.useBitmaps) {
18202         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18203                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18204                 appData.liteBackTextureMode,
18205                 appData.darkBackTextureMode );
18206       } else {
18207         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18208                 Col2Text(2),   // lightSquareColor
18209                 Col2Text(3) ); // darkSquareColor
18210       }
18211       if(appData.useBorder) {
18212         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18213                 appData.border);
18214       } else {
18215         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18216       }
18217       if(appData.useFont) {
18218         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18219                 appData.renderPiecesWithFont,
18220                 appData.fontToPieceTable,
18221                 Col2Text(9),    // appData.fontBackColorWhite
18222                 Col2Text(10) ); // appData.fontForeColorBlack
18223       } else {
18224         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18225                 appData.pieceDirectory);
18226         if(!appData.pieceDirectory[0])
18227           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18228                 Col2Text(0),   // whitePieceColor
18229                 Col2Text(1) ); // blackPieceColor
18230       }
18231       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18232                 Col2Text(4),   // highlightSquareColor
18233                 Col2Text(5) ); // premoveHighlightColor
18234         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18235         if(insert != q) insert[-1] = NULLCHAR;
18236         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18237         if(q)   free(q);
18238     }
18239     ActivateTheme(FALSE);
18240 }