Fix syntax error in bitbase code
[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, 2014 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 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   case VariantChu:
417   case VariantChuChess:
418   case VariantLion:
419     flags |= F_NULL_MOVE;
420     break;
421   default:
422     break;
423   }
424   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
425   return flags;
426 }
427
428 FILE *gameFileFP, *debugFP, *serverFP;
429 char *currentDebugFile; // [HGM] debug split: to remember name
430
431 /*
432     [AS] Note: sometimes, the sscanf() function is used to parse the input
433     into a fixed-size buffer. Because of this, we must be prepared to
434     receive strings as long as the size of the input buffer, which is currently
435     set to 4K for Windows and 8K for the rest.
436     So, we must either allocate sufficiently large buffers here, or
437     reduce the size of the input buffer in the input reading part.
438 */
439
440 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
441 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
442 char thinkOutput1[MSG_SIZ*10];
443
444 ChessProgramState first, second, pairing;
445
446 /* premove variables */
447 int premoveToX = 0;
448 int premoveToY = 0;
449 int premoveFromX = 0;
450 int premoveFromY = 0;
451 int premovePromoChar = 0;
452 int gotPremove = 0;
453 Boolean alarmSounded;
454 /* end premove variables */
455
456 char *ics_prefix = "$";
457 enum ICS_TYPE ics_type = ICS_GENERIC;
458
459 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
460 int pauseExamForwardMostMove = 0;
461 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
462 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
463 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
464 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
465 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
466 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
467 int whiteFlag = FALSE, blackFlag = FALSE;
468 int userOfferedDraw = FALSE;
469 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
470 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
471 int cmailMoveType[CMAIL_MAX_GAMES];
472 long ics_clock_paused = 0;
473 ProcRef icsPR = NoProc, cmailPR = NoProc;
474 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
475 GameMode gameMode = BeginningOfGame;
476 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
477 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
478 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
479 int hiddenThinkOutputState = 0; /* [AS] */
480 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
481 int adjudicateLossPlies = 6;
482 char white_holding[64], black_holding[64];
483 TimeMark lastNodeCountTime;
484 long lastNodeCount=0;
485 int shiftKey, controlKey; // [HGM] set by mouse handler
486
487 int have_sent_ICS_logon = 0;
488 int movesPerSession;
489 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
490 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
491 Boolean adjustedClock;
492 long timeControl_2; /* [AS] Allow separate time controls */
493 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
494 long timeRemaining[2][MAX_MOVES];
495 int matchGame = 0, nextGame = 0, roundNr = 0;
496 Boolean waitingForGame = FALSE, startingEngine = FALSE;
497 TimeMark programStartTime, pauseStart;
498 char ics_handle[MSG_SIZ];
499 int have_set_title = 0;
500
501 /* animateTraining preserves the state of appData.animate
502  * when Training mode is activated. This allows the
503  * response to be animated when appData.animate == TRUE and
504  * appData.animateDragging == TRUE.
505  */
506 Boolean animateTraining;
507
508 GameInfo gameInfo;
509
510 AppData appData;
511
512 Board boards[MAX_MOVES];
513 /* [HGM] Following 7 needed for accurate legality tests: */
514 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
515 signed char  initialRights[BOARD_FILES];
516 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
517 int   initialRulePlies, FENrulePlies;
518 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
519 int loadFlag = 0;
520 Boolean shuffleOpenings;
521 int mute; // mute all sounds
522
523 // [HGM] vari: next 12 to save and restore variations
524 #define MAX_VARIATIONS 10
525 int framePtr = MAX_MOVES-1; // points to free stack entry
526 int storedGames = 0;
527 int savedFirst[MAX_VARIATIONS];
528 int savedLast[MAX_VARIATIONS];
529 int savedFramePtr[MAX_VARIATIONS];
530 char *savedDetails[MAX_VARIATIONS];
531 ChessMove savedResult[MAX_VARIATIONS];
532
533 void PushTail P((int firstMove, int lastMove));
534 Boolean PopTail P((Boolean annotate));
535 void PushInner P((int firstMove, int lastMove));
536 void PopInner P((Boolean annotate));
537 void CleanupTail P((void));
538
539 ChessSquare  FIDEArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543         BlackKing, BlackBishop, BlackKnight, BlackRook }
544 };
545
546 ChessSquare twoKingsArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
550         BlackKing, BlackKing, BlackKnight, BlackRook }
551 };
552
553 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
555         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
556     { BlackRook, BlackMan, BlackBishop, BlackQueen,
557         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
558 };
559
560 ChessSquare SpartanArray[2][BOARD_FILES] = {
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
564         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
565 };
566
567 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
569         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
571         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
572 };
573
574 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
576         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
578         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
583         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackMan, BlackFerz,
585         BlackKing, BlackMan, BlackKnight, BlackRook }
586 };
587
588 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
589     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
590         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackMan, BlackFerz,
592         BlackKing, BlackMan, BlackKnight, BlackRook }
593 };
594
595 ChessSquare  lionArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
597         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackLion, BlackBishop, BlackQueen,
599         BlackKing, BlackBishop, BlackKnight, BlackRook }
600 };
601
602
603 #if (BOARD_FILES>=10)
604 ChessSquare ShogiArray[2][BOARD_FILES] = {
605     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
606         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
607     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
608         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
609 };
610
611 ChessSquare XiangqiArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
613         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
615         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
616 };
617
618 ChessSquare CapablancaArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
620         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
622         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
623 };
624
625 ChessSquare GreatArray[2][BOARD_FILES] = {
626     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
627         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
628     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
629         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
630 };
631
632 ChessSquare JanusArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
634         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
635     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
636         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
637 };
638
639 ChessSquare GrandArray[2][BOARD_FILES] = {
640     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
641         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
642     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
643         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
644 };
645
646 ChessSquare ChuChessArray[2][BOARD_FILES] = {
647     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
648         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
649     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
650         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
651 };
652
653 #ifdef GOTHIC
654 ChessSquare GothicArray[2][BOARD_FILES] = {
655     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
656         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
657     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
658         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
659 };
660 #else // !GOTHIC
661 #define GothicArray CapablancaArray
662 #endif // !GOTHIC
663
664 #ifdef FALCON
665 ChessSquare FalconArray[2][BOARD_FILES] = {
666     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
667         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
668     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
669         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
670 };
671 #else // !FALCON
672 #define FalconArray CapablancaArray
673 #endif // !FALCON
674
675 #else // !(BOARD_FILES>=10)
676 #define XiangqiPosition FIDEArray
677 #define CapablancaArray FIDEArray
678 #define GothicArray FIDEArray
679 #define GreatArray FIDEArray
680 #endif // !(BOARD_FILES>=10)
681
682 #if (BOARD_FILES>=12)
683 ChessSquare CourierArray[2][BOARD_FILES] = {
684     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
685         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
686     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
687         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
688 };
689 ChessSquare ChuArray[6][BOARD_FILES] = {
690     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
691       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
692     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
693       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
694     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
695       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
696     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
697       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
698     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
699       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
700     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
701       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
702 };
703 #else // !(BOARD_FILES>=12)
704 #define CourierArray CapablancaArray
705 #define ChuArray CapablancaArray
706 #endif // !(BOARD_FILES>=12)
707
708
709 Board initialPosition;
710
711
712 /* Convert str to a rating. Checks for special cases of "----",
713
714    "++++", etc. Also strips ()'s */
715 int
716 string_to_rating (char *str)
717 {
718   while(*str && !isdigit(*str)) ++str;
719   if (!*str)
720     return 0;   /* One of the special "no rating" cases */
721   else
722     return atoi(str);
723 }
724
725 void
726 ClearProgramStats ()
727 {
728     /* Init programStats */
729     programStats.movelist[0] = 0;
730     programStats.depth = 0;
731     programStats.nr_moves = 0;
732     programStats.moves_left = 0;
733     programStats.nodes = 0;
734     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
735     programStats.score = 0;
736     programStats.got_only_move = 0;
737     programStats.got_fail = 0;
738     programStats.line_is_book = 0;
739 }
740
741 void
742 CommonEngineInit ()
743 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
744     if (appData.firstPlaysBlack) {
745         first.twoMachinesColor = "black\n";
746         second.twoMachinesColor = "white\n";
747     } else {
748         first.twoMachinesColor = "white\n";
749         second.twoMachinesColor = "black\n";
750     }
751
752     first.other = &second;
753     second.other = &first;
754
755     { float norm = 1;
756         if(appData.timeOddsMode) {
757             norm = appData.timeOdds[0];
758             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
759         }
760         first.timeOdds  = appData.timeOdds[0]/norm;
761         second.timeOdds = appData.timeOdds[1]/norm;
762     }
763
764     if(programVersion) free(programVersion);
765     if (appData.noChessProgram) {
766         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
767         sprintf(programVersion, "%s", PACKAGE_STRING);
768     } else {
769       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
770       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
771       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
772     }
773 }
774
775 void
776 UnloadEngine (ChessProgramState *cps)
777 {
778         /* Kill off first chess program */
779         if (cps->isr != NULL)
780           RemoveInputSource(cps->isr);
781         cps->isr = NULL;
782
783         if (cps->pr != NoProc) {
784             ExitAnalyzeMode();
785             DoSleep( appData.delayBeforeQuit );
786             SendToProgram("quit\n", cps);
787             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
788         }
789         cps->pr = NoProc;
790         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
791 }
792
793 void
794 ClearOptions (ChessProgramState *cps)
795 {
796     int i;
797     cps->nrOptions = cps->comboCnt = 0;
798     for(i=0; i<MAX_OPTIONS; i++) {
799         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
800         cps->option[i].textValue = 0;
801     }
802 }
803
804 char *engineNames[] = {
805   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
806      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
807 N_("first"),
808   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("second")
811 };
812
813 void
814 InitEngine (ChessProgramState *cps, int n)
815 {   // [HGM] all engine initialiation put in a function that does one engine
816
817     ClearOptions(cps);
818
819     cps->which = engineNames[n];
820     cps->maybeThinking = FALSE;
821     cps->pr = NoProc;
822     cps->isr = NULL;
823     cps->sendTime = 2;
824     cps->sendDrawOffers = 1;
825
826     cps->program = appData.chessProgram[n];
827     cps->host = appData.host[n];
828     cps->dir = appData.directory[n];
829     cps->initString = appData.engInitString[n];
830     cps->computerString = appData.computerString[n];
831     cps->useSigint  = TRUE;
832     cps->useSigterm = TRUE;
833     cps->reuse = appData.reuse[n];
834     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
835     cps->useSetboard = FALSE;
836     cps->useSAN = FALSE;
837     cps->usePing = FALSE;
838     cps->lastPing = 0;
839     cps->lastPong = 0;
840     cps->usePlayother = FALSE;
841     cps->useColors = TRUE;
842     cps->useUsermove = FALSE;
843     cps->sendICS = FALSE;
844     cps->sendName = appData.icsActive;
845     cps->sdKludge = FALSE;
846     cps->stKludge = FALSE;
847     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
848     TidyProgramName(cps->program, cps->host, cps->tidy);
849     cps->matchWins = 0;
850     ASSIGN(cps->variants, appData.variant);
851     cps->analysisSupport = 2; /* detect */
852     cps->analyzing = FALSE;
853     cps->initDone = FALSE;
854     cps->reload = FALSE;
855     cps->pseudo = appData.pseudo[n];
856
857     /* New features added by Tord: */
858     cps->useFEN960 = FALSE;
859     cps->useOOCastle = TRUE;
860     /* End of new features added by Tord. */
861     cps->fenOverride  = appData.fenOverride[n];
862
863     /* [HGM] time odds: set factor for each machine */
864     cps->timeOdds  = appData.timeOdds[n];
865
866     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
867     cps->accumulateTC = appData.accumulateTC[n];
868     cps->maxNrOfSessions = 1;
869
870     /* [HGM] debug */
871     cps->debug = FALSE;
872
873     cps->drawDepth = appData.drawDepth[n];
874     cps->supportsNPS = UNKNOWN;
875     cps->memSize = FALSE;
876     cps->maxCores = FALSE;
877     ASSIGN(cps->egtFormats, "");
878
879     /* [HGM] options */
880     cps->optionSettings  = appData.engOptions[n];
881
882     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
883     cps->isUCI = appData.isUCI[n]; /* [AS] */
884     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
885     cps->highlight = 0;
886
887     if (appData.protocolVersion[n] > PROTOVER
888         || appData.protocolVersion[n] < 1)
889       {
890         char buf[MSG_SIZ];
891         int len;
892
893         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
894                        appData.protocolVersion[n]);
895         if( (len >= MSG_SIZ) && appData.debugMode )
896           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
897
898         DisplayFatalError(buf, 0, 2);
899       }
900     else
901       {
902         cps->protocolVersion = appData.protocolVersion[n];
903       }
904
905     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
906     ParseFeatures(appData.featureDefaults, cps);
907 }
908
909 ChessProgramState *savCps;
910
911 GameMode oldMode;
912
913 void
914 LoadEngine ()
915 {
916     int i;
917     if(WaitForEngine(savCps, LoadEngine)) return;
918     CommonEngineInit(); // recalculate time odds
919     if(gameInfo.variant != StringToVariant(appData.variant)) {
920         // we changed variant when loading the engine; this forces us to reset
921         Reset(TRUE, savCps != &first);
922         oldMode = BeginningOfGame; // to prevent restoring old mode
923     }
924     InitChessProgram(savCps, FALSE);
925     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
926     DisplayMessage("", "");
927     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
928     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
929     ThawUI();
930     SetGNUMode();
931     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
932 }
933
934 void
935 ReplaceEngine (ChessProgramState *cps, int n)
936 {
937     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
938     keepInfo = 1;
939     if(oldMode != BeginningOfGame) EditGameEvent();
940     keepInfo = 0;
941     UnloadEngine(cps);
942     appData.noChessProgram = FALSE;
943     appData.clockMode = TRUE;
944     InitEngine(cps, n);
945     UpdateLogos(TRUE);
946     if(n) return; // only startup first engine immediately; second can wait
947     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
948     LoadEngine();
949 }
950
951 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
952 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
953
954 static char resetOptions[] =
955         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
956         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
957         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
958         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
959
960 void
961 FloatToFront(char **list, char *engineLine)
962 {
963     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
964     int i=0;
965     if(appData.recentEngines <= 0) return;
966     TidyProgramName(engineLine, "localhost", tidy+1);
967     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
968     strncpy(buf+1, *list, MSG_SIZ-50);
969     if(p = strstr(buf, tidy)) { // tidy name appears in list
970         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
971         while(*p++ = *++q); // squeeze out
972     }
973     strcat(tidy, buf+1); // put list behind tidy name
974     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
975     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
976     ASSIGN(*list, tidy+1);
977 }
978
979 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
980
981 void
982 Load (ChessProgramState *cps, int i)
983 {
984     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
985     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
986         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
987         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
988         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
989         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
990         appData.firstProtocolVersion = PROTOVER;
991         ParseArgsFromString(buf);
992         SwapEngines(i);
993         ReplaceEngine(cps, i);
994         FloatToFront(&appData.recentEngineList, engineLine);
995         return;
996     }
997     p = engineName;
998     while(q = strchr(p, SLASH)) p = q+1;
999     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1000     if(engineDir[0] != NULLCHAR) {
1001         ASSIGN(appData.directory[i], engineDir); p = engineName;
1002     } else if(p != engineName) { // derive directory from engine path, when not given
1003         p[-1] = 0;
1004         ASSIGN(appData.directory[i], engineName);
1005         p[-1] = SLASH;
1006         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1007     } else { ASSIGN(appData.directory[i], "."); }
1008     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1009     if(params[0]) {
1010         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1011         snprintf(command, MSG_SIZ, "%s %s", p, params);
1012         p = command;
1013     }
1014     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1015     ASSIGN(appData.chessProgram[i], p);
1016     appData.isUCI[i] = isUCI;
1017     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1018     appData.hasOwnBookUCI[i] = hasBook;
1019     if(!nickName[0]) useNick = FALSE;
1020     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1021     if(addToList) {
1022         int len;
1023         char quote;
1024         q = firstChessProgramNames;
1025         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1026         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1027         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1028                         quote, p, quote, appData.directory[i],
1029                         useNick ? " -fn \"" : "",
1030                         useNick ? nickName : "",
1031                         useNick ? "\"" : "",
1032                         v1 ? " -firstProtocolVersion 1" : "",
1033                         hasBook ? "" : " -fNoOwnBookUCI",
1034                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1035                         storeVariant ? " -variant " : "",
1036                         storeVariant ? VariantName(gameInfo.variant) : "");
1037         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1038         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1039         if(insert != q) insert[-1] = NULLCHAR;
1040         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1041         if(q)   free(q);
1042         FloatToFront(&appData.recentEngineList, buf);
1043     }
1044     ReplaceEngine(cps, i);
1045 }
1046
1047 void
1048 InitTimeControls ()
1049 {
1050     int matched, min, sec;
1051     /*
1052      * Parse timeControl resource
1053      */
1054     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1055                           appData.movesPerSession)) {
1056         char buf[MSG_SIZ];
1057         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1058         DisplayFatalError(buf, 0, 2);
1059     }
1060
1061     /*
1062      * Parse searchTime resource
1063      */
1064     if (*appData.searchTime != NULLCHAR) {
1065         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1066         if (matched == 1) {
1067             searchTime = min * 60;
1068         } else if (matched == 2) {
1069             searchTime = min * 60 + sec;
1070         } else {
1071             char buf[MSG_SIZ];
1072             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1073             DisplayFatalError(buf, 0, 2);
1074         }
1075     }
1076 }
1077
1078 void
1079 InitBackEnd1 ()
1080 {
1081
1082     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1083     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1084
1085     GetTimeMark(&programStartTime);
1086     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1087     appData.seedBase = random() + (random()<<15);
1088     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1089
1090     ClearProgramStats();
1091     programStats.ok_to_send = 1;
1092     programStats.seen_stat = 0;
1093
1094     /*
1095      * Initialize game list
1096      */
1097     ListNew(&gameList);
1098
1099
1100     /*
1101      * Internet chess server status
1102      */
1103     if (appData.icsActive) {
1104         appData.matchMode = FALSE;
1105         appData.matchGames = 0;
1106 #if ZIPPY
1107         appData.noChessProgram = !appData.zippyPlay;
1108 #else
1109         appData.zippyPlay = FALSE;
1110         appData.zippyTalk = FALSE;
1111         appData.noChessProgram = TRUE;
1112 #endif
1113         if (*appData.icsHelper != NULLCHAR) {
1114             appData.useTelnet = TRUE;
1115             appData.telnetProgram = appData.icsHelper;
1116         }
1117     } else {
1118         appData.zippyTalk = appData.zippyPlay = FALSE;
1119     }
1120
1121     /* [AS] Initialize pv info list [HGM] and game state */
1122     {
1123         int i, j;
1124
1125         for( i=0; i<=framePtr; i++ ) {
1126             pvInfoList[i].depth = -1;
1127             boards[i][EP_STATUS] = EP_NONE;
1128             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1129         }
1130     }
1131
1132     InitTimeControls();
1133
1134     /* [AS] Adjudication threshold */
1135     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1136
1137     InitEngine(&first, 0);
1138     InitEngine(&second, 1);
1139     CommonEngineInit();
1140
1141     pairing.which = "pairing"; // pairing engine
1142     pairing.pr = NoProc;
1143     pairing.isr = NULL;
1144     pairing.program = appData.pairingEngine;
1145     pairing.host = "localhost";
1146     pairing.dir = ".";
1147
1148     if (appData.icsActive) {
1149         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1150     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1151         appData.clockMode = FALSE;
1152         first.sendTime = second.sendTime = 0;
1153     }
1154
1155 #if ZIPPY
1156     /* Override some settings from environment variables, for backward
1157        compatibility.  Unfortunately it's not feasible to have the env
1158        vars just set defaults, at least in xboard.  Ugh.
1159     */
1160     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1161       ZippyInit();
1162     }
1163 #endif
1164
1165     if (!appData.icsActive) {
1166       char buf[MSG_SIZ];
1167       int len;
1168
1169       /* Check for variants that are supported only in ICS mode,
1170          or not at all.  Some that are accepted here nevertheless
1171          have bugs; see comments below.
1172       */
1173       VariantClass variant = StringToVariant(appData.variant);
1174       switch (variant) {
1175       case VariantBughouse:     /* need four players and two boards */
1176       case VariantKriegspiel:   /* need to hide pieces and move details */
1177         /* case VariantFischeRandom: (Fabien: moved below) */
1178         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1179         if( (len >= MSG_SIZ) && appData.debugMode )
1180           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1181
1182         DisplayFatalError(buf, 0, 2);
1183         return;
1184
1185       case VariantUnknown:
1186       case VariantLoadable:
1187       case Variant29:
1188       case Variant30:
1189       case Variant31:
1190       case Variant32:
1191       case Variant33:
1192       case Variant34:
1193       case Variant35:
1194       case Variant36:
1195       default:
1196         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1197         if( (len >= MSG_SIZ) && appData.debugMode )
1198           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1199
1200         DisplayFatalError(buf, 0, 2);
1201         return;
1202
1203       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1204       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1205       case VariantGothic:     /* [HGM] should work */
1206       case VariantCapablanca: /* [HGM] should work */
1207       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1208       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1209       case VariantChu:        /* [HGM] experimental */
1210       case VariantKnightmate: /* [HGM] should work */
1211       case VariantCylinder:   /* [HGM] untested */
1212       case VariantFalcon:     /* [HGM] untested */
1213       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1214                                  offboard interposition not understood */
1215       case VariantNormal:     /* definitely works! */
1216       case VariantWildCastle: /* pieces not automatically shuffled */
1217       case VariantNoCastle:   /* pieces not automatically shuffled */
1218       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1219       case VariantLosers:     /* should work except for win condition,
1220                                  and doesn't know captures are mandatory */
1221       case VariantSuicide:    /* should work except for win condition,
1222                                  and doesn't know captures are mandatory */
1223       case VariantGiveaway:   /* should work except for win condition,
1224                                  and doesn't know captures are mandatory */
1225       case VariantTwoKings:   /* should work */
1226       case VariantAtomic:     /* should work except for win condition */
1227       case Variant3Check:     /* should work except for win condition */
1228       case VariantShatranj:   /* should work except for all win conditions */
1229       case VariantMakruk:     /* should work except for draw countdown */
1230       case VariantASEAN :     /* should work except for draw countdown */
1231       case VariantBerolina:   /* might work if TestLegality is off */
1232       case VariantCapaRandom: /* should work */
1233       case VariantJanus:      /* should work */
1234       case VariantSuper:      /* experimental */
1235       case VariantGreat:      /* experimental, requires legality testing to be off */
1236       case VariantSChess:     /* S-Chess, should work */
1237       case VariantGrand:      /* should work */
1238       case VariantSpartan:    /* should work */
1239       case VariantLion:       /* should work */
1240       case VariantChuChess:   /* should work */
1241         break;
1242       }
1243     }
1244
1245 }
1246
1247 int
1248 NextIntegerFromString (char ** str, long * value)
1249 {
1250     int result = -1;
1251     char * s = *str;
1252
1253     while( *s == ' ' || *s == '\t' ) {
1254         s++;
1255     }
1256
1257     *value = 0;
1258
1259     if( *s >= '0' && *s <= '9' ) {
1260         while( *s >= '0' && *s <= '9' ) {
1261             *value = *value * 10 + (*s - '0');
1262             s++;
1263         }
1264
1265         result = 0;
1266     }
1267
1268     *str = s;
1269
1270     return result;
1271 }
1272
1273 int
1274 NextTimeControlFromString (char ** str, long * value)
1275 {
1276     long temp;
1277     int result = NextIntegerFromString( str, &temp );
1278
1279     if( result == 0 ) {
1280         *value = temp * 60; /* Minutes */
1281         if( **str == ':' ) {
1282             (*str)++;
1283             result = NextIntegerFromString( str, &temp );
1284             *value += temp; /* Seconds */
1285         }
1286     }
1287
1288     return result;
1289 }
1290
1291 int
1292 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1293 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1294     int result = -1, type = 0; long temp, temp2;
1295
1296     if(**str != ':') return -1; // old params remain in force!
1297     (*str)++;
1298     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1299     if( NextIntegerFromString( str, &temp ) ) return -1;
1300     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1301
1302     if(**str != '/') {
1303         /* time only: incremental or sudden-death time control */
1304         if(**str == '+') { /* increment follows; read it */
1305             (*str)++;
1306             if(**str == '!') type = *(*str)++; // Bronstein TC
1307             if(result = NextIntegerFromString( str, &temp2)) return -1;
1308             *inc = temp2 * 1000;
1309             if(**str == '.') { // read fraction of increment
1310                 char *start = ++(*str);
1311                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1312                 temp2 *= 1000;
1313                 while(start++ < *str) temp2 /= 10;
1314                 *inc += temp2;
1315             }
1316         } else *inc = 0;
1317         *moves = 0; *tc = temp * 1000; *incType = type;
1318         return 0;
1319     }
1320
1321     (*str)++; /* classical time control */
1322     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1323
1324     if(result == 0) {
1325         *moves = temp;
1326         *tc    = temp2 * 1000;
1327         *inc   = 0;
1328         *incType = type;
1329     }
1330     return result;
1331 }
1332
1333 int
1334 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1335 {   /* [HGM] get time to add from the multi-session time-control string */
1336     int incType, moves=1; /* kludge to force reading of first session */
1337     long time, increment;
1338     char *s = tcString;
1339
1340     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1341     do {
1342         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1343         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1344         if(movenr == -1) return time;    /* last move before new session     */
1345         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1346         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1347         if(!moves) return increment;     /* current session is incremental   */
1348         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1349     } while(movenr >= -1);               /* try again for next session       */
1350
1351     return 0; // no new time quota on this move
1352 }
1353
1354 int
1355 ParseTimeControl (char *tc, float ti, int mps)
1356 {
1357   long tc1;
1358   long tc2;
1359   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1360   int min, sec=0;
1361
1362   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1363   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1364       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1365   if(ti > 0) {
1366
1367     if(mps)
1368       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1369     else
1370       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1371   } else {
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s", mytc);
1376   }
1377   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1378
1379   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1380     return FALSE;
1381   }
1382
1383   if( *tc == '/' ) {
1384     /* Parse second time control */
1385     tc++;
1386
1387     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1388       return FALSE;
1389     }
1390
1391     if( tc2 == 0 ) {
1392       return FALSE;
1393     }
1394
1395     timeControl_2 = tc2 * 1000;
1396   }
1397   else {
1398     timeControl_2 = 0;
1399   }
1400
1401   if( tc1 == 0 ) {
1402     return FALSE;
1403   }
1404
1405   timeControl = tc1 * 1000;
1406
1407   if (ti >= 0) {
1408     timeIncrement = ti * 1000;  /* convert to ms */
1409     movesPerSession = 0;
1410   } else {
1411     timeIncrement = 0;
1412     movesPerSession = mps;
1413   }
1414   return TRUE;
1415 }
1416
1417 void
1418 InitBackEnd2 ()
1419 {
1420     if (appData.debugMode) {
1421 #    ifdef __GIT_VERSION
1422       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1423 #    else
1424       fprintf(debugFP, "Version: %s\n", programVersion);
1425 #    endif
1426     }
1427     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1428
1429     set_cont_sequence(appData.wrapContSeq);
1430     if (appData.matchGames > 0) {
1431         appData.matchMode = TRUE;
1432     } else if (appData.matchMode) {
1433         appData.matchGames = 1;
1434     }
1435     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1436         appData.matchGames = appData.sameColorGames;
1437     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1438         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1439         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1440     }
1441     Reset(TRUE, FALSE);
1442     if (appData.noChessProgram || first.protocolVersion == 1) {
1443       InitBackEnd3();
1444     } else {
1445       /* kludge: allow timeout for initial "feature" commands */
1446       FreezeUI();
1447       DisplayMessage("", _("Starting chess program"));
1448       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1449     }
1450 }
1451
1452 int
1453 CalculateIndex (int index, int gameNr)
1454 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1455     int res;
1456     if(index > 0) return index; // fixed nmber
1457     if(index == 0) return 1;
1458     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1459     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1460     return res;
1461 }
1462
1463 int
1464 LoadGameOrPosition (int gameNr)
1465 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1466     if (*appData.loadGameFile != NULLCHAR) {
1467         if (!LoadGameFromFile(appData.loadGameFile,
1468                 CalculateIndex(appData.loadGameIndex, gameNr),
1469                               appData.loadGameFile, FALSE)) {
1470             DisplayFatalError(_("Bad game file"), 0, 1);
1471             return 0;
1472         }
1473     } else if (*appData.loadPositionFile != NULLCHAR) {
1474         if (!LoadPositionFromFile(appData.loadPositionFile,
1475                 CalculateIndex(appData.loadPositionIndex, gameNr),
1476                                   appData.loadPositionFile)) {
1477             DisplayFatalError(_("Bad position file"), 0, 1);
1478             return 0;
1479         }
1480     }
1481     return 1;
1482 }
1483
1484 void
1485 ReserveGame (int gameNr, char resChar)
1486 {
1487     FILE *tf = fopen(appData.tourneyFile, "r+");
1488     char *p, *q, c, buf[MSG_SIZ];
1489     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1490     safeStrCpy(buf, lastMsg, MSG_SIZ);
1491     DisplayMessage(_("Pick new game"), "");
1492     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1493     ParseArgsFromFile(tf);
1494     p = q = appData.results;
1495     if(appData.debugMode) {
1496       char *r = appData.participants;
1497       fprintf(debugFP, "results = '%s'\n", p);
1498       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1499       fprintf(debugFP, "\n");
1500     }
1501     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1502     nextGame = q - p;
1503     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1504     safeStrCpy(q, p, strlen(p) + 2);
1505     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1506     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1507     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1508         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1509         q[nextGame] = '*';
1510     }
1511     fseek(tf, -(strlen(p)+4), SEEK_END);
1512     c = fgetc(tf);
1513     if(c != '"') // depending on DOS or Unix line endings we can be one off
1514          fseek(tf, -(strlen(p)+2), SEEK_END);
1515     else fseek(tf, -(strlen(p)+3), SEEK_END);
1516     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1517     DisplayMessage(buf, "");
1518     free(p); appData.results = q;
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1520        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1521       int round = appData.defaultMatchGames * appData.tourneyType;
1522       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1523          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1524         UnloadEngine(&first);  // next game belongs to other pairing;
1525         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1526     }
1527     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1528 }
1529
1530 void
1531 MatchEvent (int mode)
1532 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1533         int dummy;
1534         if(matchMode) { // already in match mode: switch it off
1535             abortMatch = TRUE;
1536             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1537             return;
1538         }
1539 //      if(gameMode != BeginningOfGame) {
1540 //          DisplayError(_("You can only start a match from the initial position."), 0);
1541 //          return;
1542 //      }
1543         abortMatch = FALSE;
1544         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1545         /* Set up machine vs. machine match */
1546         nextGame = 0;
1547         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1548         if(appData.tourneyFile[0]) {
1549             ReserveGame(-1, 0);
1550             if(nextGame > appData.matchGames) {
1551                 char buf[MSG_SIZ];
1552                 if(strchr(appData.results, '*') == NULL) {
1553                     FILE *f;
1554                     appData.tourneyCycles++;
1555                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1556                         fclose(f);
1557                         NextTourneyGame(-1, &dummy);
1558                         ReserveGame(-1, 0);
1559                         if(nextGame <= appData.matchGames) {
1560                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1561                             matchMode = mode;
1562                             ScheduleDelayedEvent(NextMatchGame, 10000);
1563                             return;
1564                         }
1565                     }
1566                 }
1567                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1568                 DisplayError(buf, 0);
1569                 appData.tourneyFile[0] = 0;
1570                 return;
1571             }
1572         } else
1573         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1574             DisplayFatalError(_("Can't have a match with no chess programs"),
1575                               0, 2);
1576             return;
1577         }
1578         matchMode = mode;
1579         matchGame = roundNr = 1;
1580         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1581         NextMatchGame();
1582 }
1583
1584 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1585
1586 void
1587 InitBackEnd3 P((void))
1588 {
1589     GameMode initialMode;
1590     char buf[MSG_SIZ];
1591     int err, len;
1592
1593     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1594        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1595         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1596        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1597        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1598         char c, *q = first.variants, *p = strchr(q, ',');
1599         if(p) *p = NULLCHAR;
1600         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1601             int w, h, s;
1602             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1603                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1604             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1605             Reset(TRUE, FALSE);         // and re-initialize
1606         }
1607         if(p) *p = ',';
1608     }
1609
1610     InitChessProgram(&first, startedFromSetupPosition);
1611
1612     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1613         free(programVersion);
1614         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1615         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1616         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1617     }
1618
1619     if (appData.icsActive) {
1620 #ifdef WIN32
1621         /* [DM] Make a console window if needed [HGM] merged ifs */
1622         ConsoleCreate();
1623 #endif
1624         err = establish();
1625         if (err != 0)
1626           {
1627             if (*appData.icsCommPort != NULLCHAR)
1628               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1629                              appData.icsCommPort);
1630             else
1631               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1632                         appData.icsHost, appData.icsPort);
1633
1634             if( (len >= MSG_SIZ) && appData.debugMode )
1635               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1636
1637             DisplayFatalError(buf, err, 1);
1638             return;
1639         }
1640         SetICSMode();
1641         telnetISR =
1642           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1643         fromUserISR =
1644           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1645         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1646             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1647     } else if (appData.noChessProgram) {
1648         SetNCPMode();
1649     } else {
1650         SetGNUMode();
1651     }
1652
1653     if (*appData.cmailGameName != NULLCHAR) {
1654         SetCmailMode();
1655         OpenLoopback(&cmailPR);
1656         cmailISR =
1657           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1658     }
1659
1660     ThawUI();
1661     DisplayMessage("", "");
1662     if (StrCaseCmp(appData.initialMode, "") == 0) {
1663       initialMode = BeginningOfGame;
1664       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1665         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1666         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1667         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1668         ModeHighlight();
1669       }
1670     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1671       initialMode = TwoMachinesPlay;
1672     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1673       initialMode = AnalyzeFile;
1674     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1675       initialMode = AnalyzeMode;
1676     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1677       initialMode = MachinePlaysWhite;
1678     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1679       initialMode = MachinePlaysBlack;
1680     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1681       initialMode = EditGame;
1682     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1683       initialMode = EditPosition;
1684     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1685       initialMode = Training;
1686     } else {
1687       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1688       if( (len >= MSG_SIZ) && appData.debugMode )
1689         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1690
1691       DisplayFatalError(buf, 0, 2);
1692       return;
1693     }
1694
1695     if (appData.matchMode) {
1696         if(appData.tourneyFile[0]) { // start tourney from command line
1697             FILE *f;
1698             if(f = fopen(appData.tourneyFile, "r")) {
1699                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1700                 fclose(f);
1701                 appData.clockMode = TRUE;
1702                 SetGNUMode();
1703             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1704         }
1705         MatchEvent(TRUE);
1706     } else if (*appData.cmailGameName != NULLCHAR) {
1707         /* Set up cmail mode */
1708         ReloadCmailMsgEvent(TRUE);
1709     } else {
1710         /* Set up other modes */
1711         if (initialMode == AnalyzeFile) {
1712           if (*appData.loadGameFile == NULLCHAR) {
1713             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1714             return;
1715           }
1716         }
1717         if (*appData.loadGameFile != NULLCHAR) {
1718             (void) LoadGameFromFile(appData.loadGameFile,
1719                                     appData.loadGameIndex,
1720                                     appData.loadGameFile, TRUE);
1721         } else if (*appData.loadPositionFile != NULLCHAR) {
1722             (void) LoadPositionFromFile(appData.loadPositionFile,
1723                                         appData.loadPositionIndex,
1724                                         appData.loadPositionFile);
1725             /* [HGM] try to make self-starting even after FEN load */
1726             /* to allow automatic setup of fairy variants with wtm */
1727             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1728                 gameMode = BeginningOfGame;
1729                 setboardSpoiledMachineBlack = 1;
1730             }
1731             /* [HGM] loadPos: make that every new game uses the setup */
1732             /* from file as long as we do not switch variant          */
1733             if(!blackPlaysFirst) {
1734                 startedFromPositionFile = TRUE;
1735                 CopyBoard(filePosition, boards[0]);
1736             }
1737         }
1738         if (initialMode == AnalyzeMode) {
1739           if (appData.noChessProgram) {
1740             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1741             return;
1742           }
1743           if (appData.icsActive) {
1744             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1745             return;
1746           }
1747           AnalyzeModeEvent();
1748         } else if (initialMode == AnalyzeFile) {
1749           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1750           ShowThinkingEvent();
1751           AnalyzeFileEvent();
1752           AnalysisPeriodicEvent(1);
1753         } else if (initialMode == MachinePlaysWhite) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1756                               0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1761                               0, 2);
1762             return;
1763           }
1764           MachineWhiteEvent();
1765         } else if (initialMode == MachinePlaysBlack) {
1766           if (appData.noChessProgram) {
1767             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1768                               0, 2);
1769             return;
1770           }
1771           if (appData.icsActive) {
1772             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1773                               0, 2);
1774             return;
1775           }
1776           MachineBlackEvent();
1777         } else if (initialMode == TwoMachinesPlay) {
1778           if (appData.noChessProgram) {
1779             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1780                               0, 2);
1781             return;
1782           }
1783           if (appData.icsActive) {
1784             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1785                               0, 2);
1786             return;
1787           }
1788           TwoMachinesEvent();
1789         } else if (initialMode == EditGame) {
1790           EditGameEvent();
1791         } else if (initialMode == EditPosition) {
1792           EditPositionEvent();
1793         } else if (initialMode == Training) {
1794           if (*appData.loadGameFile == NULLCHAR) {
1795             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1796             return;
1797           }
1798           TrainingEvent();
1799         }
1800     }
1801 }
1802
1803 void
1804 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1805 {
1806     DisplayBook(current+1);
1807
1808     MoveHistorySet( movelist, first, last, current, pvInfoList );
1809
1810     EvalGraphSet( first, last, current, pvInfoList );
1811
1812     MakeEngineOutputTitle();
1813 }
1814
1815 /*
1816  * Establish will establish a contact to a remote host.port.
1817  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1818  *  used to talk to the host.
1819  * Returns 0 if okay, error code if not.
1820  */
1821 int
1822 establish ()
1823 {
1824     char buf[MSG_SIZ];
1825
1826     if (*appData.icsCommPort != NULLCHAR) {
1827         /* Talk to the host through a serial comm port */
1828         return OpenCommPort(appData.icsCommPort, &icsPR);
1829
1830     } else if (*appData.gateway != NULLCHAR) {
1831         if (*appData.remoteShell == NULLCHAR) {
1832             /* Use the rcmd protocol to run telnet program on a gateway host */
1833             snprintf(buf, sizeof(buf), "%s %s %s",
1834                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1835             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1836
1837         } else {
1838             /* Use the rsh program to run telnet program on a gateway host */
1839             if (*appData.remoteUser == NULLCHAR) {
1840                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1841                         appData.gateway, appData.telnetProgram,
1842                         appData.icsHost, appData.icsPort);
1843             } else {
1844                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1845                         appData.remoteShell, appData.gateway,
1846                         appData.remoteUser, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             }
1849             return StartChildProcess(buf, "", &icsPR);
1850
1851         }
1852     } else if (appData.useTelnet) {
1853         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1854
1855     } else {
1856         /* TCP socket interface differs somewhat between
1857            Unix and NT; handle details in the front end.
1858            */
1859         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1860     }
1861 }
1862
1863 void
1864 EscapeExpand (char *p, char *q)
1865 {       // [HGM] initstring: routine to shape up string arguments
1866         while(*p++ = *q++) if(p[-1] == '\\')
1867             switch(*q++) {
1868                 case 'n': p[-1] = '\n'; break;
1869                 case 'r': p[-1] = '\r'; break;
1870                 case 't': p[-1] = '\t'; break;
1871                 case '\\': p[-1] = '\\'; break;
1872                 case 0: *p = 0; return;
1873                 default: p[-1] = q[-1]; break;
1874             }
1875 }
1876
1877 void
1878 show_bytes (FILE *fp, char *buf, int count)
1879 {
1880     while (count--) {
1881         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1882             fprintf(fp, "\\%03o", *buf & 0xff);
1883         } else {
1884             putc(*buf, fp);
1885         }
1886         buf++;
1887     }
1888     fflush(fp);
1889 }
1890
1891 /* Returns an errno value */
1892 int
1893 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1894 {
1895     char buf[8192], *p, *q, *buflim;
1896     int left, newcount, outcount;
1897
1898     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1899         *appData.gateway != NULLCHAR) {
1900         if (appData.debugMode) {
1901             fprintf(debugFP, ">ICS: ");
1902             show_bytes(debugFP, message, count);
1903             fprintf(debugFP, "\n");
1904         }
1905         return OutputToProcess(pr, message, count, outError);
1906     }
1907
1908     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1909     p = message;
1910     q = buf;
1911     left = count;
1912     newcount = 0;
1913     while (left) {
1914         if (q >= buflim) {
1915             if (appData.debugMode) {
1916                 fprintf(debugFP, ">ICS: ");
1917                 show_bytes(debugFP, buf, newcount);
1918                 fprintf(debugFP, "\n");
1919             }
1920             outcount = OutputToProcess(pr, buf, newcount, outError);
1921             if (outcount < newcount) return -1; /* to be sure */
1922             q = buf;
1923             newcount = 0;
1924         }
1925         if (*p == '\n') {
1926             *q++ = '\r';
1927             newcount++;
1928         } else if (((unsigned char) *p) == TN_IAC) {
1929             *q++ = (char) TN_IAC;
1930             newcount ++;
1931         }
1932         *q++ = *p++;
1933         newcount++;
1934         left--;
1935     }
1936     if (appData.debugMode) {
1937         fprintf(debugFP, ">ICS: ");
1938         show_bytes(debugFP, buf, newcount);
1939         fprintf(debugFP, "\n");
1940     }
1941     outcount = OutputToProcess(pr, buf, newcount, outError);
1942     if (outcount < newcount) return -1; /* to be sure */
1943     return count;
1944 }
1945
1946 void
1947 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1948 {
1949     int outError, outCount;
1950     static int gotEof = 0;
1951     static FILE *ini;
1952
1953     /* Pass data read from player on to ICS */
1954     if (count > 0) {
1955         gotEof = 0;
1956         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1957         if (outCount < count) {
1958             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959         }
1960         if(have_sent_ICS_logon == 2) {
1961           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1962             fprintf(ini, "%s", message);
1963             have_sent_ICS_logon = 3;
1964           } else
1965             have_sent_ICS_logon = 1;
1966         } else if(have_sent_ICS_logon == 3) {
1967             fprintf(ini, "%s", message);
1968             fclose(ini);
1969           have_sent_ICS_logon = 1;
1970         }
1971     } else if (count < 0) {
1972         RemoveInputSource(isr);
1973         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1974     } else if (gotEof++ > 0) {
1975         RemoveInputSource(isr);
1976         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1977     }
1978 }
1979
1980 void
1981 KeepAlive ()
1982 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1983     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1984     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1985     SendToICS("date\n");
1986     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1987 }
1988
1989 /* added routine for printf style output to ics */
1990 void
1991 ics_printf (char *format, ...)
1992 {
1993     char buffer[MSG_SIZ];
1994     va_list args;
1995
1996     va_start(args, format);
1997     vsnprintf(buffer, sizeof(buffer), format, args);
1998     buffer[sizeof(buffer)-1] = '\0';
1999     SendToICS(buffer);
2000     va_end(args);
2001 }
2002
2003 void
2004 SendToICS (char *s)
2005 {
2006     int count, outCount, outError;
2007
2008     if (icsPR == NoProc) return;
2009
2010     count = strlen(s);
2011     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2012     if (outCount < count) {
2013         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2014     }
2015 }
2016
2017 /* This is used for sending logon scripts to the ICS. Sending
2018    without a delay causes problems when using timestamp on ICC
2019    (at least on my machine). */
2020 void
2021 SendToICSDelayed (char *s, long msdelay)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     if (appData.debugMode) {
2029         fprintf(debugFP, ">ICS: ");
2030         show_bytes(debugFP, s, count);
2031         fprintf(debugFP, "\n");
2032     }
2033     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2034                                       msdelay);
2035     if (outCount < count) {
2036         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2037     }
2038 }
2039
2040
2041 /* Remove all highlighting escape sequences in s
2042    Also deletes any suffix starting with '('
2043    */
2044 char *
2045 StripHighlightAndTitle (char *s)
2046 {
2047     static char retbuf[MSG_SIZ];
2048     char *p = retbuf;
2049
2050     while (*s != NULLCHAR) {
2051         while (*s == '\033') {
2052             while (*s != NULLCHAR && !isalpha(*s)) s++;
2053             if (*s != NULLCHAR) s++;
2054         }
2055         while (*s != NULLCHAR && *s != '\033') {
2056             if (*s == '(' || *s == '[') {
2057                 *p = NULLCHAR;
2058                 return retbuf;
2059             }
2060             *p++ = *s++;
2061         }
2062     }
2063     *p = NULLCHAR;
2064     return retbuf;
2065 }
2066
2067 /* Remove all highlighting escape sequences in s */
2068 char *
2069 StripHighlight (char *s)
2070 {
2071     static char retbuf[MSG_SIZ];
2072     char *p = retbuf;
2073
2074     while (*s != NULLCHAR) {
2075         while (*s == '\033') {
2076             while (*s != NULLCHAR && !isalpha(*s)) s++;
2077             if (*s != NULLCHAR) s++;
2078         }
2079         while (*s != NULLCHAR && *s != '\033') {
2080             *p++ = *s++;
2081         }
2082     }
2083     *p = NULLCHAR;
2084     return retbuf;
2085 }
2086
2087 char engineVariant[MSG_SIZ];
2088 char *variantNames[] = VARIANT_NAMES;
2089 char *
2090 VariantName (VariantClass v)
2091 {
2092     if(v == VariantUnknown || *engineVariant) return engineVariant;
2093     return variantNames[v];
2094 }
2095
2096
2097 /* Identify a variant from the strings the chess servers use or the
2098    PGN Variant tag names we use. */
2099 VariantClass
2100 StringToVariant (char *e)
2101 {
2102     char *p;
2103     int wnum = -1;
2104     VariantClass v = VariantNormal;
2105     int i, found = FALSE;
2106     char buf[MSG_SIZ];
2107     int len;
2108
2109     if (!e) return v;
2110
2111     /* [HGM] skip over optional board-size prefixes */
2112     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2113         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2114         while( *e++ != '_');
2115     }
2116
2117     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2118         v = VariantNormal;
2119         found = TRUE;
2120     } else
2121     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2122       if (p = StrCaseStr(e, variantNames[i])) {
2123         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2124         v = (VariantClass) i;
2125         found = TRUE;
2126         break;
2127       }
2128     }
2129
2130     if (!found) {
2131       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2132           || StrCaseStr(e, "wild/fr")
2133           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2134         v = VariantFischeRandom;
2135       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2136                  (i = 1, p = StrCaseStr(e, "w"))) {
2137         p += i;
2138         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2139         if (isdigit(*p)) {
2140           wnum = atoi(p);
2141         } else {
2142           wnum = -1;
2143         }
2144         switch (wnum) {
2145         case 0: /* FICS only, actually */
2146         case 1:
2147           /* Castling legal even if K starts on d-file */
2148           v = VariantWildCastle;
2149           break;
2150         case 2:
2151         case 3:
2152         case 4:
2153           /* Castling illegal even if K & R happen to start in
2154              normal positions. */
2155           v = VariantNoCastle;
2156           break;
2157         case 5:
2158         case 7:
2159         case 8:
2160         case 10:
2161         case 11:
2162         case 12:
2163         case 13:
2164         case 14:
2165         case 15:
2166         case 18:
2167         case 19:
2168           /* Castling legal iff K & R start in normal positions */
2169           v = VariantNormal;
2170           break;
2171         case 6:
2172         case 20:
2173         case 21:
2174           /* Special wilds for position setup; unclear what to do here */
2175           v = VariantLoadable;
2176           break;
2177         case 9:
2178           /* Bizarre ICC game */
2179           v = VariantTwoKings;
2180           break;
2181         case 16:
2182           v = VariantKriegspiel;
2183           break;
2184         case 17:
2185           v = VariantLosers;
2186           break;
2187         case 22:
2188           v = VariantFischeRandom;
2189           break;
2190         case 23:
2191           v = VariantCrazyhouse;
2192           break;
2193         case 24:
2194           v = VariantBughouse;
2195           break;
2196         case 25:
2197           v = Variant3Check;
2198           break;
2199         case 26:
2200           /* Not quite the same as FICS suicide! */
2201           v = VariantGiveaway;
2202           break;
2203         case 27:
2204           v = VariantAtomic;
2205           break;
2206         case 28:
2207           v = VariantShatranj;
2208           break;
2209
2210         /* Temporary names for future ICC types.  The name *will* change in
2211            the next xboard/WinBoard release after ICC defines it. */
2212         case 29:
2213           v = Variant29;
2214           break;
2215         case 30:
2216           v = Variant30;
2217           break;
2218         case 31:
2219           v = Variant31;
2220           break;
2221         case 32:
2222           v = Variant32;
2223           break;
2224         case 33:
2225           v = Variant33;
2226           break;
2227         case 34:
2228           v = Variant34;
2229           break;
2230         case 35:
2231           v = Variant35;
2232           break;
2233         case 36:
2234           v = Variant36;
2235           break;
2236         case 37:
2237           v = VariantShogi;
2238           break;
2239         case 38:
2240           v = VariantXiangqi;
2241           break;
2242         case 39:
2243           v = VariantCourier;
2244           break;
2245         case 40:
2246           v = VariantGothic;
2247           break;
2248         case 41:
2249           v = VariantCapablanca;
2250           break;
2251         case 42:
2252           v = VariantKnightmate;
2253           break;
2254         case 43:
2255           v = VariantFairy;
2256           break;
2257         case 44:
2258           v = VariantCylinder;
2259           break;
2260         case 45:
2261           v = VariantFalcon;
2262           break;
2263         case 46:
2264           v = VariantCapaRandom;
2265           break;
2266         case 47:
2267           v = VariantBerolina;
2268           break;
2269         case 48:
2270           v = VariantJanus;
2271           break;
2272         case 49:
2273           v = VariantSuper;
2274           break;
2275         case 50:
2276           v = VariantGreat;
2277           break;
2278         case -1:
2279           /* Found "wild" or "w" in the string but no number;
2280              must assume it's normal chess. */
2281           v = VariantNormal;
2282           break;
2283         default:
2284           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2285           if( (len >= MSG_SIZ) && appData.debugMode )
2286             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2287
2288           DisplayError(buf, 0);
2289           v = VariantUnknown;
2290           break;
2291         }
2292       }
2293     }
2294     if (appData.debugMode) {
2295       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2296               e, wnum, VariantName(v));
2297     }
2298     return v;
2299 }
2300
2301 static int leftover_start = 0, leftover_len = 0;
2302 char star_match[STAR_MATCH_N][MSG_SIZ];
2303
2304 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2305    advance *index beyond it, and set leftover_start to the new value of
2306    *index; else return FALSE.  If pattern contains the character '*', it
2307    matches any sequence of characters not containing '\r', '\n', or the
2308    character following the '*' (if any), and the matched sequence(s) are
2309    copied into star_match.
2310    */
2311 int
2312 looking_at ( char *buf, int *index, char *pattern)
2313 {
2314     char *bufp = &buf[*index], *patternp = pattern;
2315     int star_count = 0;
2316     char *matchp = star_match[0];
2317
2318     for (;;) {
2319         if (*patternp == NULLCHAR) {
2320             *index = leftover_start = bufp - buf;
2321             *matchp = NULLCHAR;
2322             return TRUE;
2323         }
2324         if (*bufp == NULLCHAR) return FALSE;
2325         if (*patternp == '*') {
2326             if (*bufp == *(patternp + 1)) {
2327                 *matchp = NULLCHAR;
2328                 matchp = star_match[++star_count];
2329                 patternp += 2;
2330                 bufp++;
2331                 continue;
2332             } else if (*bufp == '\n' || *bufp == '\r') {
2333                 patternp++;
2334                 if (*patternp == NULLCHAR)
2335                   continue;
2336                 else
2337                   return FALSE;
2338             } else {
2339                 *matchp++ = *bufp++;
2340                 continue;
2341             }
2342         }
2343         if (*patternp != *bufp) return FALSE;
2344         patternp++;
2345         bufp++;
2346     }
2347 }
2348
2349 void
2350 SendToPlayer (char *data, int length)
2351 {
2352     int error, outCount;
2353     outCount = OutputToProcess(NoProc, data, length, &error);
2354     if (outCount < length) {
2355         DisplayFatalError(_("Error writing to display"), error, 1);
2356     }
2357 }
2358
2359 void
2360 PackHolding (char packed[], char *holding)
2361 {
2362     char *p = holding;
2363     char *q = packed;
2364     int runlength = 0;
2365     int curr = 9999;
2366     do {
2367         if (*p == curr) {
2368             runlength++;
2369         } else {
2370             switch (runlength) {
2371               case 0:
2372                 break;
2373               case 1:
2374                 *q++ = curr;
2375                 break;
2376               case 2:
2377                 *q++ = curr;
2378                 *q++ = curr;
2379                 break;
2380               default:
2381                 sprintf(q, "%d", runlength);
2382                 while (*q) q++;
2383                 *q++ = curr;
2384                 break;
2385             }
2386             runlength = 1;
2387             curr = *p;
2388         }
2389     } while (*p++);
2390     *q = NULLCHAR;
2391 }
2392
2393 /* Telnet protocol requests from the front end */
2394 void
2395 TelnetRequest (unsigned char ddww, unsigned char option)
2396 {
2397     unsigned char msg[3];
2398     int outCount, outError;
2399
2400     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2401
2402     if (appData.debugMode) {
2403         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2404         switch (ddww) {
2405           case TN_DO:
2406             ddwwStr = "DO";
2407             break;
2408           case TN_DONT:
2409             ddwwStr = "DONT";
2410             break;
2411           case TN_WILL:
2412             ddwwStr = "WILL";
2413             break;
2414           case TN_WONT:
2415             ddwwStr = "WONT";
2416             break;
2417           default:
2418             ddwwStr = buf1;
2419             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2420             break;
2421         }
2422         switch (option) {
2423           case TN_ECHO:
2424             optionStr = "ECHO";
2425             break;
2426           default:
2427             optionStr = buf2;
2428             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2429             break;
2430         }
2431         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2432     }
2433     msg[0] = TN_IAC;
2434     msg[1] = ddww;
2435     msg[2] = option;
2436     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2437     if (outCount < 3) {
2438         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2439     }
2440 }
2441
2442 void
2443 DoEcho ()
2444 {
2445     if (!appData.icsActive) return;
2446     TelnetRequest(TN_DO, TN_ECHO);
2447 }
2448
2449 void
2450 DontEcho ()
2451 {
2452     if (!appData.icsActive) return;
2453     TelnetRequest(TN_DONT, TN_ECHO);
2454 }
2455
2456 void
2457 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2458 {
2459     /* put the holdings sent to us by the server on the board holdings area */
2460     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2461     char p;
2462     ChessSquare piece;
2463
2464     if(gameInfo.holdingsWidth < 2)  return;
2465     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2466         return; // prevent overwriting by pre-board holdings
2467
2468     if( (int)lowestPiece >= BlackPawn ) {
2469         holdingsColumn = 0;
2470         countsColumn = 1;
2471         holdingsStartRow = BOARD_HEIGHT-1;
2472         direction = -1;
2473     } else {
2474         holdingsColumn = BOARD_WIDTH-1;
2475         countsColumn = BOARD_WIDTH-2;
2476         holdingsStartRow = 0;
2477         direction = 1;
2478     }
2479
2480     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2481         board[i][holdingsColumn] = EmptySquare;
2482         board[i][countsColumn]   = (ChessSquare) 0;
2483     }
2484     while( (p=*holdings++) != NULLCHAR ) {
2485         piece = CharToPiece( ToUpper(p) );
2486         if(piece == EmptySquare) continue;
2487         /*j = (int) piece - (int) WhitePawn;*/
2488         j = PieceToNumber(piece);
2489         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2490         if(j < 0) continue;               /* should not happen */
2491         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2492         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2493         board[holdingsStartRow+j*direction][countsColumn]++;
2494     }
2495 }
2496
2497
2498 void
2499 VariantSwitch (Board board, VariantClass newVariant)
2500 {
2501    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2502    static Board oldBoard;
2503
2504    startedFromPositionFile = FALSE;
2505    if(gameInfo.variant == newVariant) return;
2506
2507    /* [HGM] This routine is called each time an assignment is made to
2508     * gameInfo.variant during a game, to make sure the board sizes
2509     * are set to match the new variant. If that means adding or deleting
2510     * holdings, we shift the playing board accordingly
2511     * This kludge is needed because in ICS observe mode, we get boards
2512     * of an ongoing game without knowing the variant, and learn about the
2513     * latter only later. This can be because of the move list we requested,
2514     * in which case the game history is refilled from the beginning anyway,
2515     * but also when receiving holdings of a crazyhouse game. In the latter
2516     * case we want to add those holdings to the already received position.
2517     */
2518
2519
2520    if (appData.debugMode) {
2521      fprintf(debugFP, "Switch board from %s to %s\n",
2522              VariantName(gameInfo.variant), VariantName(newVariant));
2523      setbuf(debugFP, NULL);
2524    }
2525    shuffleOpenings = 0;       /* [HGM] shuffle */
2526    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2527    switch(newVariant)
2528      {
2529      case VariantShogi:
2530        newWidth = 9;  newHeight = 9;
2531        gameInfo.holdingsSize = 7;
2532      case VariantBughouse:
2533      case VariantCrazyhouse:
2534        newHoldingsWidth = 2; break;
2535      case VariantGreat:
2536        newWidth = 10;
2537      case VariantSuper:
2538        newHoldingsWidth = 2;
2539        gameInfo.holdingsSize = 8;
2540        break;
2541      case VariantGothic:
2542      case VariantCapablanca:
2543      case VariantCapaRandom:
2544        newWidth = 10;
2545      default:
2546        newHoldingsWidth = gameInfo.holdingsSize = 0;
2547      };
2548
2549    if(newWidth  != gameInfo.boardWidth  ||
2550       newHeight != gameInfo.boardHeight ||
2551       newHoldingsWidth != gameInfo.holdingsWidth ) {
2552
2553      /* shift position to new playing area, if needed */
2554      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2555        for(i=0; i<BOARD_HEIGHT; i++)
2556          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2557            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2558              board[i][j];
2559        for(i=0; i<newHeight; i++) {
2560          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2561          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2562        }
2563      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568      }
2569      board[HOLDINGS_SET] = 0;
2570      gameInfo.boardWidth  = newWidth;
2571      gameInfo.boardHeight = newHeight;
2572      gameInfo.holdingsWidth = newHoldingsWidth;
2573      gameInfo.variant = newVariant;
2574      InitDrawingSizes(-2, 0);
2575    } else gameInfo.variant = newVariant;
2576    CopyBoard(oldBoard, board);   // remember correctly formatted board
2577      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2578    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2579 }
2580
2581 static int loggedOn = FALSE;
2582
2583 /*-- Game start info cache: --*/
2584 int gs_gamenum;
2585 char gs_kind[MSG_SIZ];
2586 static char player1Name[128] = "";
2587 static char player2Name[128] = "";
2588 static char cont_seq[] = "\n\\   ";
2589 static int player1Rating = -1;
2590 static int player2Rating = -1;
2591 /*----------------------------*/
2592
2593 ColorClass curColor = ColorNormal;
2594 int suppressKibitz = 0;
2595
2596 // [HGM] seekgraph
2597 Boolean soughtPending = FALSE;
2598 Boolean seekGraphUp;
2599 #define MAX_SEEK_ADS 200
2600 #define SQUARE 0x80
2601 char *seekAdList[MAX_SEEK_ADS];
2602 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2603 float tcList[MAX_SEEK_ADS];
2604 char colorList[MAX_SEEK_ADS];
2605 int nrOfSeekAds = 0;
2606 int minRating = 1010, maxRating = 2800;
2607 int hMargin = 10, vMargin = 20, h, w;
2608 extern int squareSize, lineGap;
2609
2610 void
2611 PlotSeekAd (int i)
2612 {
2613         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2614         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2615         if(r < minRating+100 && r >=0 ) r = minRating+100;
2616         if(r > maxRating) r = maxRating;
2617         if(tc < 1.f) tc = 1.f;
2618         if(tc > 95.f) tc = 95.f;
2619         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2620         y = ((double)r - minRating)/(maxRating - minRating)
2621             * (h-vMargin-squareSize/8-1) + vMargin;
2622         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2623         if(strstr(seekAdList[i], " u ")) color = 1;
2624         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2625            !strstr(seekAdList[i], "bullet") &&
2626            !strstr(seekAdList[i], "blitz") &&
2627            !strstr(seekAdList[i], "standard") ) color = 2;
2628         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2629         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2630 }
2631
2632 void
2633 PlotSingleSeekAd (int i)
2634 {
2635         PlotSeekAd(i);
2636 }
2637
2638 void
2639 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2640 {
2641         char buf[MSG_SIZ], *ext = "";
2642         VariantClass v = StringToVariant(type);
2643         if(strstr(type, "wild")) {
2644             ext = type + 4; // append wild number
2645             if(v == VariantFischeRandom) type = "chess960"; else
2646             if(v == VariantLoadable) type = "setup"; else
2647             type = VariantName(v);
2648         }
2649         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2650         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2651             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2652             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2653             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2654             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2655             seekNrList[nrOfSeekAds] = nr;
2656             zList[nrOfSeekAds] = 0;
2657             seekAdList[nrOfSeekAds++] = StrSave(buf);
2658             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2659         }
2660 }
2661
2662 void
2663 EraseSeekDot (int i)
2664 {
2665     int x = xList[i], y = yList[i], d=squareSize/4, k;
2666     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2667     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2668     // now replot every dot that overlapped
2669     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2670         int xx = xList[k], yy = yList[k];
2671         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2672             DrawSeekDot(xx, yy, colorList[k]);
2673     }
2674 }
2675
2676 void
2677 RemoveSeekAd (int nr)
2678 {
2679         int i;
2680         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2681             EraseSeekDot(i);
2682             if(seekAdList[i]) free(seekAdList[i]);
2683             seekAdList[i] = seekAdList[--nrOfSeekAds];
2684             seekNrList[i] = seekNrList[nrOfSeekAds];
2685             ratingList[i] = ratingList[nrOfSeekAds];
2686             colorList[i]  = colorList[nrOfSeekAds];
2687             tcList[i] = tcList[nrOfSeekAds];
2688             xList[i]  = xList[nrOfSeekAds];
2689             yList[i]  = yList[nrOfSeekAds];
2690             zList[i]  = zList[nrOfSeekAds];
2691             seekAdList[nrOfSeekAds] = NULL;
2692             break;
2693         }
2694 }
2695
2696 Boolean
2697 MatchSoughtLine (char *line)
2698 {
2699     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2700     int nr, base, inc, u=0; char dummy;
2701
2702     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2703        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2704        (u=1) &&
2705        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2706         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2707         // match: compact and save the line
2708         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2709         return TRUE;
2710     }
2711     return FALSE;
2712 }
2713
2714 int
2715 DrawSeekGraph ()
2716 {
2717     int i;
2718     if(!seekGraphUp) return FALSE;
2719     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2720     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2721
2722     DrawSeekBackground(0, 0, w, h);
2723     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2724     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2725     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2726         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2727         yy = h-1-yy;
2728         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2729         if(i%500 == 0) {
2730             char buf[MSG_SIZ];
2731             snprintf(buf, MSG_SIZ, "%d", i);
2732             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2733         }
2734     }
2735     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2736     for(i=1; i<100; i+=(i<10?1:5)) {
2737         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2738         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2739         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2740             char buf[MSG_SIZ];
2741             snprintf(buf, MSG_SIZ, "%d", i);
2742             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2743         }
2744     }
2745     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2746     return TRUE;
2747 }
2748
2749 int
2750 SeekGraphClick (ClickType click, int x, int y, int moving)
2751 {
2752     static int lastDown = 0, displayed = 0, lastSecond;
2753     if(y < 0) return FALSE;
2754     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2755         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2756         if(!seekGraphUp) return FALSE;
2757         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2758         DrawPosition(TRUE, NULL);
2759         return TRUE;
2760     }
2761     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2762         if(click == Release || moving) return FALSE;
2763         nrOfSeekAds = 0;
2764         soughtPending = TRUE;
2765         SendToICS(ics_prefix);
2766         SendToICS("sought\n"); // should this be "sought all"?
2767     } else { // issue challenge based on clicked ad
2768         int dist = 10000; int i, closest = 0, second = 0;
2769         for(i=0; i<nrOfSeekAds; i++) {
2770             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2771             if(d < dist) { dist = d; closest = i; }
2772             second += (d - zList[i] < 120); // count in-range ads
2773             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2774         }
2775         if(dist < 120) {
2776             char buf[MSG_SIZ];
2777             second = (second > 1);
2778             if(displayed != closest || second != lastSecond) {
2779                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2780                 lastSecond = second; displayed = closest;
2781             }
2782             if(click == Press) {
2783                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2784                 lastDown = closest;
2785                 return TRUE;
2786             } // on press 'hit', only show info
2787             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2788             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2789             SendToICS(ics_prefix);
2790             SendToICS(buf);
2791             return TRUE; // let incoming board of started game pop down the graph
2792         } else if(click == Release) { // release 'miss' is ignored
2793             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2794             if(moving == 2) { // right up-click
2795                 nrOfSeekAds = 0; // refresh graph
2796                 soughtPending = TRUE;
2797                 SendToICS(ics_prefix);
2798                 SendToICS("sought\n"); // should this be "sought all"?
2799             }
2800             return TRUE;
2801         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2802         // press miss or release hit 'pop down' seek graph
2803         seekGraphUp = FALSE;
2804         DrawPosition(TRUE, NULL);
2805     }
2806     return TRUE;
2807 }
2808
2809 void
2810 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2811 {
2812 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2813 #define STARTED_NONE 0
2814 #define STARTED_MOVES 1
2815 #define STARTED_BOARD 2
2816 #define STARTED_OBSERVE 3
2817 #define STARTED_HOLDINGS 4
2818 #define STARTED_CHATTER 5
2819 #define STARTED_COMMENT 6
2820 #define STARTED_MOVES_NOHIDE 7
2821
2822     static int started = STARTED_NONE;
2823     static char parse[20000];
2824     static int parse_pos = 0;
2825     static char buf[BUF_SIZE + 1];
2826     static int firstTime = TRUE, intfSet = FALSE;
2827     static ColorClass prevColor = ColorNormal;
2828     static int savingComment = FALSE;
2829     static int cmatch = 0; // continuation sequence match
2830     char *bp;
2831     char str[MSG_SIZ];
2832     int i, oldi;
2833     int buf_len;
2834     int next_out;
2835     int tkind;
2836     int backup;    /* [DM] For zippy color lines */
2837     char *p;
2838     char talker[MSG_SIZ]; // [HGM] chat
2839     int channel, collective=0;
2840
2841     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2842
2843     if (appData.debugMode) {
2844       if (!error) {
2845         fprintf(debugFP, "<ICS: ");
2846         show_bytes(debugFP, data, count);
2847         fprintf(debugFP, "\n");
2848       }
2849     }
2850
2851     if (appData.debugMode) { int f = forwardMostMove;
2852         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2853                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2854                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2855     }
2856     if (count > 0) {
2857         /* If last read ended with a partial line that we couldn't parse,
2858            prepend it to the new read and try again. */
2859         if (leftover_len > 0) {
2860             for (i=0; i<leftover_len; i++)
2861               buf[i] = buf[leftover_start + i];
2862         }
2863
2864     /* copy new characters into the buffer */
2865     bp = buf + leftover_len;
2866     buf_len=leftover_len;
2867     for (i=0; i<count; i++)
2868     {
2869         // ignore these
2870         if (data[i] == '\r')
2871             continue;
2872
2873         // join lines split by ICS?
2874         if (!appData.noJoin)
2875         {
2876             /*
2877                 Joining just consists of finding matches against the
2878                 continuation sequence, and discarding that sequence
2879                 if found instead of copying it.  So, until a match
2880                 fails, there's nothing to do since it might be the
2881                 complete sequence, and thus, something we don't want
2882                 copied.
2883             */
2884             if (data[i] == cont_seq[cmatch])
2885             {
2886                 cmatch++;
2887                 if (cmatch == strlen(cont_seq))
2888                 {
2889                     cmatch = 0; // complete match.  just reset the counter
2890
2891                     /*
2892                         it's possible for the ICS to not include the space
2893                         at the end of the last word, making our [correct]
2894                         join operation fuse two separate words.  the server
2895                         does this when the space occurs at the width setting.
2896                     */
2897                     if (!buf_len || buf[buf_len-1] != ' ')
2898                     {
2899                         *bp++ = ' ';
2900                         buf_len++;
2901                     }
2902                 }
2903                 continue;
2904             }
2905             else if (cmatch)
2906             {
2907                 /*
2908                     match failed, so we have to copy what matched before
2909                     falling through and copying this character.  In reality,
2910                     this will only ever be just the newline character, but
2911                     it doesn't hurt to be precise.
2912                 */
2913                 strncpy(bp, cont_seq, cmatch);
2914                 bp += cmatch;
2915                 buf_len += cmatch;
2916                 cmatch = 0;
2917             }
2918         }
2919
2920         // copy this char
2921         *bp++ = data[i];
2922         buf_len++;
2923     }
2924
2925         buf[buf_len] = NULLCHAR;
2926 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2927         next_out = 0;
2928         leftover_start = 0;
2929
2930         i = 0;
2931         while (i < buf_len) {
2932             /* Deal with part of the TELNET option negotiation
2933                protocol.  We refuse to do anything beyond the
2934                defaults, except that we allow the WILL ECHO option,
2935                which ICS uses to turn off password echoing when we are
2936                directly connected to it.  We reject this option
2937                if localLineEditing mode is on (always on in xboard)
2938                and we are talking to port 23, which might be a real
2939                telnet server that will try to keep WILL ECHO on permanently.
2940              */
2941             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2942                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2943                 unsigned char option;
2944                 oldi = i;
2945                 switch ((unsigned char) buf[++i]) {
2946                   case TN_WILL:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<WILL ");
2949                     switch (option = (unsigned char) buf[++i]) {
2950                       case TN_ECHO:
2951                         if (appData.debugMode)
2952                           fprintf(debugFP, "ECHO ");
2953                         /* Reply only if this is a change, according
2954                            to the protocol rules. */
2955                         if (remoteEchoOption) break;
2956                         if (appData.localLineEditing &&
2957                             atoi(appData.icsPort) == TN_PORT) {
2958                             TelnetRequest(TN_DONT, TN_ECHO);
2959                         } else {
2960                             EchoOff();
2961                             TelnetRequest(TN_DO, TN_ECHO);
2962                             remoteEchoOption = TRUE;
2963                         }
2964                         break;
2965                       default:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "%d ", option);
2968                         /* Whatever this is, we don't want it. */
2969                         TelnetRequest(TN_DONT, option);
2970                         break;
2971                     }
2972                     break;
2973                   case TN_WONT:
2974                     if (appData.debugMode)
2975                       fprintf(debugFP, "\n<WONT ");
2976                     switch (option = (unsigned char) buf[++i]) {
2977                       case TN_ECHO:
2978                         if (appData.debugMode)
2979                           fprintf(debugFP, "ECHO ");
2980                         /* Reply only if this is a change, according
2981                            to the protocol rules. */
2982                         if (!remoteEchoOption) break;
2983                         EchoOn();
2984                         TelnetRequest(TN_DONT, TN_ECHO);
2985                         remoteEchoOption = FALSE;
2986                         break;
2987                       default:
2988                         if (appData.debugMode)
2989                           fprintf(debugFP, "%d ", (unsigned char) option);
2990                         /* Whatever this is, it must already be turned
2991                            off, because we never agree to turn on
2992                            anything non-default, so according to the
2993                            protocol rules, we don't reply. */
2994                         break;
2995                     }
2996                     break;
2997                   case TN_DO:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<DO ");
3000                     switch (option = (unsigned char) buf[++i]) {
3001                       default:
3002                         /* Whatever this is, we refuse to do it. */
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", option);
3005                         TelnetRequest(TN_WONT, option);
3006                         break;
3007                     }
3008                     break;
3009                   case TN_DONT:
3010                     if (appData.debugMode)
3011                       fprintf(debugFP, "\n<DONT ");
3012                     switch (option = (unsigned char) buf[++i]) {
3013                       default:
3014                         if (appData.debugMode)
3015                           fprintf(debugFP, "%d ", option);
3016                         /* Whatever this is, we are already not doing
3017                            it, because we never agree to do anything
3018                            non-default, so according to the protocol
3019                            rules, we don't reply. */
3020                         break;
3021                     }
3022                     break;
3023                   case TN_IAC:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<IAC ");
3026                     /* Doubled IAC; pass it through */
3027                     i--;
3028                     break;
3029                   default:
3030                     if (appData.debugMode)
3031                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3032                     /* Drop all other telnet commands on the floor */
3033                     break;
3034                 }
3035                 if (oldi > next_out)
3036                   SendToPlayer(&buf[next_out], oldi - next_out);
3037                 if (++i > next_out)
3038                   next_out = i;
3039                 continue;
3040             }
3041
3042             /* OK, this at least will *usually* work */
3043             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3044                 loggedOn = TRUE;
3045             }
3046
3047             if (loggedOn && !intfSet) {
3048                 if (ics_type == ICS_ICC) {
3049                   snprintf(str, MSG_SIZ,
3050                           "/set-quietly interface %s\n/set-quietly style 12\n",
3051                           programVersion);
3052                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3053                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3054                 } else if (ics_type == ICS_CHESSNET) {
3055                   snprintf(str, MSG_SIZ, "/style 12\n");
3056                 } else {
3057                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3058                   strcat(str, programVersion);
3059                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3062 #ifdef WIN32
3063                   strcat(str, "$iset nohighlight 1\n");
3064 #endif
3065                   strcat(str, "$iset lock 1\n$style 12\n");
3066                 }
3067                 SendToICS(str);
3068                 NotifyFrontendLogin();
3069                 intfSet = TRUE;
3070             }
3071
3072             if (started == STARTED_COMMENT) {
3073                 /* Accumulate characters in comment */
3074                 parse[parse_pos++] = buf[i];
3075                 if (buf[i] == '\n') {
3076                     parse[parse_pos] = NULLCHAR;
3077                     if(chattingPartner>=0) {
3078                         char mess[MSG_SIZ];
3079                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3080                         OutputChatMessage(chattingPartner, mess);
3081                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3082                             int p;
3083                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3084                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3085                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3086                                 OutputChatMessage(p, mess);
3087                                 break;
3088                             }
3089                         }
3090                         chattingPartner = -1;
3091                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3092                         collective = 0;
3093                     } else
3094                     if(!suppressKibitz) // [HGM] kibitz
3095                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3096                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3097                         int nrDigit = 0, nrAlph = 0, j;
3098                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3099                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3100                         parse[parse_pos] = NULLCHAR;
3101                         // try to be smart: if it does not look like search info, it should go to
3102                         // ICS interaction window after all, not to engine-output window.
3103                         for(j=0; j<parse_pos; j++) { // count letters and digits
3104                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3105                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3106                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3107                         }
3108                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3109                             int depth=0; float score;
3110                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3111                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3112                                 pvInfoList[forwardMostMove-1].depth = depth;
3113                                 pvInfoList[forwardMostMove-1].score = 100*score;
3114                             }
3115                             OutputKibitz(suppressKibitz, parse);
3116                         } else {
3117                             char tmp[MSG_SIZ];
3118                             if(gameMode == IcsObserving) // restore original ICS messages
3119                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3120                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3121                             else
3122                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3123                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3124                             SendToPlayer(tmp, strlen(tmp));
3125                         }
3126                         next_out = i+1; // [HGM] suppress printing in ICS window
3127                     }
3128                     started = STARTED_NONE;
3129                 } else {
3130                     /* Don't match patterns against characters in comment */
3131                     i++;
3132                     continue;
3133                 }
3134             }
3135             if (started == STARTED_CHATTER) {
3136                 if (buf[i] != '\n') {
3137                     /* Don't match patterns against characters in chatter */
3138                     i++;
3139                     continue;
3140                 }
3141                 started = STARTED_NONE;
3142                 if(suppressKibitz) next_out = i+1;
3143             }
3144
3145             /* Kludge to deal with rcmd protocol */
3146             if (firstTime && looking_at(buf, &i, "\001*")) {
3147                 DisplayFatalError(&buf[1], 0, 1);
3148                 continue;
3149             } else {
3150                 firstTime = FALSE;
3151             }
3152
3153             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3154                 ics_type = ICS_ICC;
3155                 ics_prefix = "/";
3156                 if (appData.debugMode)
3157                   fprintf(debugFP, "ics_type %d\n", ics_type);
3158                 continue;
3159             }
3160             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3161                 ics_type = ICS_FICS;
3162                 ics_prefix = "$";
3163                 if (appData.debugMode)
3164                   fprintf(debugFP, "ics_type %d\n", ics_type);
3165                 continue;
3166             }
3167             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3168                 ics_type = ICS_CHESSNET;
3169                 ics_prefix = "/";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174
3175             if (!loggedOn &&
3176                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3177                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3178                  looking_at(buf, &i, "will be \"*\""))) {
3179               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3180               continue;
3181             }
3182
3183             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3184               char buf[MSG_SIZ];
3185               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3186               DisplayIcsInteractionTitle(buf);
3187               have_set_title = TRUE;
3188             }
3189
3190             /* skip finger notes */
3191             if (started == STARTED_NONE &&
3192                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3193                  (buf[i] == '1' && buf[i+1] == '0')) &&
3194                 buf[i+2] == ':' && buf[i+3] == ' ') {
3195               started = STARTED_CHATTER;
3196               i += 3;
3197               continue;
3198             }
3199
3200             oldi = i;
3201             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3202             if(appData.seekGraph) {
3203                 if(soughtPending && MatchSoughtLine(buf+i)) {
3204                     i = strstr(buf+i, "rated") - buf;
3205                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3206                     next_out = leftover_start = i;
3207                     started = STARTED_CHATTER;
3208                     suppressKibitz = TRUE;
3209                     continue;
3210                 }
3211                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3212                         && looking_at(buf, &i, "* ads displayed")) {
3213                     soughtPending = FALSE;
3214                     seekGraphUp = TRUE;
3215                     DrawSeekGraph();
3216                     continue;
3217                 }
3218                 if(appData.autoRefresh) {
3219                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3220                         int s = (ics_type == ICS_ICC); // ICC format differs
3221                         if(seekGraphUp)
3222                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3223                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3224                         looking_at(buf, &i, "*% "); // eat prompt
3225                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3226                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227                         next_out = i; // suppress
3228                         continue;
3229                     }
3230                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3231                         char *p = star_match[0];
3232                         while(*p) {
3233                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3234                             while(*p && *p++ != ' '); // next
3235                         }
3236                         looking_at(buf, &i, "*% "); // eat prompt
3237                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3238                         next_out = i;
3239                         continue;
3240                     }
3241                 }
3242             }
3243
3244             /* skip formula vars */
3245             if (started == STARTED_NONE &&
3246                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3247               started = STARTED_CHATTER;
3248               i += 3;
3249               continue;
3250             }
3251
3252             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3253             if (appData.autoKibitz && started == STARTED_NONE &&
3254                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3255                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3256                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3257                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3258                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3259                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3260                         suppressKibitz = TRUE;
3261                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = i;
3263                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3264                                 && (gameMode == IcsPlayingWhite)) ||
3265                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3266                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3267                             started = STARTED_CHATTER; // own kibitz we simply discard
3268                         else {
3269                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3270                             parse_pos = 0; parse[0] = NULLCHAR;
3271                             savingComment = TRUE;
3272                             suppressKibitz = gameMode != IcsObserving ? 2 :
3273                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3274                         }
3275                         continue;
3276                 } else
3277                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3278                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3279                          && atoi(star_match[0])) {
3280                     // suppress the acknowledgements of our own autoKibitz
3281                     char *p;
3282                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3283                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3284                     SendToPlayer(star_match[0], strlen(star_match[0]));
3285                     if(looking_at(buf, &i, "*% ")) // eat prompt
3286                         suppressKibitz = FALSE;
3287                     next_out = i;
3288                     continue;
3289                 }
3290             } // [HGM] kibitz: end of patch
3291
3292             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3293
3294             // [HGM] chat: intercept tells by users for which we have an open chat window
3295             channel = -1;
3296             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3297                                            looking_at(buf, &i, "* whispers:") ||
3298                                            looking_at(buf, &i, "* kibitzes:") ||
3299                                            looking_at(buf, &i, "* shouts:") ||
3300                                            looking_at(buf, &i, "* c-shouts:") ||
3301                                            looking_at(buf, &i, "--> * ") ||
3302                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3303                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3306                 int p;
3307                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3308                 chattingPartner = -1; collective = 0;
3309
3310                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3311                 for(p=0; p<MAX_CHAT; p++) {
3312                     collective = 1;
3313                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3314                     talker[0] = '['; strcat(talker, "] ");
3315                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3316                     chattingPartner = p; break;
3317                     }
3318                 } else
3319                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(!strcmp("kibitzes", chatPartner[p])) {
3323                         talker[0] = '['; strcat(talker, "] ");
3324                         chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("whispers", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3336                   if(buf[i-8] == '-' && buf[i-3] == 't')
3337                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3338                     collective = 1;
3339                     if(!strcmp("c-shouts", chatPartner[p])) {
3340                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3341                         chattingPartner = p; break;
3342                     }
3343                   }
3344                   if(chattingPartner < 0)
3345                   for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("shouts", chatPartner[p])) {
3348                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3349                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3350                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3351                         chattingPartner = p; break;
3352                     }
3353                   }
3354                 }
3355                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3356                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3357                     talker[0] = 0;
3358                     Colorize(ColorTell, FALSE);
3359                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3360                     collective |= 2;
3361                     chattingPartner = p; break;
3362                 }
3363                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3364                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3365                     started = STARTED_COMMENT;
3366                     parse_pos = 0; parse[0] = NULLCHAR;
3367                     savingComment = 3 + chattingPartner; // counts as TRUE
3368                     if(collective == 3) i = oldi; else {
3369                         suppressKibitz = TRUE;
3370                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3371                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3372                         continue;
3373                     }
3374                 }
3375             } // [HGM] chat: end of patch
3376
3377           backup = i;
3378             if (appData.zippyTalk || appData.zippyPlay) {
3379                 /* [DM] Backup address for color zippy lines */
3380 #if ZIPPY
3381                if (loggedOn == TRUE)
3382                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3383                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3384 #endif
3385             } // [DM] 'else { ' deleted
3386                 if (
3387                     /* Regular tells and says */
3388                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3389                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3390                     looking_at(buf, &i, "* says: ") ||
3391                     /* Don't color "message" or "messages" output */
3392                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3393                     looking_at(buf, &i, "*. * at *:*: ") ||
3394                     looking_at(buf, &i, "--* (*:*): ") ||
3395                     /* Message notifications (same color as tells) */
3396                     looking_at(buf, &i, "* has left a message ") ||
3397                     looking_at(buf, &i, "* just sent you a message:\n") ||
3398                     /* Whispers and kibitzes */
3399                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3400                     looking_at(buf, &i, "* kibitzes: ") ||
3401                     /* Channel tells */
3402                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3403
3404                   if (tkind == 1 && strchr(star_match[0], ':')) {
3405                       /* Avoid "tells you:" spoofs in channels */
3406                      tkind = 3;
3407                   }
3408                   if (star_match[0][0] == NULLCHAR ||
3409                       strchr(star_match[0], ' ') ||
3410                       (tkind == 3 && strchr(star_match[1], ' '))) {
3411                     /* Reject bogus matches */
3412                     i = oldi;
3413                   } else {
3414                     if (appData.colorize) {
3415                       if (oldi > next_out) {
3416                         SendToPlayer(&buf[next_out], oldi - next_out);
3417                         next_out = oldi;
3418                       }
3419                       switch (tkind) {
3420                       case 1:
3421                         Colorize(ColorTell, FALSE);
3422                         curColor = ColorTell;
3423                         break;
3424                       case 2:
3425                         Colorize(ColorKibitz, FALSE);
3426                         curColor = ColorKibitz;
3427                         break;
3428                       case 3:
3429                         p = strrchr(star_match[1], '(');
3430                         if (p == NULL) {
3431                           p = star_match[1];
3432                         } else {
3433                           p++;
3434                         }
3435                         if (atoi(p) == 1) {
3436                           Colorize(ColorChannel1, FALSE);
3437                           curColor = ColorChannel1;
3438                         } else {
3439                           Colorize(ColorChannel, FALSE);
3440                           curColor = ColorChannel;
3441                         }
3442                         break;
3443                       case 5:
3444                         curColor = ColorNormal;
3445                         break;
3446                       }
3447                     }
3448                     if (started == STARTED_NONE && appData.autoComment &&
3449                         (gameMode == IcsObserving ||
3450                          gameMode == IcsPlayingWhite ||
3451                          gameMode == IcsPlayingBlack)) {
3452                       parse_pos = i - oldi;
3453                       memcpy(parse, &buf[oldi], parse_pos);
3454                       parse[parse_pos] = NULLCHAR;
3455                       started = STARTED_COMMENT;
3456                       savingComment = TRUE;
3457                     } else if(collective != 3) {
3458                       started = STARTED_CHATTER;
3459                       savingComment = FALSE;
3460                     }
3461                     loggedOn = TRUE;
3462                     continue;
3463                   }
3464                 }
3465
3466                 if (looking_at(buf, &i, "* s-shouts: ") ||
3467                     looking_at(buf, &i, "* c-shouts: ")) {
3468                     if (appData.colorize) {
3469                         if (oldi > next_out) {
3470                             SendToPlayer(&buf[next_out], oldi - next_out);
3471                             next_out = oldi;
3472                         }
3473                         Colorize(ColorSShout, FALSE);
3474                         curColor = ColorSShout;
3475                     }
3476                     loggedOn = TRUE;
3477                     started = STARTED_CHATTER;
3478                     continue;
3479                 }
3480
3481                 if (looking_at(buf, &i, "--->")) {
3482                     loggedOn = TRUE;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "* shouts: ") ||
3487                     looking_at(buf, &i, "--> ")) {
3488                     if (appData.colorize) {
3489                         if (oldi > next_out) {
3490                             SendToPlayer(&buf[next_out], oldi - next_out);
3491                             next_out = oldi;
3492                         }
3493                         Colorize(ColorShout, FALSE);
3494                         curColor = ColorShout;
3495                     }
3496                     loggedOn = TRUE;
3497                     started = STARTED_CHATTER;
3498                     continue;
3499                 }
3500
3501                 if (looking_at( buf, &i, "Challenge:")) {
3502                     if (appData.colorize) {
3503                         if (oldi > next_out) {
3504                             SendToPlayer(&buf[next_out], oldi - next_out);
3505                             next_out = oldi;
3506                         }
3507                         Colorize(ColorChallenge, FALSE);
3508                         curColor = ColorChallenge;
3509                     }
3510                     loggedOn = TRUE;
3511                     continue;
3512                 }
3513
3514                 if (looking_at(buf, &i, "* offers you") ||
3515                     looking_at(buf, &i, "* offers to be") ||
3516                     looking_at(buf, &i, "* would like to") ||
3517                     looking_at(buf, &i, "* requests to") ||
3518                     looking_at(buf, &i, "Your opponent offers") ||
3519                     looking_at(buf, &i, "Your opponent requests")) {
3520
3521                     if (appData.colorize) {
3522                         if (oldi > next_out) {
3523                             SendToPlayer(&buf[next_out], oldi - next_out);
3524                             next_out = oldi;
3525                         }
3526                         Colorize(ColorRequest, FALSE);
3527                         curColor = ColorRequest;
3528                     }
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* (*) seeking")) {
3533                     if (appData.colorize) {
3534                         if (oldi > next_out) {
3535                             SendToPlayer(&buf[next_out], oldi - next_out);
3536                             next_out = oldi;
3537                         }
3538                         Colorize(ColorSeek, FALSE);
3539                         curColor = ColorSeek;
3540                     }
3541                     continue;
3542             }
3543
3544           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3545
3546             if (looking_at(buf, &i, "\\   ")) {
3547                 if (prevColor != ColorNormal) {
3548                     if (oldi > next_out) {
3549                         SendToPlayer(&buf[next_out], oldi - next_out);
3550                         next_out = oldi;
3551                     }
3552                     Colorize(prevColor, TRUE);
3553                     curColor = prevColor;
3554                 }
3555                 if (savingComment) {
3556                     parse_pos = i - oldi;
3557                     memcpy(parse, &buf[oldi], parse_pos);
3558                     parse[parse_pos] = NULLCHAR;
3559                     started = STARTED_COMMENT;
3560                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3561                         chattingPartner = savingComment - 3; // kludge to remember the box
3562                 } else {
3563                     started = STARTED_CHATTER;
3564                 }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Black Strength :") ||
3569                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3570                 looking_at(buf, &i, "<10>") ||
3571                 looking_at(buf, &i, "#@#")) {
3572                 /* Wrong board style */
3573                 loggedOn = TRUE;
3574                 SendToICS(ics_prefix);
3575                 SendToICS("set style 12\n");
3576                 SendToICS(ics_prefix);
3577                 SendToICS("refresh\n");
3578                 continue;
3579             }
3580
3581             if (looking_at(buf, &i, "login:")) {
3582               if (!have_sent_ICS_logon) {
3583                 if(ICSInitScript())
3584                   have_sent_ICS_logon = 1;
3585                 else // no init script was found
3586                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3587               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3588                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3589               }
3590                 continue;
3591             }
3592
3593             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3594                 (looking_at(buf, &i, "\n<12> ") ||
3595                  looking_at(buf, &i, "<12> "))) {
3596                 loggedOn = TRUE;
3597                 if (oldi > next_out) {
3598                     SendToPlayer(&buf[next_out], oldi - next_out);
3599                 }
3600                 next_out = i;
3601                 started = STARTED_BOARD;
3602                 parse_pos = 0;
3603                 continue;
3604             }
3605
3606             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3607                 looking_at(buf, &i, "<b1> ")) {
3608                 if (oldi > next_out) {
3609                     SendToPlayer(&buf[next_out], oldi - next_out);
3610                 }
3611                 next_out = i;
3612                 started = STARTED_HOLDINGS;
3613                 parse_pos = 0;
3614                 continue;
3615             }
3616
3617             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3618                 loggedOn = TRUE;
3619                 /* Header for a move list -- first line */
3620
3621                 switch (ics_getting_history) {
3622                   case H_FALSE:
3623                     switch (gameMode) {
3624                       case IcsIdle:
3625                       case BeginningOfGame:
3626                         /* User typed "moves" or "oldmoves" while we
3627                            were idle.  Pretend we asked for these
3628                            moves and soak them up so user can step
3629                            through them and/or save them.
3630                            */
3631                         Reset(FALSE, TRUE);
3632                         gameMode = IcsObserving;
3633                         ModeHighlight();
3634                         ics_gamenum = -1;
3635                         ics_getting_history = H_GOT_UNREQ_HEADER;
3636                         break;
3637                       case EditGame: /*?*/
3638                       case EditPosition: /*?*/
3639                         /* Should above feature work in these modes too? */
3640                         /* For now it doesn't */
3641                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3642                         break;
3643                       default:
3644                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3645                         break;
3646                     }
3647                     break;
3648                   case H_REQUESTED:
3649                     /* Is this the right one? */
3650                     if (gameInfo.white && gameInfo.black &&
3651                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3652                         strcmp(gameInfo.black, star_match[2]) == 0) {
3653                         /* All is well */
3654                         ics_getting_history = H_GOT_REQ_HEADER;
3655                     }
3656                     break;
3657                   case H_GOT_REQ_HEADER:
3658                   case H_GOT_UNREQ_HEADER:
3659                   case H_GOT_UNWANTED_HEADER:
3660                   case H_GETTING_MOVES:
3661                     /* Should not happen */
3662                     DisplayError(_("Error gathering move list: two headers"), 0);
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666
3667                 /* Save player ratings into gameInfo if needed */
3668                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3669                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3670                     (gameInfo.whiteRating == -1 ||
3671                      gameInfo.blackRating == -1)) {
3672
3673                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3674                     gameInfo.blackRating = string_to_rating(star_match[3]);
3675                     if (appData.debugMode)
3676                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3677                               gameInfo.whiteRating, gameInfo.blackRating);
3678                 }
3679                 continue;
3680             }
3681
3682             if (looking_at(buf, &i,
3683               "* * match, initial time: * minute*, increment: * second")) {
3684                 /* Header for a move list -- second line */
3685                 /* Initial board will follow if this is a wild game */
3686                 if (gameInfo.event != NULL) free(gameInfo.event);
3687                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3688                 gameInfo.event = StrSave(str);
3689                 /* [HGM] we switched variant. Translate boards if needed. */
3690                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3691                 continue;
3692             }
3693
3694             if (looking_at(buf, &i, "Move  ")) {
3695                 /* Beginning of a move list */
3696                 switch (ics_getting_history) {
3697                   case H_FALSE:
3698                     /* Normally should not happen */
3699                     /* Maybe user hit reset while we were parsing */
3700                     break;
3701                   case H_REQUESTED:
3702                     /* Happens if we are ignoring a move list that is not
3703                      * the one we just requested.  Common if the user
3704                      * tries to observe two games without turning off
3705                      * getMoveList */
3706                     break;
3707                   case H_GETTING_MOVES:
3708                     /* Should not happen */
3709                     DisplayError(_("Error gathering move list: nested"), 0);
3710                     ics_getting_history = H_FALSE;
3711                     break;
3712                   case H_GOT_REQ_HEADER:
3713                     ics_getting_history = H_GETTING_MOVES;
3714                     started = STARTED_MOVES;
3715                     parse_pos = 0;
3716                     if (oldi > next_out) {
3717                         SendToPlayer(&buf[next_out], oldi - next_out);
3718                     }
3719                     break;
3720                   case H_GOT_UNREQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES_NOHIDE;
3723                     parse_pos = 0;
3724                     break;
3725                   case H_GOT_UNWANTED_HEADER:
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                 }
3729                 continue;
3730             }
3731
3732             if (looking_at(buf, &i, "% ") ||
3733                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3734                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3735                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3736                     soughtPending = FALSE;
3737                     seekGraphUp = TRUE;
3738                     DrawSeekGraph();
3739                 }
3740                 if(suppressKibitz) next_out = i;
3741                 savingComment = FALSE;
3742                 suppressKibitz = 0;
3743                 switch (started) {
3744                   case STARTED_MOVES:
3745                   case STARTED_MOVES_NOHIDE:
3746                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3747                     parse[parse_pos + i - oldi] = NULLCHAR;
3748                     ParseGameHistory(parse);
3749 #if ZIPPY
3750                     if (appData.zippyPlay && first.initDone) {
3751                         FeedMovesToProgram(&first, forwardMostMove);
3752                         if (gameMode == IcsPlayingWhite) {
3753                             if (WhiteOnMove(forwardMostMove)) {
3754                                 if (first.sendTime) {
3755                                   if (first.useColors) {
3756                                     SendToProgram("black\n", &first);
3757                                   }
3758                                   SendTimeRemaining(&first, TRUE);
3759                                 }
3760                                 if (first.useColors) {
3761                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3762                                 }
3763                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3764                                 first.maybeThinking = TRUE;
3765                             } else {
3766                                 if (first.usePlayother) {
3767                                   if (first.sendTime) {
3768                                     SendTimeRemaining(&first, TRUE);
3769                                   }
3770                                   SendToProgram("playother\n", &first);
3771                                   firstMove = FALSE;
3772                                 } else {
3773                                   firstMove = TRUE;
3774                                 }
3775                             }
3776                         } else if (gameMode == IcsPlayingBlack) {
3777                             if (!WhiteOnMove(forwardMostMove)) {
3778                                 if (first.sendTime) {
3779                                   if (first.useColors) {
3780                                     SendToProgram("white\n", &first);
3781                                   }
3782                                   SendTimeRemaining(&first, FALSE);
3783                                 }
3784                                 if (first.useColors) {
3785                                   SendToProgram("black\n", &first);
3786                                 }
3787                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3788                                 first.maybeThinking = TRUE;
3789                             } else {
3790                                 if (first.usePlayother) {
3791                                   if (first.sendTime) {
3792                                     SendTimeRemaining(&first, FALSE);
3793                                   }
3794                                   SendToProgram("playother\n", &first);
3795                                   firstMove = FALSE;
3796                                 } else {
3797                                   firstMove = TRUE;
3798                                 }
3799                             }
3800                         }
3801                     }
3802 #endif
3803                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3804                         /* Moves came from oldmoves or moves command
3805                            while we weren't doing anything else.
3806                            */
3807                         currentMove = forwardMostMove;
3808                         ClearHighlights();/*!!could figure this out*/
3809                         flipView = appData.flipView;
3810                         DrawPosition(TRUE, boards[currentMove]);
3811                         DisplayBothClocks();
3812                         snprintf(str, MSG_SIZ, "%s %s %s",
3813                                 gameInfo.white, _("vs."),  gameInfo.black);
3814                         DisplayTitle(str);
3815                         gameMode = IcsIdle;
3816                     } else {
3817                         /* Moves were history of an active game */
3818                         if (gameInfo.resultDetails != NULL) {
3819                             free(gameInfo.resultDetails);
3820                             gameInfo.resultDetails = NULL;
3821                         }
3822                     }
3823                     HistorySet(parseList, backwardMostMove,
3824                                forwardMostMove, currentMove-1);
3825                     DisplayMove(currentMove - 1);
3826                     if (started == STARTED_MOVES) next_out = i;
3827                     started = STARTED_NONE;
3828                     ics_getting_history = H_FALSE;
3829                     break;
3830
3831                   case STARTED_OBSERVE:
3832                     started = STARTED_NONE;
3833                     SendToICS(ics_prefix);
3834                     SendToICS("refresh\n");
3835                     break;
3836
3837                   default:
3838                     break;
3839                 }
3840                 if(bookHit) { // [HGM] book: simulate book reply
3841                     static char bookMove[MSG_SIZ]; // a bit generous?
3842
3843                     programStats.nodes = programStats.depth = programStats.time =
3844                     programStats.score = programStats.got_only_move = 0;
3845                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3846
3847                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3848                     strcat(bookMove, bookHit);
3849                     HandleMachineMove(bookMove, &first);
3850                 }
3851                 continue;
3852             }
3853
3854             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3855                  started == STARTED_HOLDINGS ||
3856                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3857                 /* Accumulate characters in move list or board */
3858                 parse[parse_pos++] = buf[i];
3859             }
3860
3861             /* Start of game messages.  Mostly we detect start of game
3862                when the first board image arrives.  On some versions
3863                of the ICS, though, we need to do a "refresh" after starting
3864                to observe in order to get the current board right away. */
3865             if (looking_at(buf, &i, "Adding game * to observation list")) {
3866                 started = STARTED_OBSERVE;
3867                 continue;
3868             }
3869
3870             /* Handle auto-observe */
3871             if (appData.autoObserve &&
3872                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3873                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3874                 char *player;
3875                 /* Choose the player that was highlighted, if any. */
3876                 if (star_match[0][0] == '\033' ||
3877                     star_match[1][0] != '\033') {
3878                     player = star_match[0];
3879                 } else {
3880                     player = star_match[2];
3881                 }
3882                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3883                         ics_prefix, StripHighlightAndTitle(player));
3884                 SendToICS(str);
3885
3886                 /* Save ratings from notify string */
3887                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3888                 player1Rating = string_to_rating(star_match[1]);
3889                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3890                 player2Rating = string_to_rating(star_match[3]);
3891
3892                 if (appData.debugMode)
3893                   fprintf(debugFP,
3894                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3895                           player1Name, player1Rating,
3896                           player2Name, player2Rating);
3897
3898                 continue;
3899             }
3900
3901             /* Deal with automatic examine mode after a game,
3902                and with IcsObserving -> IcsExamining transition */
3903             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3904                 looking_at(buf, &i, "has made you an examiner of game *")) {
3905
3906                 int gamenum = atoi(star_match[0]);
3907                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3908                     gamenum == ics_gamenum) {
3909                     /* We were already playing or observing this game;
3910                        no need to refetch history */
3911                     gameMode = IcsExamining;
3912                     if (pausing) {
3913                         pauseExamForwardMostMove = forwardMostMove;
3914                     } else if (currentMove < forwardMostMove) {
3915                         ForwardInner(forwardMostMove);
3916                     }
3917                 } else {
3918                     /* I don't think this case really can happen */
3919                     SendToICS(ics_prefix);
3920                     SendToICS("refresh\n");
3921                 }
3922                 continue;
3923             }
3924
3925             /* Error messages */
3926 //          if (ics_user_moved) {
3927             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3928                 if (looking_at(buf, &i, "Illegal move") ||
3929                     looking_at(buf, &i, "Not a legal move") ||
3930                     looking_at(buf, &i, "Your king is in check") ||
3931                     looking_at(buf, &i, "It isn't your turn") ||
3932                     looking_at(buf, &i, "It is not your move")) {
3933                     /* Illegal move */
3934                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3935                         currentMove = forwardMostMove-1;
3936                         DisplayMove(currentMove - 1); /* before DMError */
3937                         DrawPosition(FALSE, boards[currentMove]);
3938                         SwitchClocks(forwardMostMove-1); // [HGM] race
3939                         DisplayBothClocks();
3940                     }
3941                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3942                     ics_user_moved = 0;
3943                     continue;
3944                 }
3945             }
3946
3947             if (looking_at(buf, &i, "still have time") ||
3948                 looking_at(buf, &i, "not out of time") ||
3949                 looking_at(buf, &i, "either player is out of time") ||
3950                 looking_at(buf, &i, "has timeseal; checking")) {
3951                 /* We must have called his flag a little too soon */
3952                 whiteFlag = blackFlag = FALSE;
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "added * seconds to") ||
3957                 looking_at(buf, &i, "seconds were added to")) {
3958                 /* Update the clocks */
3959                 SendToICS(ics_prefix);
3960                 SendToICS("refresh\n");
3961                 continue;
3962             }
3963
3964             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3965                 ics_clock_paused = TRUE;
3966                 StopClocks();
3967                 continue;
3968             }
3969
3970             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3971                 ics_clock_paused = FALSE;
3972                 StartClocks();
3973                 continue;
3974             }
3975
3976             /* Grab player ratings from the Creating: message.
3977                Note we have to check for the special case when
3978                the ICS inserts things like [white] or [black]. */
3979             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3980                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3981                 /* star_matches:
3982                    0    player 1 name (not necessarily white)
3983                    1    player 1 rating
3984                    2    empty, white, or black (IGNORED)
3985                    3    player 2 name (not necessarily black)
3986                    4    player 2 rating
3987
3988                    The names/ratings are sorted out when the game
3989                    actually starts (below).
3990                 */
3991                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3992                 player1Rating = string_to_rating(star_match[1]);
3993                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3994                 player2Rating = string_to_rating(star_match[4]);
3995
3996                 if (appData.debugMode)
3997                   fprintf(debugFP,
3998                           "Ratings from 'Creating:' %s %d, %s %d\n",
3999                           player1Name, player1Rating,
4000                           player2Name, player2Rating);
4001
4002                 continue;
4003             }
4004
4005             /* Improved generic start/end-of-game messages */
4006             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4007                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4008                 /* If tkind == 0: */
4009                 /* star_match[0] is the game number */
4010                 /*           [1] is the white player's name */
4011                 /*           [2] is the black player's name */
4012                 /* For end-of-game: */
4013                 /*           [3] is the reason for the game end */
4014                 /*           [4] is a PGN end game-token, preceded by " " */
4015                 /* For start-of-game: */
4016                 /*           [3] begins with "Creating" or "Continuing" */
4017                 /*           [4] is " *" or empty (don't care). */
4018                 int gamenum = atoi(star_match[0]);
4019                 char *whitename, *blackname, *why, *endtoken;
4020                 ChessMove endtype = EndOfFile;
4021
4022                 if (tkind == 0) {
4023                   whitename = star_match[1];
4024                   blackname = star_match[2];
4025                   why = star_match[3];
4026                   endtoken = star_match[4];
4027                 } else {
4028                   whitename = star_match[1];
4029                   blackname = star_match[3];
4030                   why = star_match[5];
4031                   endtoken = star_match[6];
4032                 }
4033
4034                 /* Game start messages */
4035                 if (strncmp(why, "Creating ", 9) == 0 ||
4036                     strncmp(why, "Continuing ", 11) == 0) {
4037                     gs_gamenum = gamenum;
4038                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4039                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4040                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4041 #if ZIPPY
4042                     if (appData.zippyPlay) {
4043                         ZippyGameStart(whitename, blackname);
4044                     }
4045 #endif /*ZIPPY*/
4046                     partnerBoardValid = FALSE; // [HGM] bughouse
4047                     continue;
4048                 }
4049
4050                 /* Game end messages */
4051                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4052                     ics_gamenum != gamenum) {
4053                     continue;
4054                 }
4055                 while (endtoken[0] == ' ') endtoken++;
4056                 switch (endtoken[0]) {
4057                   case '*':
4058                   default:
4059                     endtype = GameUnfinished;
4060                     break;
4061                   case '0':
4062                     endtype = BlackWins;
4063                     break;
4064                   case '1':
4065                     if (endtoken[1] == '/')
4066                       endtype = GameIsDrawn;
4067                     else
4068                       endtype = WhiteWins;
4069                     break;
4070                 }
4071                 GameEnds(endtype, why, GE_ICS);
4072 #if ZIPPY
4073                 if (appData.zippyPlay && first.initDone) {
4074                     ZippyGameEnd(endtype, why);
4075                     if (first.pr == NoProc) {
4076                       /* Start the next process early so that we'll
4077                          be ready for the next challenge */
4078                       StartChessProgram(&first);
4079                     }
4080                     /* Send "new" early, in case this command takes
4081                        a long time to finish, so that we'll be ready
4082                        for the next challenge. */
4083                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4084                     Reset(TRUE, TRUE);
4085                 }
4086 #endif /*ZIPPY*/
4087                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4088                 continue;
4089             }
4090
4091             if (looking_at(buf, &i, "Removing game * from observation") ||
4092                 looking_at(buf, &i, "no longer observing game *") ||
4093                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4094                 if (gameMode == IcsObserving &&
4095                     atoi(star_match[0]) == ics_gamenum)
4096                   {
4097                       /* icsEngineAnalyze */
4098                       if (appData.icsEngineAnalyze) {
4099                             ExitAnalyzeMode();
4100                             ModeHighlight();
4101                       }
4102                       StopClocks();
4103                       gameMode = IcsIdle;
4104                       ics_gamenum = -1;
4105                       ics_user_moved = FALSE;
4106                   }
4107                 continue;
4108             }
4109
4110             if (looking_at(buf, &i, "no longer examining game *")) {
4111                 if (gameMode == IcsExamining &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       gameMode = IcsIdle;
4115                       ics_gamenum = -1;
4116                       ics_user_moved = FALSE;
4117                   }
4118                 continue;
4119             }
4120
4121             /* Advance leftover_start past any newlines we find,
4122                so only partial lines can get reparsed */
4123             if (looking_at(buf, &i, "\n")) {
4124                 prevColor = curColor;
4125                 if (curColor != ColorNormal) {
4126                     if (oldi > next_out) {
4127                         SendToPlayer(&buf[next_out], oldi - next_out);
4128                         next_out = oldi;
4129                     }
4130                     Colorize(ColorNormal, FALSE);
4131                     curColor = ColorNormal;
4132                 }
4133                 if (started == STARTED_BOARD) {
4134                     started = STARTED_NONE;
4135                     parse[parse_pos] = NULLCHAR;
4136                     ParseBoard12(parse);
4137                     ics_user_moved = 0;
4138
4139                     /* Send premove here */
4140                     if (appData.premove) {
4141                       char str[MSG_SIZ];
4142                       if (currentMove == 0 &&
4143                           gameMode == IcsPlayingWhite &&
4144                           appData.premoveWhite) {
4145                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4146                         if (appData.debugMode)
4147                           fprintf(debugFP, "Sending premove:\n");
4148                         SendToICS(str);
4149                       } else if (currentMove == 1 &&
4150                                  gameMode == IcsPlayingBlack &&
4151                                  appData.premoveBlack) {
4152                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                         SendToICS(str);
4156                       } else if (gotPremove) {
4157                         gotPremove = 0;
4158                         ClearPremoveHighlights();
4159                         if (appData.debugMode)
4160                           fprintf(debugFP, "Sending premove:\n");
4161                           UserMoveEvent(premoveFromX, premoveFromY,
4162                                         premoveToX, premoveToY,
4163                                         premovePromoChar);
4164                       }
4165                     }
4166
4167                     /* Usually suppress following prompt */
4168                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4169                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4170                         if (looking_at(buf, &i, "*% ")) {
4171                             savingComment = FALSE;
4172                             suppressKibitz = 0;
4173                         }
4174                     }
4175                     next_out = i;
4176                 } else if (started == STARTED_HOLDINGS) {
4177                     int gamenum;
4178                     char new_piece[MSG_SIZ];
4179                     started = STARTED_NONE;
4180                     parse[parse_pos] = NULLCHAR;
4181                     if (appData.debugMode)
4182                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4183                                                         parse, currentMove);
4184                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4185                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4186                         if (gameInfo.variant == VariantNormal) {
4187                           /* [HGM] We seem to switch variant during a game!
4188                            * Presumably no holdings were displayed, so we have
4189                            * to move the position two files to the right to
4190                            * create room for them!
4191                            */
4192                           VariantClass newVariant;
4193                           switch(gameInfo.boardWidth) { // base guess on board width
4194                                 case 9:  newVariant = VariantShogi; break;
4195                                 case 10: newVariant = VariantGreat; break;
4196                                 default: newVariant = VariantCrazyhouse; break;
4197                           }
4198                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4199                           /* Get a move list just to see the header, which
4200                              will tell us whether this is really bug or zh */
4201                           if (ics_getting_history == H_FALSE) {
4202                             ics_getting_history = H_REQUESTED;
4203                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4204                             SendToICS(str);
4205                           }
4206                         }
4207                         new_piece[0] = NULLCHAR;
4208                         sscanf(parse, "game %d white [%s black [%s <- %s",
4209                                &gamenum, white_holding, black_holding,
4210                                new_piece);
4211                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4212                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4213                         /* [HGM] copy holdings to board holdings area */
4214                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4215                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4216                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4217 #if ZIPPY
4218                         if (appData.zippyPlay && first.initDone) {
4219                             ZippyHoldings(white_holding, black_holding,
4220                                           new_piece);
4221                         }
4222 #endif /*ZIPPY*/
4223                         if (tinyLayout || smallLayout) {
4224                             char wh[16], bh[16];
4225                             PackHolding(wh, white_holding);
4226                             PackHolding(bh, black_holding);
4227                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4228                                     gameInfo.white, gameInfo.black);
4229                         } else {
4230                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4231                                     gameInfo.white, white_holding, _("vs."),
4232                                     gameInfo.black, black_holding);
4233                         }
4234                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4235                         DrawPosition(FALSE, boards[currentMove]);
4236                         DisplayTitle(str);
4237                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4238                         sscanf(parse, "game %d white [%s black [%s <- %s",
4239                                &gamenum, white_holding, black_holding,
4240                                new_piece);
4241                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4242                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4243                         /* [HGM] copy holdings to partner-board holdings area */
4244                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4245                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4246                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4247                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4249                       }
4250                     }
4251                     /* Suppress following prompt */
4252                     if (looking_at(buf, &i, "*% ")) {
4253                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4254                         savingComment = FALSE;
4255                         suppressKibitz = 0;
4256                     }
4257                     next_out = i;
4258                 }
4259                 continue;
4260             }
4261
4262             i++;                /* skip unparsed character and loop back */
4263         }
4264
4265         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4266 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4267 //          SendToPlayer(&buf[next_out], i - next_out);
4268             started != STARTED_HOLDINGS && leftover_start > next_out) {
4269             SendToPlayer(&buf[next_out], leftover_start - next_out);
4270             next_out = i;
4271         }
4272
4273         leftover_len = buf_len - leftover_start;
4274         /* if buffer ends with something we couldn't parse,
4275            reparse it after appending the next read */
4276
4277     } else if (count == 0) {
4278         RemoveInputSource(isr);
4279         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4280     } else {
4281         DisplayFatalError(_("Error reading from ICS"), error, 1);
4282     }
4283 }
4284
4285
4286 /* Board style 12 looks like this:
4287
4288    <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
4289
4290  * The "<12> " is stripped before it gets to this routine.  The two
4291  * trailing 0's (flip state and clock ticking) are later addition, and
4292  * some chess servers may not have them, or may have only the first.
4293  * Additional trailing fields may be added in the future.
4294  */
4295
4296 #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"
4297
4298 #define RELATION_OBSERVING_PLAYED    0
4299 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4300 #define RELATION_PLAYING_MYMOVE      1
4301 #define RELATION_PLAYING_NOTMYMOVE  -1
4302 #define RELATION_EXAMINING           2
4303 #define RELATION_ISOLATED_BOARD     -3
4304 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4305
4306 void
4307 ParseBoard12 (char *string)
4308 {
4309 #if ZIPPY
4310     int i, takeback;
4311     char *bookHit = NULL; // [HGM] book
4312 #endif
4313     GameMode newGameMode;
4314     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4315     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4316     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4317     char to_play, board_chars[200];
4318     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4319     char black[32], white[32];
4320     Board board;
4321     int prevMove = currentMove;
4322     int ticking = 2;
4323     ChessMove moveType;
4324     int fromX, fromY, toX, toY;
4325     char promoChar;
4326     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4327     Boolean weird = FALSE, reqFlag = FALSE;
4328
4329     fromX = fromY = toX = toY = -1;
4330
4331     newGame = FALSE;
4332
4333     if (appData.debugMode)
4334       fprintf(debugFP, "Parsing board: %s\n", string);
4335
4336     move_str[0] = NULLCHAR;
4337     elapsed_time[0] = NULLCHAR;
4338     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4339         int  i = 0, j;
4340         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4341             if(string[i] == ' ') { ranks++; files = 0; }
4342             else files++;
4343             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4344             i++;
4345         }
4346         for(j = 0; j <i; j++) board_chars[j] = string[j];
4347         board_chars[i] = '\0';
4348         string += i + 1;
4349     }
4350     n = sscanf(string, PATTERN, &to_play, &double_push,
4351                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4352                &gamenum, white, black, &relation, &basetime, &increment,
4353                &white_stren, &black_stren, &white_time, &black_time,
4354                &moveNum, str, elapsed_time, move_str, &ics_flip,
4355                &ticking);
4356
4357     if (n < 21) {
4358         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4359         DisplayError(str, 0);
4360         return;
4361     }
4362
4363     /* Convert the move number to internal form */
4364     moveNum = (moveNum - 1) * 2;
4365     if (to_play == 'B') moveNum++;
4366     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4367       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4368                         0, 1);
4369       return;
4370     }
4371
4372     switch (relation) {
4373       case RELATION_OBSERVING_PLAYED:
4374       case RELATION_OBSERVING_STATIC:
4375         if (gamenum == -1) {
4376             /* Old ICC buglet */
4377             relation = RELATION_OBSERVING_STATIC;
4378         }
4379         newGameMode = IcsObserving;
4380         break;
4381       case RELATION_PLAYING_MYMOVE:
4382       case RELATION_PLAYING_NOTMYMOVE:
4383         newGameMode =
4384           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4385             IcsPlayingWhite : IcsPlayingBlack;
4386         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4387         break;
4388       case RELATION_EXAMINING:
4389         newGameMode = IcsExamining;
4390         break;
4391       case RELATION_ISOLATED_BOARD:
4392       default:
4393         /* Just display this board.  If user was doing something else,
4394            we will forget about it until the next board comes. */
4395         newGameMode = IcsIdle;
4396         break;
4397       case RELATION_STARTING_POSITION:
4398         newGameMode = gameMode;
4399         break;
4400     }
4401
4402     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4403         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4404          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4405       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4406       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4407       static int lastBgGame = -1;
4408       char *toSqr;
4409       for (k = 0; k < ranks; k++) {
4410         for (j = 0; j < files; j++)
4411           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4412         if(gameInfo.holdingsWidth > 1) {
4413              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4414              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4415         }
4416       }
4417       CopyBoard(partnerBoard, board);
4418       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4419         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4420         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4421       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4422       if(toSqr = strchr(str, '-')) {
4423         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4424         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4425       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4426       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4427       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4428       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4429       if(twoBoards) {
4430           DisplayWhiteClock(white_time*fac, to_play == 'W');
4431           DisplayBlackClock(black_time*fac, to_play != 'W');
4432           activePartner = to_play;
4433           if(gamenum != lastBgGame) {
4434               char buf[MSG_SIZ];
4435               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4436               DisplayTitle(buf);
4437           }
4438           lastBgGame = gamenum;
4439           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4440                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4441       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4442                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4443       if(!twoBoards) DisplayMessage(partnerStatus, "");
4444         partnerBoardValid = TRUE;
4445       return;
4446     }
4447
4448     if(appData.dualBoard && appData.bgObserve) {
4449         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4450             SendToICS(ics_prefix), SendToICS("pobserve\n");
4451         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4452             char buf[MSG_SIZ];
4453             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4454             SendToICS(buf);
4455         }
4456     }
4457
4458     /* Modify behavior for initial board display on move listing
4459        of wild games.
4460        */
4461     switch (ics_getting_history) {
4462       case H_FALSE:
4463       case H_REQUESTED:
4464         break;
4465       case H_GOT_REQ_HEADER:
4466       case H_GOT_UNREQ_HEADER:
4467         /* This is the initial position of the current game */
4468         gamenum = ics_gamenum;
4469         moveNum = 0;            /* old ICS bug workaround */
4470         if (to_play == 'B') {
4471           startedFromSetupPosition = TRUE;
4472           blackPlaysFirst = TRUE;
4473           moveNum = 1;
4474           if (forwardMostMove == 0) forwardMostMove = 1;
4475           if (backwardMostMove == 0) backwardMostMove = 1;
4476           if (currentMove == 0) currentMove = 1;
4477         }
4478         newGameMode = gameMode;
4479         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4480         break;
4481       case H_GOT_UNWANTED_HEADER:
4482         /* This is an initial board that we don't want */
4483         return;
4484       case H_GETTING_MOVES:
4485         /* Should not happen */
4486         DisplayError(_("Error gathering move list: extra board"), 0);
4487         ics_getting_history = H_FALSE;
4488         return;
4489     }
4490
4491    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4492                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4493                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4494      /* [HGM] We seem to have switched variant unexpectedly
4495       * Try to guess new variant from board size
4496       */
4497           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4498           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4499           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4500           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4501           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4502           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4503           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4504           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4505           /* Get a move list just to see the header, which
4506              will tell us whether this is really bug or zh */
4507           if (ics_getting_history == H_FALSE) {
4508             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4509             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4510             SendToICS(str);
4511           }
4512     }
4513
4514     /* Take action if this is the first board of a new game, or of a
4515        different game than is currently being displayed.  */
4516     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4517         relation == RELATION_ISOLATED_BOARD) {
4518
4519         /* Forget the old game and get the history (if any) of the new one */
4520         if (gameMode != BeginningOfGame) {
4521           Reset(TRUE, TRUE);
4522         }
4523         newGame = TRUE;
4524         if (appData.autoRaiseBoard) BoardToTop();
4525         prevMove = -3;
4526         if (gamenum == -1) {
4527             newGameMode = IcsIdle;
4528         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4529                    appData.getMoveList && !reqFlag) {
4530             /* Need to get game history */
4531             ics_getting_history = H_REQUESTED;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534         }
4535
4536         /* Initially flip the board to have black on the bottom if playing
4537            black or if the ICS flip flag is set, but let the user change
4538            it with the Flip View button. */
4539         flipView = appData.autoFlipView ?
4540           (newGameMode == IcsPlayingBlack) || ics_flip :
4541           appData.flipView;
4542
4543         /* Done with values from previous mode; copy in new ones */
4544         gameMode = newGameMode;
4545         ModeHighlight();
4546         ics_gamenum = gamenum;
4547         if (gamenum == gs_gamenum) {
4548             int klen = strlen(gs_kind);
4549             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4550             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4551             gameInfo.event = StrSave(str);
4552         } else {
4553             gameInfo.event = StrSave("ICS game");
4554         }
4555         gameInfo.site = StrSave(appData.icsHost);
4556         gameInfo.date = PGNDate();
4557         gameInfo.round = StrSave("-");
4558         gameInfo.white = StrSave(white);
4559         gameInfo.black = StrSave(black);
4560         timeControl = basetime * 60 * 1000;
4561         timeControl_2 = 0;
4562         timeIncrement = increment * 1000;
4563         movesPerSession = 0;
4564         gameInfo.timeControl = TimeControlTagValue();
4565         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4566   if (appData.debugMode) {
4567     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4568     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4569     setbuf(debugFP, NULL);
4570   }
4571
4572         gameInfo.outOfBook = NULL;
4573
4574         /* Do we have the ratings? */
4575         if (strcmp(player1Name, white) == 0 &&
4576             strcmp(player2Name, black) == 0) {
4577             if (appData.debugMode)
4578               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4579                       player1Rating, player2Rating);
4580             gameInfo.whiteRating = player1Rating;
4581             gameInfo.blackRating = player2Rating;
4582         } else if (strcmp(player2Name, white) == 0 &&
4583                    strcmp(player1Name, black) == 0) {
4584             if (appData.debugMode)
4585               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586                       player2Rating, player1Rating);
4587             gameInfo.whiteRating = player2Rating;
4588             gameInfo.blackRating = player1Rating;
4589         }
4590         player1Name[0] = player2Name[0] = NULLCHAR;
4591
4592         /* Silence shouts if requested */
4593         if (appData.quietPlay &&
4594             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4595             SendToICS(ics_prefix);
4596             SendToICS("set shout 0\n");
4597         }
4598     }
4599
4600     /* Deal with midgame name changes */
4601     if (!newGame) {
4602         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4603             if (gameInfo.white) free(gameInfo.white);
4604             gameInfo.white = StrSave(white);
4605         }
4606         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4607             if (gameInfo.black) free(gameInfo.black);
4608             gameInfo.black = StrSave(black);
4609         }
4610     }
4611
4612     /* Throw away game result if anything actually changes in examine mode */
4613     if (gameMode == IcsExamining && !newGame) {
4614         gameInfo.result = GameUnfinished;
4615         if (gameInfo.resultDetails != NULL) {
4616             free(gameInfo.resultDetails);
4617             gameInfo.resultDetails = NULL;
4618         }
4619     }
4620
4621     /* In pausing && IcsExamining mode, we ignore boards coming
4622        in if they are in a different variation than we are. */
4623     if (pauseExamInvalid) return;
4624     if (pausing && gameMode == IcsExamining) {
4625         if (moveNum <= pauseExamForwardMostMove) {
4626             pauseExamInvalid = TRUE;
4627             forwardMostMove = pauseExamForwardMostMove;
4628             return;
4629         }
4630     }
4631
4632   if (appData.debugMode) {
4633     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4634   }
4635     /* Parse the board */
4636     for (k = 0; k < ranks; k++) {
4637       for (j = 0; j < files; j++)
4638         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4639       if(gameInfo.holdingsWidth > 1) {
4640            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4641            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4642       }
4643     }
4644     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4645       board[5][BOARD_RGHT+1] = WhiteAngel;
4646       board[6][BOARD_RGHT+1] = WhiteMarshall;
4647       board[1][0] = BlackMarshall;
4648       board[2][0] = BlackAngel;
4649       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4650     }
4651     CopyBoard(boards[moveNum], board);
4652     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4653     if (moveNum == 0) {
4654         startedFromSetupPosition =
4655           !CompareBoards(board, initialPosition);
4656         if(startedFromSetupPosition)
4657             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4658     }
4659
4660     /* [HGM] Set castling rights. Take the outermost Rooks,
4661        to make it also work for FRC opening positions. Note that board12
4662        is really defective for later FRC positions, as it has no way to
4663        indicate which Rook can castle if they are on the same side of King.
4664        For the initial position we grant rights to the outermost Rooks,
4665        and remember thos rights, and we then copy them on positions
4666        later in an FRC game. This means WB might not recognize castlings with
4667        Rooks that have moved back to their original position as illegal,
4668        but in ICS mode that is not its job anyway.
4669     */
4670     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4671     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4672
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[0][i] == WhiteRook) j = i;
4675         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[0][i] == WhiteRook) j = i;
4678         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4680             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4681         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4682         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4683             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4684         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685
4686         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4687         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4688         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4689             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4690         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4691             if(board[BOARD_HEIGHT-1][k] == bKing)
4692                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4693         if(gameInfo.variant == VariantTwoKings) {
4694             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4695             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4696             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4697         }
4698     } else { int r;
4699         r = boards[moveNum][CASTLING][0] = initialRights[0];
4700         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4701         r = boards[moveNum][CASTLING][1] = initialRights[1];
4702         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4703         r = boards[moveNum][CASTLING][3] = initialRights[3];
4704         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4705         r = boards[moveNum][CASTLING][4] = initialRights[4];
4706         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4707         /* wildcastle kludge: always assume King has rights */
4708         r = boards[moveNum][CASTLING][2] = initialRights[2];
4709         r = boards[moveNum][CASTLING][5] = initialRights[5];
4710     }
4711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4712     boards[moveNum][EP_STATUS] = EP_NONE;
4713     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4714     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4715     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4716
4717
4718     if (ics_getting_history == H_GOT_REQ_HEADER ||
4719         ics_getting_history == H_GOT_UNREQ_HEADER) {
4720         /* This was an initial position from a move list, not
4721            the current position */
4722         return;
4723     }
4724
4725     /* Update currentMove and known move number limits */
4726     newMove = newGame || moveNum > forwardMostMove;
4727
4728     if (newGame) {
4729         forwardMostMove = backwardMostMove = currentMove = moveNum;
4730         if (gameMode == IcsExamining && moveNum == 0) {
4731           /* Workaround for ICS limitation: we are not told the wild
4732              type when starting to examine a game.  But if we ask for
4733              the move list, the move list header will tell us */
4734             ics_getting_history = H_REQUESTED;
4735             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4736             SendToICS(str);
4737         }
4738     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4739                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4740 #if ZIPPY
4741         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4742         /* [HGM] applied this also to an engine that is silently watching        */
4743         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4744             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4745             gameInfo.variant == currentlyInitializedVariant) {
4746           takeback = forwardMostMove - moveNum;
4747           for (i = 0; i < takeback; i++) {
4748             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4749             SendToProgram("undo\n", &first);
4750           }
4751         }
4752 #endif
4753
4754         forwardMostMove = moveNum;
4755         if (!pausing || currentMove > forwardMostMove)
4756           currentMove = forwardMostMove;
4757     } else {
4758         /* New part of history that is not contiguous with old part */
4759         if (pausing && gameMode == IcsExamining) {
4760             pauseExamInvalid = TRUE;
4761             forwardMostMove = pauseExamForwardMostMove;
4762             return;
4763         }
4764         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4765 #if ZIPPY
4766             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4767                 // [HGM] when we will receive the move list we now request, it will be
4768                 // fed to the engine from the first move on. So if the engine is not
4769                 // in the initial position now, bring it there.
4770                 InitChessProgram(&first, 0);
4771             }
4772 #endif
4773             ics_getting_history = H_REQUESTED;
4774             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4775             SendToICS(str);
4776         }
4777         forwardMostMove = backwardMostMove = currentMove = moveNum;
4778     }
4779
4780     /* Update the clocks */
4781     if (strchr(elapsed_time, '.')) {
4782       /* Time is in ms */
4783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4785     } else {
4786       /* Time is in seconds */
4787       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4788       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4789     }
4790
4791
4792 #if ZIPPY
4793     if (appData.zippyPlay && newGame &&
4794         gameMode != IcsObserving && gameMode != IcsIdle &&
4795         gameMode != IcsExamining)
4796       ZippyFirstBoard(moveNum, basetime, increment);
4797 #endif
4798
4799     /* Put the move on the move list, first converting
4800        to canonical algebraic form. */
4801     if (moveNum > 0) {
4802   if (appData.debugMode) {
4803     int f = forwardMostMove;
4804     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4805             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4806             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4808     fprintf(debugFP, "moveNum = %d\n", moveNum);
4809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4810     setbuf(debugFP, NULL);
4811   }
4812         if (moveNum <= backwardMostMove) {
4813             /* We don't know what the board looked like before
4814                this move.  Punt. */
4815           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4816             strcat(parseList[moveNum - 1], " ");
4817             strcat(parseList[moveNum - 1], elapsed_time);
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819         } else if (strcmp(move_str, "none") == 0) {
4820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4821             /* Again, we don't know what the board looked like;
4822                this is really the start of the game. */
4823             parseList[moveNum - 1][0] = NULLCHAR;
4824             moveList[moveNum - 1][0] = NULLCHAR;
4825             backwardMostMove = moveNum;
4826             startedFromSetupPosition = TRUE;
4827             fromX = fromY = toX = toY = -1;
4828         } else {
4829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4830           //                 So we parse the long-algebraic move string in stead of the SAN move
4831           int valid; char buf[MSG_SIZ], *prom;
4832
4833           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4834                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4835           // str looks something like "Q/a1-a2"; kill the slash
4836           if(str[1] == '/')
4837             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4838           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4839           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4840                 strcat(buf, prom); // long move lacks promo specification!
4841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4842                 if(appData.debugMode)
4843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4844                 safeStrCpy(move_str, buf, MSG_SIZ);
4845           }
4846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4847                                 &fromX, &fromY, &toX, &toY, &promoChar)
4848                || ParseOneMove(buf, moveNum - 1, &moveType,
4849                                 &fromX, &fromY, &toX, &toY, &promoChar);
4850           // end of long SAN patch
4851           if (valid) {
4852             (void) CoordsToAlgebraic(boards[moveNum - 1],
4853                                      PosFlags(moveNum - 1),
4854                                      fromY, fromX, toY, toX, promoChar,
4855                                      parseList[moveNum-1]);
4856             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4857               case MT_NONE:
4858               case MT_STALEMATE:
4859               default:
4860                 break;
4861               case MT_CHECK:
4862                 if(!IS_SHOGI(gameInfo.variant))
4863                     strcat(parseList[moveNum - 1], "+");
4864                 break;
4865               case MT_CHECKMATE:
4866               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4867                 strcat(parseList[moveNum - 1], "#");
4868                 break;
4869             }
4870             strcat(parseList[moveNum - 1], " ");
4871             strcat(parseList[moveNum - 1], elapsed_time);
4872             /* currentMoveString is set as a side-effect of ParseOneMove */
4873             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4874             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4875             strcat(moveList[moveNum - 1], "\n");
4876
4877             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4878                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4879               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4880                 ChessSquare old, new = boards[moveNum][k][j];
4881                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4882                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4883                   if(old == new) continue;
4884                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4885                   else if(new == WhiteWazir || new == BlackWazir) {
4886                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4887                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4888                       else boards[moveNum][k][j] = old; // preserve type of Gold
4889                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4890                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4891               }
4892           } else {
4893             /* Move from ICS was illegal!?  Punt. */
4894             if (appData.debugMode) {
4895               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4896               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4897             }
4898             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             moveList[moveNum - 1][0] = NULLCHAR;
4902             fromX = fromY = toX = toY = -1;
4903           }
4904         }
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4907     setbuf(debugFP, NULL);
4908   }
4909
4910 #if ZIPPY
4911         /* Send move to chess program (BEFORE animating it). */
4912         if (appData.zippyPlay && !newGame && newMove &&
4913            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4914
4915             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4916                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4917                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4918                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4919                             move_str);
4920                     DisplayError(str, 0);
4921                 } else {
4922                     if (first.sendTime) {
4923                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4924                     }
4925                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4926                     if (firstMove && !bookHit) {
4927                         firstMove = FALSE;
4928                         if (first.useColors) {
4929                           SendToProgram(gameMode == IcsPlayingWhite ?
4930                                         "white\ngo\n" :
4931                                         "black\ngo\n", &first);
4932                         } else {
4933                           SendToProgram("go\n", &first);
4934                         }
4935                         first.maybeThinking = TRUE;
4936                     }
4937                 }
4938             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4939               if (moveList[moveNum - 1][0] == NULLCHAR) {
4940                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4941                 DisplayError(str, 0);
4942               } else {
4943                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4944                 SendMoveToProgram(moveNum - 1, &first);
4945               }
4946             }
4947         }
4948 #endif
4949     }
4950
4951     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4952         /* If move comes from a remote source, animate it.  If it
4953            isn't remote, it will have already been animated. */
4954         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4955             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4956         }
4957         if (!pausing && appData.highlightLastMove) {
4958             SetHighlights(fromX, fromY, toX, toY);
4959         }
4960     }
4961
4962     /* Start the clocks */
4963     whiteFlag = blackFlag = FALSE;
4964     appData.clockMode = !(basetime == 0 && increment == 0);
4965     if (ticking == 0) {
4966       ics_clock_paused = TRUE;
4967       StopClocks();
4968     } else if (ticking == 1) {
4969       ics_clock_paused = FALSE;
4970     }
4971     if (gameMode == IcsIdle ||
4972         relation == RELATION_OBSERVING_STATIC ||
4973         relation == RELATION_EXAMINING ||
4974         ics_clock_paused)
4975       DisplayBothClocks();
4976     else
4977       StartClocks();
4978
4979     /* Display opponents and material strengths */
4980     if (gameInfo.variant != VariantBughouse &&
4981         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4982         if (tinyLayout || smallLayout) {
4983             if(gameInfo.variant == VariantNormal)
4984               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4985                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4986                     basetime, increment);
4987             else
4988               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4990                     basetime, increment, (int) gameInfo.variant);
4991         } else {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4994                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4998                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4999                     basetime, increment, VariantName(gameInfo.variant));
5000         }
5001         DisplayTitle(str);
5002   if (appData.debugMode) {
5003     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5004   }
5005     }
5006
5007
5008     /* Display the board */
5009     if (!pausing && !appData.noGUI) {
5010
5011       if (appData.premove)
5012           if (!gotPremove ||
5013              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5014              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5015               ClearPremoveHighlights();
5016
5017       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5018         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5019       DrawPosition(j, boards[currentMove]);
5020
5021       DisplayMove(moveNum - 1);
5022       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5023             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5024               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5025         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5026       }
5027     }
5028
5029     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5030 #if ZIPPY
5031     if(bookHit) { // [HGM] book: simulate book reply
5032         static char bookMove[MSG_SIZ]; // a bit generous?
5033
5034         programStats.nodes = programStats.depth = programStats.time =
5035         programStats.score = programStats.got_only_move = 0;
5036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5037
5038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5039         strcat(bookMove, bookHit);
5040         HandleMachineMove(bookMove, &first);
5041     }
5042 #endif
5043 }
5044
5045 void
5046 GetMoveListEvent ()
5047 {
5048     char buf[MSG_SIZ];
5049     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5050         ics_getting_history = H_REQUESTED;
5051         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5052         SendToICS(buf);
5053     }
5054 }
5055
5056 void
5057 SendToBoth (char *msg)
5058 {   // to make it easy to keep two engines in step in dual analysis
5059     SendToProgram(msg, &first);
5060     if(second.analyzing) SendToProgram(msg, &second);
5061 }
5062
5063 void
5064 AnalysisPeriodicEvent (int force)
5065 {
5066     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5067          && !force) || !appData.periodicUpdates)
5068       return;
5069
5070     /* Send . command to Crafty to collect stats */
5071     SendToBoth(".\n");
5072
5073     /* Don't send another until we get a response (this makes
5074        us stop sending to old Crafty's which don't understand
5075        the "." command (sending illegal cmds resets node count & time,
5076        which looks bad)) */
5077     programStats.ok_to_send = 0;
5078 }
5079
5080 void
5081 ics_update_width (int new_width)
5082 {
5083         ics_printf("set width %d\n", new_width);
5084 }
5085
5086 void
5087 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5088 {
5089     char buf[MSG_SIZ];
5090
5091     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5092         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5093             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5094             SendToProgram(buf, cps);
5095             return;
5096         }
5097         // null move in variant where engine does not understand it (for analysis purposes)
5098         SendBoard(cps, moveNum + 1); // send position after move in stead.
5099         return;
5100     }
5101     if (cps->useUsermove) {
5102       SendToProgram("usermove ", cps);
5103     }
5104     if (cps->useSAN) {
5105       char *space;
5106       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5107         int len = space - parseList[moveNum];
5108         memcpy(buf, parseList[moveNum], len);
5109         buf[len++] = '\n';
5110         buf[len] = NULLCHAR;
5111       } else {
5112         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5113       }
5114       SendToProgram(buf, cps);
5115     } else {
5116       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5117         AlphaRank(moveList[moveNum], 4);
5118         SendToProgram(moveList[moveNum], cps);
5119         AlphaRank(moveList[moveNum], 4); // and back
5120       } else
5121       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5122        * the engine. It would be nice to have a better way to identify castle
5123        * moves here. */
5124       if(appData.fischerCastling && cps->useOOCastle) {
5125         int fromX = moveList[moveNum][0] - AAA;
5126         int fromY = moveList[moveNum][1] - ONE;
5127         int toX = moveList[moveNum][2] - AAA;
5128         int toY = moveList[moveNum][3] - ONE;
5129         if((boards[moveNum][fromY][fromX] == WhiteKing
5130             && boards[moveNum][toY][toX] == WhiteRook)
5131            || (boards[moveNum][fromY][fromX] == BlackKing
5132                && boards[moveNum][toY][toX] == BlackRook)) {
5133           if(toX > fromX) SendToProgram("O-O\n", cps);
5134           else SendToProgram("O-O-O\n", cps);
5135         }
5136         else SendToProgram(moveList[moveNum], cps);
5137       } else
5138       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5139           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5140                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5143           SendToProgram(buf, cps);
5144       } else
5145       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5146         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5147           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5148           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5149                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5150         } else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5152                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5153         SendToProgram(buf, cps);
5154       }
5155       else SendToProgram(moveList[moveNum], cps);
5156       /* End of additions by Tord */
5157     }
5158
5159     /* [HGM] setting up the opening has brought engine in force mode! */
5160     /*       Send 'go' if we are in a mode where machine should play. */
5161     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5162         (gameMode == TwoMachinesPlay   ||
5163 #if ZIPPY
5164          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5165 #endif
5166          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5167         SendToProgram("go\n", cps);
5168   if (appData.debugMode) {
5169     fprintf(debugFP, "(extra)\n");
5170   }
5171     }
5172     setboardSpoiledMachineBlack = 0;
5173 }
5174
5175 void
5176 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5177 {
5178     char user_move[MSG_SIZ];
5179     char suffix[4];
5180
5181     if(gameInfo.variant == VariantSChess && promoChar) {
5182         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5183         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5184     } else suffix[0] = NULLCHAR;
5185
5186     switch (moveType) {
5187       default:
5188         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5189                 (int)moveType, fromX, fromY, toX, toY);
5190         DisplayError(user_move + strlen("say "), 0);
5191         break;
5192       case WhiteKingSideCastle:
5193       case BlackKingSideCastle:
5194       case WhiteQueenSideCastleWild:
5195       case BlackQueenSideCastleWild:
5196       /* PUSH Fabien */
5197       case WhiteHSideCastleFR:
5198       case BlackHSideCastleFR:
5199       /* POP Fabien */
5200         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5201         break;
5202       case WhiteQueenSideCastle:
5203       case BlackQueenSideCastle:
5204       case WhiteKingSideCastleWild:
5205       case BlackKingSideCastleWild:
5206       /* PUSH Fabien */
5207       case WhiteASideCastleFR:
5208       case BlackASideCastleFR:
5209       /* POP Fabien */
5210         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5211         break;
5212       case WhiteNonPromotion:
5213       case BlackNonPromotion:
5214         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5215         break;
5216       case WhitePromotion:
5217       case BlackPromotion:
5218         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5219            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5220           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5222                 PieceToChar(WhiteFerz));
5223         else if(gameInfo.variant == VariantGreat)
5224           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5225                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5226                 PieceToChar(WhiteMan));
5227         else
5228           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5229                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5230                 promoChar);
5231         break;
5232       case WhiteDrop:
5233       case BlackDrop:
5234       drop:
5235         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5236                  ToUpper(PieceToChar((ChessSquare) fromX)),
5237                  AAA + toX, ONE + toY);
5238         break;
5239       case IllegalMove:  /* could be a variant we don't quite understand */
5240         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5246         break;
5247     }
5248     SendToICS(user_move);
5249     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5250         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5251 }
5252
5253 void
5254 UploadGameEvent ()
5255 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5256     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5257     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5258     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5259       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5260       return;
5261     }
5262     if(gameMode != IcsExamining) { // is this ever not the case?
5263         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5264
5265         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5266           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5267         } else { // on FICS we must first go to general examine mode
5268           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5269         }
5270         if(gameInfo.variant != VariantNormal) {
5271             // try figure out wild number, as xboard names are not always valid on ICS
5272             for(i=1; i<=36; i++) {
5273               snprintf(buf, MSG_SIZ, "wild/%d", i);
5274                 if(StringToVariant(buf) == gameInfo.variant) break;
5275             }
5276             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5277             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5278             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5279         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5280         SendToICS(ics_prefix);
5281         SendToICS(buf);
5282         if(startedFromSetupPosition || backwardMostMove != 0) {
5283           fen = PositionToFEN(backwardMostMove, NULL, 1);
5284           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5285             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5286             SendToICS(buf);
5287           } else { // FICS: everything has to set by separate bsetup commands
5288             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5289             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5290             SendToICS(buf);
5291             if(!WhiteOnMove(backwardMostMove)) {
5292                 SendToICS("bsetup tomove black\n");
5293             }
5294             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5295             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5296             SendToICS(buf);
5297             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5298             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5299             SendToICS(buf);
5300             i = boards[backwardMostMove][EP_STATUS];
5301             if(i >= 0) { // set e.p.
5302               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5303                 SendToICS(buf);
5304             }
5305             bsetup++;
5306           }
5307         }
5308       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5309             SendToICS("bsetup done\n"); // switch to normal examining.
5310     }
5311     for(i = backwardMostMove; i<last; i++) {
5312         char buf[20];
5313         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5314         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5315             int len = strlen(moveList[i]);
5316             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5317             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5318         }
5319         SendToICS(buf);
5320     }
5321     SendToICS(ics_prefix);
5322     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5323 }
5324
5325 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5326
5327 void
5328 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5329 {
5330     if (rf == DROP_RANK) {
5331       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5332       sprintf(move, "%c@%c%c\n",
5333                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5334     } else {
5335         if (promoChar == 'x' || promoChar == NULLCHAR) {
5336           sprintf(move, "%c%c%c%c\n",
5337                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5338           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5339         } else {
5340             sprintf(move, "%c%c%c%c%c\n",
5341                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5342         }
5343     }
5344 }
5345
5346 void
5347 ProcessICSInitScript (FILE *f)
5348 {
5349     char buf[MSG_SIZ];
5350
5351     while (fgets(buf, MSG_SIZ, f)) {
5352         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5353     }
5354
5355     fclose(f);
5356 }
5357
5358
5359 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5360 int dragging;
5361 static ClickType lastClickType;
5362
5363 int
5364 Partner (ChessSquare *p)
5365 { // change piece into promotion partner if one shogi-promotes to the other
5366   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5367   ChessSquare partner;
5368   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5369   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5370   *p = partner;
5371   return 1;
5372 }
5373
5374 void
5375 Sweep (int step)
5376 {
5377     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5378     static int toggleFlag;
5379     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5380     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5381     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5382     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5383     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5384     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5385     do {
5386         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5387         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5388         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5389         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5390         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5391         if(!step) step = -1;
5392     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5393             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5394             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5395             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5396     if(toX >= 0) {
5397         int victim = boards[currentMove][toY][toX];
5398         boards[currentMove][toY][toX] = promoSweep;
5399         DrawPosition(FALSE, boards[currentMove]);
5400         boards[currentMove][toY][toX] = victim;
5401     } else
5402     ChangeDragPiece(promoSweep);
5403 }
5404
5405 int
5406 PromoScroll (int x, int y)
5407 {
5408   int step = 0;
5409
5410   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5411   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5412   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5413   if(!step) return FALSE;
5414   lastX = x; lastY = y;
5415   if((promoSweep < BlackPawn) == flipView) step = -step;
5416   if(step > 0) selectFlag = 1;
5417   if(!selectFlag) Sweep(step);
5418   return FALSE;
5419 }
5420
5421 void
5422 NextPiece (int step)
5423 {
5424     ChessSquare piece = boards[currentMove][toY][toX];
5425     do {
5426         pieceSweep -= step;
5427         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5428         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5429         if(!step) step = -1;
5430     } while(PieceToChar(pieceSweep) == '.');
5431     boards[currentMove][toY][toX] = pieceSweep;
5432     DrawPosition(FALSE, boards[currentMove]);
5433     boards[currentMove][toY][toX] = piece;
5434 }
5435 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5436 void
5437 AlphaRank (char *move, int n)
5438 {
5439 //    char *p = move, c; int x, y;
5440
5441     if (appData.debugMode) {
5442         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5443     }
5444
5445     if(move[1]=='*' &&
5446        move[2]>='0' && move[2]<='9' &&
5447        move[3]>='a' && move[3]<='x'    ) {
5448         move[1] = '@';
5449         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5450         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5451     } else
5452     if(move[0]>='0' && move[0]<='9' &&
5453        move[1]>='a' && move[1]<='x' &&
5454        move[2]>='0' && move[2]<='9' &&
5455        move[3]>='a' && move[3]<='x'    ) {
5456         /* input move, Shogi -> normal */
5457         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5458         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5459         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5460         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5461     } else
5462     if(move[1]=='@' &&
5463        move[3]>='0' && move[3]<='9' &&
5464        move[2]>='a' && move[2]<='x'    ) {
5465         move[1] = '*';
5466         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5467         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5468     } else
5469     if(
5470        move[0]>='a' && move[0]<='x' &&
5471        move[3]>='0' && move[3]<='9' &&
5472        move[2]>='a' && move[2]<='x'    ) {
5473          /* output move, normal -> Shogi */
5474         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5475         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5476         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5477         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5478         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5479     }
5480     if (appData.debugMode) {
5481         fprintf(debugFP, "   out = '%s'\n", move);
5482     }
5483 }
5484
5485 char yy_textstr[8000];
5486
5487 /* Parser for moves from gnuchess, ICS, or user typein box */
5488 Boolean
5489 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5490 {
5491     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5492
5493     switch (*moveType) {
5494       case WhitePromotion:
5495       case BlackPromotion:
5496       case WhiteNonPromotion:
5497       case BlackNonPromotion:
5498       case NormalMove:
5499       case FirstLeg:
5500       case WhiteCapturesEnPassant:
5501       case BlackCapturesEnPassant:
5502       case WhiteKingSideCastle:
5503       case WhiteQueenSideCastle:
5504       case BlackKingSideCastle:
5505       case BlackQueenSideCastle:
5506       case WhiteKingSideCastleWild:
5507       case WhiteQueenSideCastleWild:
5508       case BlackKingSideCastleWild:
5509       case BlackQueenSideCastleWild:
5510       /* Code added by Tord: */
5511       case WhiteHSideCastleFR:
5512       case WhiteASideCastleFR:
5513       case BlackHSideCastleFR:
5514       case BlackASideCastleFR:
5515       /* End of code added by Tord */
5516       case IllegalMove:         /* bug or odd chess variant */
5517         *fromX = currentMoveString[0] - AAA;
5518         *fromY = currentMoveString[1] - ONE;
5519         *toX = currentMoveString[2] - AAA;
5520         *toY = currentMoveString[3] - ONE;
5521         *promoChar = currentMoveString[4];
5522         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5523             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5524     if (appData.debugMode) {
5525         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5526     }
5527             *fromX = *fromY = *toX = *toY = 0;
5528             return FALSE;
5529         }
5530         if (appData.testLegality) {
5531           return (*moveType != IllegalMove);
5532         } else {
5533           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5534                          // [HGM] lion: if this is a double move we are less critical
5535                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5536         }
5537
5538       case WhiteDrop:
5539       case BlackDrop:
5540         *fromX = *moveType == WhiteDrop ?
5541           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5542           (int) CharToPiece(ToLower(currentMoveString[0]));
5543         *fromY = DROP_RANK;
5544         *toX = currentMoveString[2] - AAA;
5545         *toY = currentMoveString[3] - ONE;
5546         *promoChar = NULLCHAR;
5547         return TRUE;
5548
5549       case AmbiguousMove:
5550       case ImpossibleMove:
5551       case EndOfFile:
5552       case ElapsedTime:
5553       case Comment:
5554       case PGNTag:
5555       case NAG:
5556       case WhiteWins:
5557       case BlackWins:
5558       case GameIsDrawn:
5559       default:
5560     if (appData.debugMode) {
5561         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5562     }
5563         /* bug? */
5564         *fromX = *fromY = *toX = *toY = 0;
5565         *promoChar = NULLCHAR;
5566         return FALSE;
5567     }
5568 }
5569
5570 Boolean pushed = FALSE;
5571 char *lastParseAttempt;
5572
5573 void
5574 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5575 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5576   int fromX, fromY, toX, toY; char promoChar;
5577   ChessMove moveType;
5578   Boolean valid;
5579   int nr = 0;
5580
5581   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5582   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5583     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5584     pushed = TRUE;
5585   }
5586   endPV = forwardMostMove;
5587   do {
5588     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5589     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5590     lastParseAttempt = pv;
5591     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5592     if(!valid && nr == 0 &&
5593        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5594         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5595         // Hande case where played move is different from leading PV move
5596         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5597         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5598         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5599         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5600           endPV += 2; // if position different, keep this
5601           moveList[endPV-1][0] = fromX + AAA;
5602           moveList[endPV-1][1] = fromY + ONE;
5603           moveList[endPV-1][2] = toX + AAA;
5604           moveList[endPV-1][3] = toY + ONE;
5605           parseList[endPV-1][0] = NULLCHAR;
5606           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5607         }
5608       }
5609     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5610     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5611     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5612     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5613         valid++; // allow comments in PV
5614         continue;
5615     }
5616     nr++;
5617     if(endPV+1 > framePtr) break; // no space, truncate
5618     if(!valid) break;
5619     endPV++;
5620     CopyBoard(boards[endPV], boards[endPV-1]);
5621     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5622     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5623     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5624     CoordsToAlgebraic(boards[endPV - 1],
5625                              PosFlags(endPV - 1),
5626                              fromY, fromX, toY, toX, promoChar,
5627                              parseList[endPV - 1]);
5628   } while(valid);
5629   if(atEnd == 2) return; // used hidden, for PV conversion
5630   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5631   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634   DrawPosition(TRUE, boards[currentMove]);
5635 }
5636
5637 int
5638 MultiPV (ChessProgramState *cps)
5639 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5640         int i;
5641         for(i=0; i<cps->nrOptions; i++)
5642             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5643                 return i;
5644         return -1;
5645 }
5646
5647 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5648
5649 Boolean
5650 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5651 {
5652         int startPV, multi, lineStart, origIndex = index;
5653         char *p, buf2[MSG_SIZ];
5654         ChessProgramState *cps = (pane ? &second : &first);
5655
5656         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5657         lastX = x; lastY = y;
5658         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5659         lineStart = startPV = index;
5660         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5661         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5662         index = startPV;
5663         do{ while(buf[index] && buf[index] != '\n') index++;
5664         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5665         buf[index] = 0;
5666         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5667                 int n = cps->option[multi].value;
5668                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5669                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5670                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5671                 cps->option[multi].value = n;
5672                 *start = *end = 0;
5673                 return FALSE;
5674         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5675                 ExcludeClick(origIndex - lineStart);
5676                 return FALSE;
5677         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5678                 Collapse(origIndex - lineStart);
5679                 return FALSE;
5680         }
5681         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5682         *start = startPV; *end = index-1;
5683         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5684         return TRUE;
5685 }
5686
5687 char *
5688 PvToSAN (char *pv)
5689 {
5690         static char buf[10*MSG_SIZ];
5691         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5692         *buf = NULLCHAR;
5693         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5694         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5695         for(i = forwardMostMove; i<endPV; i++){
5696             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5697             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5698             k += strlen(buf+k);
5699         }
5700         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5701         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5702         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5703         endPV = savedEnd;
5704         return buf;
5705 }
5706
5707 Boolean
5708 LoadPV (int x, int y)
5709 { // called on right mouse click to load PV
5710   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5711   lastX = x; lastY = y;
5712   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5713   extendGame = FALSE;
5714   return TRUE;
5715 }
5716
5717 void
5718 UnLoadPV ()
5719 {
5720   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5721   if(endPV < 0) return;
5722   if(appData.autoCopyPV) CopyFENToClipboard();
5723   endPV = -1;
5724   if(extendGame && currentMove > forwardMostMove) {
5725         Boolean saveAnimate = appData.animate;
5726         if(pushed) {
5727             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5728                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5729             } else storedGames--; // abandon shelved tail of original game
5730         }
5731         pushed = FALSE;
5732         forwardMostMove = currentMove;
5733         currentMove = oldFMM;
5734         appData.animate = FALSE;
5735         ToNrEvent(forwardMostMove);
5736         appData.animate = saveAnimate;
5737   }
5738   currentMove = forwardMostMove;
5739   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5740   ClearPremoveHighlights();
5741   DrawPosition(TRUE, boards[currentMove]);
5742 }
5743
5744 void
5745 MovePV (int x, int y, int h)
5746 { // step through PV based on mouse coordinates (called on mouse move)
5747   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5748
5749   // we must somehow check if right button is still down (might be released off board!)
5750   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5751   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5752   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5753   if(!step) return;
5754   lastX = x; lastY = y;
5755
5756   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5757   if(endPV < 0) return;
5758   if(y < margin) step = 1; else
5759   if(y > h - margin) step = -1;
5760   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5761   currentMove += step;
5762   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5763   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5764                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5765   DrawPosition(FALSE, boards[currentMove]);
5766 }
5767
5768
5769 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5770 // All positions will have equal probability, but the current method will not provide a unique
5771 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5772 #define DARK 1
5773 #define LITE 2
5774 #define ANY 3
5775
5776 int squaresLeft[4];
5777 int piecesLeft[(int)BlackPawn];
5778 int seed, nrOfShuffles;
5779
5780 void
5781 GetPositionNumber ()
5782 {       // sets global variable seed
5783         int i;
5784
5785         seed = appData.defaultFrcPosition;
5786         if(seed < 0) { // randomize based on time for negative FRC position numbers
5787                 for(i=0; i<50; i++) seed += random();
5788                 seed = random() ^ random() >> 8 ^ random() << 8;
5789                 if(seed<0) seed = -seed;
5790         }
5791 }
5792
5793 int
5794 put (Board board, int pieceType, int rank, int n, int shade)
5795 // put the piece on the (n-1)-th empty squares of the given shade
5796 {
5797         int i;
5798
5799         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5800                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5801                         board[rank][i] = (ChessSquare) pieceType;
5802                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5803                         squaresLeft[ANY]--;
5804                         piecesLeft[pieceType]--;
5805                         return i;
5806                 }
5807         }
5808         return -1;
5809 }
5810
5811
5812 void
5813 AddOnePiece (Board board, int pieceType, int rank, int shade)
5814 // calculate where the next piece goes, (any empty square), and put it there
5815 {
5816         int i;
5817
5818         i = seed % squaresLeft[shade];
5819         nrOfShuffles *= squaresLeft[shade];
5820         seed /= squaresLeft[shade];
5821         put(board, pieceType, rank, i, shade);
5822 }
5823
5824 void
5825 AddTwoPieces (Board board, int pieceType, int rank)
5826 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5827 {
5828         int i, n=squaresLeft[ANY], j=n-1, k;
5829
5830         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5831         i = seed % k;  // pick one
5832         nrOfShuffles *= k;
5833         seed /= k;
5834         while(i >= j) i -= j--;
5835         j = n - 1 - j; i += j;
5836         put(board, pieceType, rank, j, ANY);
5837         put(board, pieceType, rank, i, ANY);
5838 }
5839
5840 void
5841 SetUpShuffle (Board board, int number)
5842 {
5843         int i, p, first=1;
5844
5845         GetPositionNumber(); nrOfShuffles = 1;
5846
5847         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5848         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5849         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5850
5851         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5852
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5854             p = (int) board[0][i];
5855             if(p < (int) BlackPawn) piecesLeft[p] ++;
5856             board[0][i] = EmptySquare;
5857         }
5858
5859         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5860             // shuffles restricted to allow normal castling put KRR first
5861             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5862                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5863             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5864                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5865             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5866                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5867             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5868                 put(board, WhiteRook, 0, 0, ANY);
5869             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5870         }
5871
5872         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5873             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5874             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5875                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5876                 while(piecesLeft[p] >= 2) {
5877                     AddOnePiece(board, p, 0, LITE);
5878                     AddOnePiece(board, p, 0, DARK);
5879                 }
5880                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5881             }
5882
5883         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5884             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5885             // but we leave King and Rooks for last, to possibly obey FRC restriction
5886             if(p == (int)WhiteRook) continue;
5887             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5888             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5889         }
5890
5891         // now everything is placed, except perhaps King (Unicorn) and Rooks
5892
5893         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5894             // Last King gets castling rights
5895             while(piecesLeft[(int)WhiteUnicorn]) {
5896                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900             while(piecesLeft[(int)WhiteKing]) {
5901                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5902                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5903             }
5904
5905
5906         } else {
5907             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5908             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5909         }
5910
5911         // Only Rooks can be left; simply place them all
5912         while(piecesLeft[(int)WhiteRook]) {
5913                 i = put(board, WhiteRook, 0, 0, ANY);
5914                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5915                         if(first) {
5916                                 first=0;
5917                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5918                         }
5919                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5920                 }
5921         }
5922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5923             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5924         }
5925
5926         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5927 }
5928
5929 int
5930 SetCharTable (char *table, const char * map)
5931 /* [HGM] moved here from winboard.c because of its general usefulness */
5932 /*       Basically a safe strcpy that uses the last character as King */
5933 {
5934     int result = FALSE; int NrPieces;
5935
5936     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5937                     && NrPieces >= 12 && !(NrPieces&1)) {
5938         int i; /* [HGM] Accept even length from 12 to 34 */
5939
5940         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5941         for( i=0; i<NrPieces/2-1; i++ ) {
5942             table[i] = map[i];
5943             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5944         }
5945         table[(int) WhiteKing]  = map[NrPieces/2-1];
5946         table[(int) BlackKing]  = map[NrPieces-1];
5947
5948         result = TRUE;
5949     }
5950
5951     return result;
5952 }
5953
5954 void
5955 Prelude (Board board)
5956 {       // [HGM] superchess: random selection of exo-pieces
5957         int i, j, k; ChessSquare p;
5958         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5959
5960         GetPositionNumber(); // use FRC position number
5961
5962         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5963             SetCharTable(pieceToChar, appData.pieceToCharTable);
5964             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5965                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5966         }
5967
5968         j = seed%4;                 seed /= 4;
5969         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5970         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5971         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5972         j = seed%3 + (seed%3 >= j); seed /= 3;
5973         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5974         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5975         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5976         j = seed%3;                 seed /= 3;
5977         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5978         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5979         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5980         j = seed%2 + (seed%2 >= j); seed /= 2;
5981         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5984         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5985         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5986         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5987         put(board, exoPieces[0],    0, 0, ANY);
5988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5989 }
5990
5991 void
5992 InitPosition (int redraw)
5993 {
5994     ChessSquare (* pieces)[BOARD_FILES];
5995     int i, j, pawnRow=1, pieceRows=1, overrule,
5996     oldx = gameInfo.boardWidth,
5997     oldy = gameInfo.boardHeight,
5998     oldh = gameInfo.holdingsWidth;
5999     static int oldv;
6000
6001     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6002
6003     /* [AS] Initialize pv info list [HGM] and game status */
6004     {
6005         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6006             pvInfoList[i].depth = 0;
6007             boards[i][EP_STATUS] = EP_NONE;
6008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6009         }
6010
6011         initialRulePlies = 0; /* 50-move counter start */
6012
6013         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6014         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6015     }
6016
6017
6018     /* [HGM] logic here is completely changed. In stead of full positions */
6019     /* the initialized data only consist of the two backranks. The switch */
6020     /* selects which one we will use, which is than copied to the Board   */
6021     /* initialPosition, which for the rest is initialized by Pawns and    */
6022     /* empty squares. This initial position is then copied to boards[0],  */
6023     /* possibly after shuffling, so that it remains available.            */
6024
6025     gameInfo.holdingsWidth = 0; /* default board sizes */
6026     gameInfo.boardWidth    = 8;
6027     gameInfo.boardHeight   = 8;
6028     gameInfo.holdingsSize  = 0;
6029     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6030     for(i=0; i<BOARD_FILES-2; i++)
6031       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6032     initialPosition[EP_STATUS] = EP_NONE;
6033     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6034     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6035          SetCharTable(pieceNickName, appData.pieceNickNames);
6036     else SetCharTable(pieceNickName, "............");
6037     pieces = FIDEArray;
6038
6039     switch (gameInfo.variant) {
6040     case VariantFischeRandom:
6041       shuffleOpenings = TRUE;
6042       appData.fischerCastling = TRUE;
6043     default:
6044       break;
6045     case VariantShatranj:
6046       pieces = ShatranjArray;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6049       break;
6050     case VariantMakruk:
6051       pieces = makrukArray;
6052       nrCastlingRights = 0;
6053       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6054       break;
6055     case VariantASEAN:
6056       pieces = aseanArray;
6057       nrCastlingRights = 0;
6058       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6059       break;
6060     case VariantTwoKings:
6061       pieces = twoKingsArray;
6062       break;
6063     case VariantGrand:
6064       pieces = GrandArray;
6065       nrCastlingRights = 0;
6066       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6067       gameInfo.boardWidth = 10;
6068       gameInfo.boardHeight = 10;
6069       gameInfo.holdingsSize = 7;
6070       break;
6071     case VariantCapaRandom:
6072       shuffleOpenings = TRUE;
6073       appData.fischerCastling = TRUE;
6074     case VariantCapablanca:
6075       pieces = CapablancaArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6078       break;
6079     case VariantGothic:
6080       pieces = GothicArray;
6081       gameInfo.boardWidth = 10;
6082       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6083       break;
6084     case VariantSChess:
6085       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6086       gameInfo.holdingsSize = 7;
6087       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6088       break;
6089     case VariantJanus:
6090       pieces = JanusArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6093       nrCastlingRights = 6;
6094         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6095         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6096         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6097         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6098         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6099         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6100       break;
6101     case VariantFalcon:
6102       pieces = FalconArray;
6103       gameInfo.boardWidth = 10;
6104       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6105       break;
6106     case VariantXiangqi:
6107       pieces = XiangqiArray;
6108       gameInfo.boardWidth  = 9;
6109       gameInfo.boardHeight = 10;
6110       nrCastlingRights = 0;
6111       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6112       break;
6113     case VariantShogi:
6114       pieces = ShogiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 9;
6117       gameInfo.holdingsSize = 7;
6118       nrCastlingRights = 0;
6119       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6120       break;
6121     case VariantChu:
6122       pieces = ChuArray; pieceRows = 3;
6123       gameInfo.boardWidth  = 12;
6124       gameInfo.boardHeight = 12;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6127                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6128       break;
6129     case VariantCourier:
6130       pieces = CourierArray;
6131       gameInfo.boardWidth  = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6134       break;
6135     case VariantKnightmate:
6136       pieces = KnightmateArray;
6137       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6138       break;
6139     case VariantSpartan:
6140       pieces = SpartanArray;
6141       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6142       break;
6143     case VariantLion:
6144       pieces = lionArray;
6145       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6146       break;
6147     case VariantChuChess:
6148       pieces = ChuChessArray;
6149       gameInfo.boardWidth = 10;
6150       gameInfo.boardHeight = 10;
6151       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6152       break;
6153     case VariantFairy:
6154       pieces = fairyArray;
6155       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6156       break;
6157     case VariantGreat:
6158       pieces = GreatArray;
6159       gameInfo.boardWidth = 10;
6160       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6161       gameInfo.holdingsSize = 8;
6162       break;
6163     case VariantSuper:
6164       pieces = FIDEArray;
6165       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6166       gameInfo.holdingsSize = 8;
6167       startedFromSetupPosition = TRUE;
6168       break;
6169     case VariantCrazyhouse:
6170     case VariantBughouse:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6173       gameInfo.holdingsSize = 5;
6174       break;
6175     case VariantWildCastle:
6176       pieces = FIDEArray;
6177       /* !!?shuffle with kings guaranteed to be on d or e file */
6178       shuffleOpenings = 1;
6179       break;
6180     case VariantNoCastle:
6181       pieces = FIDEArray;
6182       nrCastlingRights = 0;
6183       /* !!?unconstrained back-rank shuffle */
6184       shuffleOpenings = 1;
6185       break;
6186     }
6187
6188     overrule = 0;
6189     if(appData.NrFiles >= 0) {
6190         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6191         gameInfo.boardWidth = appData.NrFiles;
6192     }
6193     if(appData.NrRanks >= 0) {
6194         gameInfo.boardHeight = appData.NrRanks;
6195     }
6196     if(appData.holdingsSize >= 0) {
6197         i = appData.holdingsSize;
6198         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6199         gameInfo.holdingsSize = i;
6200     }
6201     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6202     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6203         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6204
6205     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6206     if(pawnRow < 1) pawnRow = 1;
6207     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6208        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6209     if(gameInfo.variant == VariantChu) pawnRow = 3;
6210
6211     /* User pieceToChar list overrules defaults */
6212     if(appData.pieceToCharTable != NULL)
6213         SetCharTable(pieceToChar, appData.pieceToCharTable);
6214
6215     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6216
6217         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6218             s = (ChessSquare) 0; /* account holding counts in guard band */
6219         for( i=0; i<BOARD_HEIGHT; i++ )
6220             initialPosition[i][j] = s;
6221
6222         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6223         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6224         initialPosition[pawnRow][j] = WhitePawn;
6225         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6226         if(gameInfo.variant == VariantXiangqi) {
6227             if(j&1) {
6228                 initialPosition[pawnRow][j] =
6229                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6230                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6231                    initialPosition[2][j] = WhiteCannon;
6232                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6233                 }
6234             }
6235         }
6236         if(gameInfo.variant == VariantChu) {
6237              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6238                initialPosition[pawnRow+1][j] = WhiteCobra,
6239                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6240              for(i=1; i<pieceRows; i++) {
6241                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6242                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6243              }
6244         }
6245         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6246             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6247                initialPosition[0][j] = WhiteRook;
6248                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6249             }
6250         }
6251         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6252     }
6253     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6254     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6255
6256             j=BOARD_LEFT+1;
6257             initialPosition[1][j] = WhiteBishop;
6258             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6259             j=BOARD_RGHT-2;
6260             initialPosition[1][j] = WhiteRook;
6261             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6262     }
6263
6264     if( nrCastlingRights == -1) {
6265         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6266         /*       This sets default castling rights from none to normal corners   */
6267         /* Variants with other castling rights must set them themselves above    */
6268         nrCastlingRights = 6;
6269
6270         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6271         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6272         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6273         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6274         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6275         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6276      }
6277
6278      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6279      if(gameInfo.variant == VariantGreat) { // promotion commoners
6280         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6282         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6284      }
6285      if( gameInfo.variant == VariantSChess ) {
6286       initialPosition[1][0] = BlackMarshall;
6287       initialPosition[2][0] = BlackAngel;
6288       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6289       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6290       initialPosition[1][1] = initialPosition[2][1] =
6291       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6292      }
6293   if (appData.debugMode) {
6294     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6295   }
6296     if(shuffleOpenings) {
6297         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6298         startedFromSetupPosition = TRUE;
6299     }
6300     if(startedFromPositionFile) {
6301       /* [HGM] loadPos: use PositionFile for every new game */
6302       CopyBoard(initialPosition, filePosition);
6303       for(i=0; i<nrCastlingRights; i++)
6304           initialRights[i] = filePosition[CASTLING][i];
6305       startedFromSetupPosition = TRUE;
6306     }
6307
6308     CopyBoard(boards[0], initialPosition);
6309
6310     if(oldx != gameInfo.boardWidth ||
6311        oldy != gameInfo.boardHeight ||
6312        oldv != gameInfo.variant ||
6313        oldh != gameInfo.holdingsWidth
6314                                          )
6315             InitDrawingSizes(-2 ,0);
6316
6317     oldv = gameInfo.variant;
6318     if (redraw)
6319       DrawPosition(TRUE, boards[currentMove]);
6320 }
6321
6322 void
6323 SendBoard (ChessProgramState *cps, int moveNum)
6324 {
6325     char message[MSG_SIZ];
6326
6327     if (cps->useSetboard) {
6328       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6329       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6330       SendToProgram(message, cps);
6331       free(fen);
6332
6333     } else {
6334       ChessSquare *bp;
6335       int i, j, left=0, right=BOARD_WIDTH;
6336       /* Kludge to set black to move, avoiding the troublesome and now
6337        * deprecated "black" command.
6338        */
6339       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6340         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6341
6342       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6343
6344       SendToProgram("edit\n", cps);
6345       SendToProgram("#\n", cps);
6346       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6347         bp = &boards[moveNum][i][left];
6348         for (j = left; j < right; j++, bp++) {
6349           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6350           if ((int) *bp < (int) BlackPawn) {
6351             if(j == BOARD_RGHT+1)
6352                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6353             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6354             if(message[0] == '+' || message[0] == '~') {
6355               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6356                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6357                         AAA + j, ONE + i);
6358             }
6359             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6360                 message[1] = BOARD_RGHT   - 1 - j + '1';
6361                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6362             }
6363             SendToProgram(message, cps);
6364           }
6365         }
6366       }
6367
6368       SendToProgram("c\n", cps);
6369       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6370         bp = &boards[moveNum][i][left];
6371         for (j = left; j < right; j++, bp++) {
6372           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6373           if (((int) *bp != (int) EmptySquare)
6374               && ((int) *bp >= (int) BlackPawn)) {
6375             if(j == BOARD_LEFT-2)
6376                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6377             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6378                     AAA + j, ONE + i);
6379             if(message[0] == '+' || message[0] == '~') {
6380               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6381                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6382                         AAA + j, ONE + i);
6383             }
6384             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6385                 message[1] = BOARD_RGHT   - 1 - j + '1';
6386                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6387             }
6388             SendToProgram(message, cps);
6389           }
6390         }
6391       }
6392
6393       SendToProgram(".\n", cps);
6394     }
6395     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6396 }
6397
6398 char exclusionHeader[MSG_SIZ];
6399 int exCnt, excludePtr;
6400 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6401 static Exclusion excluTab[200];
6402 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6403
6404 static void
6405 WriteMap (int s)
6406 {
6407     int j;
6408     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6409     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6410 }
6411
6412 static void
6413 ClearMap ()
6414 {
6415     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6416     excludePtr = 24; exCnt = 0;
6417     WriteMap(0);
6418 }
6419
6420 static void
6421 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6422 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6423     char buf[2*MOVE_LEN], *p;
6424     Exclusion *e = excluTab;
6425     int i;
6426     for(i=0; i<exCnt; i++)
6427         if(e[i].ff == fromX && e[i].fr == fromY &&
6428            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6429     if(i == exCnt) { // was not in exclude list; add it
6430         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6431         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6432             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6433             return; // abort
6434         }
6435         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6436         excludePtr++; e[i].mark = excludePtr++;
6437         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6438         exCnt++;
6439     }
6440     exclusionHeader[e[i].mark] = state;
6441 }
6442
6443 static int
6444 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6445 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6446     char buf[MSG_SIZ];
6447     int j, k;
6448     ChessMove moveType;
6449     if((signed char)promoChar == -1) { // kludge to indicate best move
6450         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6451             return 1; // if unparsable, abort
6452     }
6453     // update exclusion map (resolving toggle by consulting existing state)
6454     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6455     j = k%8; k >>= 3;
6456     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6457     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6458          excludeMap[k] |=   1<<j;
6459     else excludeMap[k] &= ~(1<<j);
6460     // update header
6461     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6462     // inform engine
6463     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6465     SendToBoth(buf);
6466     return (state == '+');
6467 }
6468
6469 static void
6470 ExcludeClick (int index)
6471 {
6472     int i, j;
6473     Exclusion *e = excluTab;
6474     if(index < 25) { // none, best or tail clicked
6475         if(index < 13) { // none: include all
6476             WriteMap(0); // clear map
6477             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6478             SendToBoth("include all\n"); // and inform engine
6479         } else if(index > 18) { // tail
6480             if(exclusionHeader[19] == '-') { // tail was excluded
6481                 SendToBoth("include all\n");
6482                 WriteMap(0); // clear map completely
6483                 // now re-exclude selected moves
6484                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6485                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6486             } else { // tail was included or in mixed state
6487                 SendToBoth("exclude all\n");
6488                 WriteMap(0xFF); // fill map completely
6489                 // now re-include selected moves
6490                 j = 0; // count them
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6493                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6494             }
6495         } else { // best
6496             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6497         }
6498     } else {
6499         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6500             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6501             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6502             break;
6503         }
6504     }
6505 }
6506
6507 ChessSquare
6508 DefaultPromoChoice (int white)
6509 {
6510     ChessSquare result;
6511     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6512        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6513         result = WhiteFerz; // no choice
6514     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6515         result= WhiteKing; // in Suicide Q is the last thing we want
6516     else if(gameInfo.variant == VariantSpartan)
6517         result = white ? WhiteQueen : WhiteAngel;
6518     else result = WhiteQueen;
6519     if(!white) result = WHITE_TO_BLACK result;
6520     return result;
6521 }
6522
6523 static int autoQueen; // [HGM] oneclick
6524
6525 int
6526 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6527 {
6528     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6529     /* [HGM] add Shogi promotions */
6530     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6531     ChessSquare piece, partner;
6532     ChessMove moveType;
6533     Boolean premove;
6534
6535     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6536     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6537
6538     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6539       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6540         return FALSE;
6541
6542     piece = boards[currentMove][fromY][fromX];
6543     if(gameInfo.variant == VariantChu) {
6544         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6545         promotionZoneSize = BOARD_HEIGHT/3;
6546         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6547     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6548         promotionZoneSize = BOARD_HEIGHT/3;
6549         highestPromotingPiece = (int)WhiteAlfil;
6550     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6551         promotionZoneSize = 3;
6552     }
6553
6554     // Treat Lance as Pawn when it is not representing Amazon or Lance
6555     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6556         if(piece == WhiteLance) piece = WhitePawn; else
6557         if(piece == BlackLance) piece = BlackPawn;
6558     }
6559
6560     // next weed out all moves that do not touch the promotion zone at all
6561     if((int)piece >= BlackPawn) {
6562         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6563              return FALSE;
6564         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6565         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6566     } else {
6567         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6568            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6569         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6570              return FALSE;
6571     }
6572
6573     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6574
6575     // weed out mandatory Shogi promotions
6576     if(gameInfo.variant == VariantShogi) {
6577         if(piece >= BlackPawn) {
6578             if(toY == 0 && piece == BlackPawn ||
6579                toY == 0 && piece == BlackQueen ||
6580                toY <= 1 && piece == BlackKnight) {
6581                 *promoChoice = '+';
6582                 return FALSE;
6583             }
6584         } else {
6585             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6586                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6587                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         }
6592     }
6593
6594     // weed out obviously illegal Pawn moves
6595     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6596         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6597         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6598         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6599         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6600         // note we are not allowed to test for valid (non-)capture, due to premove
6601     }
6602
6603     // we either have a choice what to promote to, or (in Shogi) whether to promote
6604     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6605        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6606         ChessSquare p=BlackFerz;  // no choice
6607         while(p < EmptySquare) {  //but make sure we use piece that exists
6608             *promoChoice = PieceToChar(p++);
6609             if(*promoChoice != '.') break;
6610         }
6611         return FALSE;
6612     }
6613     // no sense asking what we must promote to if it is going to explode...
6614     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6615         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6616         return FALSE;
6617     }
6618     // give caller the default choice even if we will not make it
6619     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6620     partner = piece; // pieces can promote if the pieceToCharTable says so
6621     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6622     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6623     if(        sweepSelect && gameInfo.variant != VariantGreat
6624                            && gameInfo.variant != VariantGrand
6625                            && gameInfo.variant != VariantSuper) return FALSE;
6626     if(autoQueen) return FALSE; // predetermined
6627
6628     // suppress promotion popup on illegal moves that are not premoves
6629     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6630               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6631     if(appData.testLegality && !premove) {
6632         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6633                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6634         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6635         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6636             return FALSE;
6637     }
6638
6639     return TRUE;
6640 }
6641
6642 int
6643 InPalace (int row, int column)
6644 {   /* [HGM] for Xiangqi */
6645     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6646          column < (BOARD_WIDTH + 4)/2 &&
6647          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6648     return FALSE;
6649 }
6650
6651 int
6652 PieceForSquare (int x, int y)
6653 {
6654   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6655      return -1;
6656   else
6657      return boards[currentMove][y][x];
6658 }
6659
6660 int
6661 OKToStartUserMove (int x, int y)
6662 {
6663     ChessSquare from_piece;
6664     int white_piece;
6665
6666     if (matchMode) return FALSE;
6667     if (gameMode == EditPosition) return TRUE;
6668
6669     if (x >= 0 && y >= 0)
6670       from_piece = boards[currentMove][y][x];
6671     else
6672       from_piece = EmptySquare;
6673
6674     if (from_piece == EmptySquare) return FALSE;
6675
6676     white_piece = (int)from_piece >= (int)WhitePawn &&
6677       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6678
6679     switch (gameMode) {
6680       case AnalyzeFile:
6681       case TwoMachinesPlay:
6682       case EndOfGame:
6683         return FALSE;
6684
6685       case IcsObserving:
6686       case IcsIdle:
6687         return FALSE;
6688
6689       case MachinePlaysWhite:
6690       case IcsPlayingBlack:
6691         if (appData.zippyPlay) return FALSE;
6692         if (white_piece) {
6693             DisplayMoveError(_("You are playing Black"));
6694             return FALSE;
6695         }
6696         break;
6697
6698       case MachinePlaysBlack:
6699       case IcsPlayingWhite:
6700         if (appData.zippyPlay) return FALSE;
6701         if (!white_piece) {
6702             DisplayMoveError(_("You are playing White"));
6703             return FALSE;
6704         }
6705         break;
6706
6707       case PlayFromGameFile:
6708             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6709       case EditGame:
6710         if (!white_piece && WhiteOnMove(currentMove)) {
6711             DisplayMoveError(_("It is White's turn"));
6712             return FALSE;
6713         }
6714         if (white_piece && !WhiteOnMove(currentMove)) {
6715             DisplayMoveError(_("It is Black's turn"));
6716             return FALSE;
6717         }
6718         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6719             /* Editing correspondence game history */
6720             /* Could disallow this or prompt for confirmation */
6721             cmailOldMove = -1;
6722         }
6723         break;
6724
6725       case BeginningOfGame:
6726         if (appData.icsActive) return FALSE;
6727         if (!appData.noChessProgram) {
6728             if (!white_piece) {
6729                 DisplayMoveError(_("You are playing White"));
6730                 return FALSE;
6731             }
6732         }
6733         break;
6734
6735       case Training:
6736         if (!white_piece && WhiteOnMove(currentMove)) {
6737             DisplayMoveError(_("It is White's turn"));
6738             return FALSE;
6739         }
6740         if (white_piece && !WhiteOnMove(currentMove)) {
6741             DisplayMoveError(_("It is Black's turn"));
6742             return FALSE;
6743         }
6744         break;
6745
6746       default:
6747       case IcsExamining:
6748         break;
6749     }
6750     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6751         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6752         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6753         && gameMode != AnalyzeFile && gameMode != Training) {
6754         DisplayMoveError(_("Displayed position is not current"));
6755         return FALSE;
6756     }
6757     return TRUE;
6758 }
6759
6760 Boolean
6761 OnlyMove (int *x, int *y, Boolean captures)
6762 {
6763     DisambiguateClosure cl;
6764     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6765     switch(gameMode) {
6766       case MachinePlaysBlack:
6767       case IcsPlayingWhite:
6768       case BeginningOfGame:
6769         if(!WhiteOnMove(currentMove)) return FALSE;
6770         break;
6771       case MachinePlaysWhite:
6772       case IcsPlayingBlack:
6773         if(WhiteOnMove(currentMove)) return FALSE;
6774         break;
6775       case EditGame:
6776         break;
6777       default:
6778         return FALSE;
6779     }
6780     cl.pieceIn = EmptySquare;
6781     cl.rfIn = *y;
6782     cl.ffIn = *x;
6783     cl.rtIn = -1;
6784     cl.ftIn = -1;
6785     cl.promoCharIn = NULLCHAR;
6786     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6787     if( cl.kind == NormalMove ||
6788         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6789         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6790         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6791       fromX = cl.ff;
6792       fromY = cl.rf;
6793       *x = cl.ft;
6794       *y = cl.rt;
6795       return TRUE;
6796     }
6797     if(cl.kind != ImpossibleMove) return FALSE;
6798     cl.pieceIn = EmptySquare;
6799     cl.rfIn = -1;
6800     cl.ffIn = -1;
6801     cl.rtIn = *y;
6802     cl.ftIn = *x;
6803     cl.promoCharIn = NULLCHAR;
6804     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6805     if( cl.kind == NormalMove ||
6806         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6807         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6809       fromX = cl.ff;
6810       fromY = cl.rf;
6811       *x = cl.ft;
6812       *y = cl.rt;
6813       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6814       return TRUE;
6815     }
6816     return FALSE;
6817 }
6818
6819 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6820 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6821 int lastLoadGameUseList = FALSE;
6822 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6823 ChessMove lastLoadGameStart = EndOfFile;
6824 int doubleClick;
6825 Boolean addToBookFlag;
6826
6827 void
6828 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6829 {
6830     ChessMove moveType;
6831     ChessSquare pup;
6832     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6833
6834     /* Check if the user is playing in turn.  This is complicated because we
6835        let the user "pick up" a piece before it is his turn.  So the piece he
6836        tried to pick up may have been captured by the time he puts it down!
6837        Therefore we use the color the user is supposed to be playing in this
6838        test, not the color of the piece that is currently on the starting
6839        square---except in EditGame mode, where the user is playing both
6840        sides; fortunately there the capture race can't happen.  (It can
6841        now happen in IcsExamining mode, but that's just too bad.  The user
6842        will get a somewhat confusing message in that case.)
6843        */
6844
6845     switch (gameMode) {
6846       case AnalyzeFile:
6847       case TwoMachinesPlay:
6848       case EndOfGame:
6849       case IcsObserving:
6850       case IcsIdle:
6851         /* We switched into a game mode where moves are not accepted,
6852            perhaps while the mouse button was down. */
6853         return;
6854
6855       case MachinePlaysWhite:
6856         /* User is moving for Black */
6857         if (WhiteOnMove(currentMove)) {
6858             DisplayMoveError(_("It is White's turn"));
6859             return;
6860         }
6861         break;
6862
6863       case MachinePlaysBlack:
6864         /* User is moving for White */
6865         if (!WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is Black's turn"));
6867             return;
6868         }
6869         break;
6870
6871       case PlayFromGameFile:
6872             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6873       case EditGame:
6874       case IcsExamining:
6875       case BeginningOfGame:
6876       case AnalyzeMode:
6877       case Training:
6878         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6879         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6880             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6881             /* User is moving for Black */
6882             if (WhiteOnMove(currentMove)) {
6883                 DisplayMoveError(_("It is White's turn"));
6884                 return;
6885             }
6886         } else {
6887             /* User is moving for White */
6888             if (!WhiteOnMove(currentMove)) {
6889                 DisplayMoveError(_("It is Black's turn"));
6890                 return;
6891             }
6892         }
6893         break;
6894
6895       case IcsPlayingBlack:
6896         /* User is moving for Black */
6897         if (WhiteOnMove(currentMove)) {
6898             if (!appData.premove) {
6899                 DisplayMoveError(_("It is White's turn"));
6900             } else if (toX >= 0 && toY >= 0) {
6901                 premoveToX = toX;
6902                 premoveToY = toY;
6903                 premoveFromX = fromX;
6904                 premoveFromY = fromY;
6905                 premovePromoChar = promoChar;
6906                 gotPremove = 1;
6907                 if (appData.debugMode)
6908                     fprintf(debugFP, "Got premove: fromX %d,"
6909                             "fromY %d, toX %d, toY %d\n",
6910                             fromX, fromY, toX, toY);
6911             }
6912             return;
6913         }
6914         break;
6915
6916       case IcsPlayingWhite:
6917         /* User is moving for White */
6918         if (!WhiteOnMove(currentMove)) {
6919             if (!appData.premove) {
6920                 DisplayMoveError(_("It is Black's turn"));
6921             } else if (toX >= 0 && toY >= 0) {
6922                 premoveToX = toX;
6923                 premoveToY = toY;
6924                 premoveFromX = fromX;
6925                 premoveFromY = fromY;
6926                 premovePromoChar = promoChar;
6927                 gotPremove = 1;
6928                 if (appData.debugMode)
6929                     fprintf(debugFP, "Got premove: fromX %d,"
6930                             "fromY %d, toX %d, toY %d\n",
6931                             fromX, fromY, toX, toY);
6932             }
6933             return;
6934         }
6935         break;
6936
6937       default:
6938         break;
6939
6940       case EditPosition:
6941         /* EditPosition, empty square, or different color piece;
6942            click-click move is possible */
6943         if (toX == -2 || toY == -2) {
6944             boards[0][fromY][fromX] = EmptySquare;
6945             DrawPosition(FALSE, boards[currentMove]);
6946             return;
6947         } else if (toX >= 0 && toY >= 0) {
6948             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6949                 ChessSquare q, p = boards[0][rf][ff];
6950                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6951                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6952                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6953                 if(PieceToChar(q) == '+') gatingPiece = p;
6954             }
6955             boards[0][toY][toX] = boards[0][fromY][fromX];
6956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6957                 if(boards[0][fromY][0] != EmptySquare) {
6958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6960                 }
6961             } else
6962             if(fromX == BOARD_RGHT+1) {
6963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6966                 }
6967             } else
6968             boards[0][fromY][fromX] = gatingPiece;
6969             DrawPosition(FALSE, boards[currentMove]);
6970             return;
6971         }
6972         return;
6973     }
6974
6975     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6976     pup = boards[currentMove][toY][toX];
6977
6978     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6979     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6980          if( pup != EmptySquare ) return;
6981          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6982            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6983                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6984            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6985            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6986            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6987            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6988          fromY = DROP_RANK;
6989     }
6990
6991     /* [HGM] always test for legality, to get promotion info */
6992     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6993                                          fromY, fromX, toY, toX, promoChar);
6994
6995     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6996
6997     /* [HGM] but possibly ignore an IllegalMove result */
6998     if (appData.testLegality) {
6999         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7000             DisplayMoveError(_("Illegal move"));
7001             return;
7002         }
7003     }
7004
7005     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7006         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7007              ClearPremoveHighlights(); // was included
7008         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7009         return;
7010     }
7011
7012     if(addToBookFlag) { // adding moves to book
7013         char buf[MSG_SIZ], move[MSG_SIZ];
7014         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7015         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7016         AddBookMove(buf);
7017         addToBookFlag = FALSE;
7018         ClearHighlights();
7019         return;
7020     }
7021
7022     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7023 }
7024
7025 /* Common tail of UserMoveEvent and DropMenuEvent */
7026 int
7027 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7028 {
7029     char *bookHit = 0;
7030
7031     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7032         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7033         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7034         if(WhiteOnMove(currentMove)) {
7035             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7036         } else {
7037             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7038         }
7039     }
7040
7041     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7042        move type in caller when we know the move is a legal promotion */
7043     if(moveType == NormalMove && promoChar)
7044         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7045
7046     /* [HGM] <popupFix> The following if has been moved here from
7047        UserMoveEvent(). Because it seemed to belong here (why not allow
7048        piece drops in training games?), and because it can only be
7049        performed after it is known to what we promote. */
7050     if (gameMode == Training) {
7051       /* compare the move played on the board to the next move in the
7052        * game. If they match, display the move and the opponent's response.
7053        * If they don't match, display an error message.
7054        */
7055       int saveAnimate;
7056       Board testBoard;
7057       CopyBoard(testBoard, boards[currentMove]);
7058       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7059
7060       if (CompareBoards(testBoard, boards[currentMove+1])) {
7061         ForwardInner(currentMove+1);
7062
7063         /* Autoplay the opponent's response.
7064          * if appData.animate was TRUE when Training mode was entered,
7065          * the response will be animated.
7066          */
7067         saveAnimate = appData.animate;
7068         appData.animate = animateTraining;
7069         ForwardInner(currentMove+1);
7070         appData.animate = saveAnimate;
7071
7072         /* check for the end of the game */
7073         if (currentMove >= forwardMostMove) {
7074           gameMode = PlayFromGameFile;
7075           ModeHighlight();
7076           SetTrainingModeOff();
7077           DisplayInformation(_("End of game"));
7078         }
7079       } else {
7080         DisplayError(_("Incorrect move"), 0);
7081       }
7082       return 1;
7083     }
7084
7085   /* Ok, now we know that the move is good, so we can kill
7086      the previous line in Analysis Mode */
7087   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7088                                 && currentMove < forwardMostMove) {
7089     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7090     else forwardMostMove = currentMove;
7091   }
7092
7093   ClearMap();
7094
7095   /* If we need the chess program but it's dead, restart it */
7096   ResurrectChessProgram();
7097
7098   /* A user move restarts a paused game*/
7099   if (pausing)
7100     PauseEvent();
7101
7102   thinkOutput[0] = NULLCHAR;
7103
7104   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7105
7106   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7107     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7108     return 1;
7109   }
7110
7111   if (gameMode == BeginningOfGame) {
7112     if (appData.noChessProgram) {
7113       gameMode = EditGame;
7114       SetGameInfo();
7115     } else {
7116       char buf[MSG_SIZ];
7117       gameMode = MachinePlaysBlack;
7118       StartClocks();
7119       SetGameInfo();
7120       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7121       DisplayTitle(buf);
7122       if (first.sendName) {
7123         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7124         SendToProgram(buf, &first);
7125       }
7126       StartClocks();
7127     }
7128     ModeHighlight();
7129   }
7130
7131   /* Relay move to ICS or chess engine */
7132   if (appData.icsActive) {
7133     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7134         gameMode == IcsExamining) {
7135       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7136         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7137         SendToICS("draw ");
7138         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7139       }
7140       // also send plain move, in case ICS does not understand atomic claims
7141       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7142       ics_user_moved = 1;
7143     }
7144   } else {
7145     if (first.sendTime && (gameMode == BeginningOfGame ||
7146                            gameMode == MachinePlaysWhite ||
7147                            gameMode == MachinePlaysBlack)) {
7148       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7149     }
7150     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7151          // [HGM] book: if program might be playing, let it use book
7152         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7153         first.maybeThinking = TRUE;
7154     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7155         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7156         SendBoard(&first, currentMove+1);
7157         if(second.analyzing) {
7158             if(!second.useSetboard) SendToProgram("undo\n", &second);
7159             SendBoard(&second, currentMove+1);
7160         }
7161     } else {
7162         SendMoveToProgram(forwardMostMove-1, &first);
7163         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7164     }
7165     if (currentMove == cmailOldMove + 1) {
7166       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7167     }
7168   }
7169
7170   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7171
7172   switch (gameMode) {
7173   case EditGame:
7174     if(appData.testLegality)
7175     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7176     case MT_NONE:
7177     case MT_CHECK:
7178       break;
7179     case MT_CHECKMATE:
7180     case MT_STAINMATE:
7181       if (WhiteOnMove(currentMove)) {
7182         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7183       } else {
7184         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7185       }
7186       break;
7187     case MT_STALEMATE:
7188       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7189       break;
7190     }
7191     break;
7192
7193   case MachinePlaysBlack:
7194   case MachinePlaysWhite:
7195     /* disable certain menu options while machine is thinking */
7196     SetMachineThinkingEnables();
7197     break;
7198
7199   default:
7200     break;
7201   }
7202
7203   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7204   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7205
7206   if(bookHit) { // [HGM] book: simulate book reply
7207         static char bookMove[MSG_SIZ]; // a bit generous?
7208
7209         programStats.nodes = programStats.depth = programStats.time =
7210         programStats.score = programStats.got_only_move = 0;
7211         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7212
7213         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7214         strcat(bookMove, bookHit);
7215         HandleMachineMove(bookMove, &first);
7216   }
7217   return 1;
7218 }
7219
7220 void
7221 MarkByFEN(char *fen)
7222 {
7223         int r, f;
7224         if(!appData.markers || !appData.highlightDragging) return;
7225         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7226         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7227         while(*fen) {
7228             int s = 0;
7229             marker[r][f] = 0;
7230             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7231             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7232             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7233             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7234             if(*fen == 'T') marker[r][f++] = 0; else
7235             if(*fen == 'Y') marker[r][f++] = 1; else
7236             if(*fen == 'G') marker[r][f++] = 3; else
7237             if(*fen == 'B') marker[r][f++] = 4; else
7238             if(*fen == 'C') marker[r][f++] = 5; else
7239             if(*fen == 'M') marker[r][f++] = 6; else
7240             if(*fen == 'W') marker[r][f++] = 7; else
7241             if(*fen == 'D') marker[r][f++] = 8; else
7242             if(*fen == 'R') marker[r][f++] = 2; else {
7243                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7244               f += s; fen -= s>0;
7245             }
7246             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7247             if(r < 0) break;
7248             fen++;
7249         }
7250         DrawPosition(TRUE, NULL);
7251 }
7252
7253 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7254
7255 void
7256 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7257 {
7258     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7259     Markers *m = (Markers *) closure;
7260     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7261         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7262                          || kind == WhiteCapturesEnPassant
7263                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7264     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7265 }
7266
7267 static int hoverSavedValid;
7268
7269 void
7270 MarkTargetSquares (int clear)
7271 {
7272   int x, y, sum=0;
7273   if(clear) { // no reason to ever suppress clearing
7274     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7275     hoverSavedValid = 0;
7276     if(!sum) return; // nothing was cleared,no redraw needed
7277   } else {
7278     int capt = 0;
7279     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7280        !appData.testLegality || gameMode == EditPosition) return;
7281     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7282     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7283       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7284       if(capt)
7285       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7286     }
7287   }
7288   DrawPosition(FALSE, NULL);
7289 }
7290
7291 int
7292 Explode (Board board, int fromX, int fromY, int toX, int toY)
7293 {
7294     if(gameInfo.variant == VariantAtomic &&
7295        (board[toY][toX] != EmptySquare ||                     // capture?
7296         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7297                          board[fromY][fromX] == BlackPawn   )
7298       )) {
7299         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7300         return TRUE;
7301     }
7302     return FALSE;
7303 }
7304
7305 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7306
7307 int
7308 CanPromote (ChessSquare piece, int y)
7309 {
7310         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7311         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7312         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7313         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7314            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7315            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7316          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7317         return (piece == BlackPawn && y <= zone ||
7318                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7319                 piece == BlackLance && y == 1 ||
7320                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7321 }
7322
7323 void
7324 HoverEvent (int xPix, int yPix, int x, int y)
7325 {
7326         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7327         int r, f;
7328         if(!first.highlight) return;
7329         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7330         if(x == oldX && y == oldY) return; // only do something if we enter new square
7331         oldFromX = fromX; oldFromY = fromY;
7332         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7333           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7334             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7335           hoverSavedValid = 1;
7336         } else if(oldX != x || oldY != y) {
7337           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7338           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7339           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7340             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7341           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7342             char buf[MSG_SIZ];
7343             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7344             SendToProgram(buf, &first);
7345           }
7346           oldX = x; oldY = y;
7347 //        SetHighlights(fromX, fromY, x, y);
7348         }
7349 }
7350
7351 void ReportClick(char *action, int x, int y)
7352 {
7353         char buf[MSG_SIZ]; // Inform engine of what user does
7354         int r, f;
7355         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7356           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7357         if(!first.highlight || gameMode == EditPosition) return;
7358         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7359         SendToProgram(buf, &first);
7360 }
7361
7362 void
7363 LeftClick (ClickType clickType, int xPix, int yPix)
7364 {
7365     int x, y;
7366     Boolean saveAnimate;
7367     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7368     char promoChoice = NULLCHAR;
7369     ChessSquare piece;
7370     static TimeMark lastClickTime, prevClickTime;
7371
7372     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7373
7374     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7375
7376     if (clickType == Press) ErrorPopDown();
7377     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7378
7379     x = EventToSquare(xPix, BOARD_WIDTH);
7380     y = EventToSquare(yPix, BOARD_HEIGHT);
7381     if (!flipView && y >= 0) {
7382         y = BOARD_HEIGHT - 1 - y;
7383     }
7384     if (flipView && x >= 0) {
7385         x = BOARD_WIDTH - 1 - x;
7386     }
7387
7388     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7389         defaultPromoChoice = promoSweep;
7390         promoSweep = EmptySquare;   // terminate sweep
7391         promoDefaultAltered = TRUE;
7392         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7393     }
7394
7395     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7396         if(clickType == Release) return; // ignore upclick of click-click destination
7397         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7398         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7399         if(gameInfo.holdingsWidth &&
7400                 (WhiteOnMove(currentMove)
7401                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7402                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7403             // click in right holdings, for determining promotion piece
7404             ChessSquare p = boards[currentMove][y][x];
7405             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7406             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7407             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7408                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7409                 fromX = fromY = -1;
7410                 return;
7411             }
7412         }
7413         DrawPosition(FALSE, boards[currentMove]);
7414         return;
7415     }
7416
7417     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7418     if(clickType == Press
7419             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7420               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7421               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7422         return;
7423
7424     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7425         // could be static click on premove from-square: abort premove
7426         gotPremove = 0;
7427         ClearPremoveHighlights();
7428     }
7429
7430     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7431         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7432
7433     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7434         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7435                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7436         defaultPromoChoice = DefaultPromoChoice(side);
7437     }
7438
7439     autoQueen = appData.alwaysPromoteToQueen;
7440
7441     if (fromX == -1) {
7442       int originalY = y;
7443       gatingPiece = EmptySquare;
7444       if (clickType != Press) {
7445         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7446             DragPieceEnd(xPix, yPix); dragging = 0;
7447             DrawPosition(FALSE, NULL);
7448         }
7449         return;
7450       }
7451       doubleClick = FALSE;
7452       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7453         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7454       }
7455       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7456       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7457          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7458          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7459             /* First square */
7460             if (OKToStartUserMove(fromX, fromY)) {
7461                 second = 0;
7462                 ReportClick("lift", x, y);
7463                 MarkTargetSquares(0);
7464                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7465                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7466                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7467                     promoSweep = defaultPromoChoice;
7468                     selectFlag = 0; lastX = xPix; lastY = yPix;
7469                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7470                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7471                 }
7472                 if (appData.highlightDragging) {
7473                     SetHighlights(fromX, fromY, -1, -1);
7474                 } else {
7475                     ClearHighlights();
7476                 }
7477             } else fromX = fromY = -1;
7478             return;
7479         }
7480     }
7481
7482     /* fromX != -1 */
7483     if (clickType == Press && gameMode != EditPosition) {
7484         ChessSquare fromP;
7485         ChessSquare toP;
7486         int frc;
7487
7488         // ignore off-board to clicks
7489         if(y < 0 || x < 0) return;
7490
7491         /* Check if clicking again on the same color piece */
7492         fromP = boards[currentMove][fromY][fromX];
7493         toP = boards[currentMove][y][x];
7494         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7495         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7496            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7497              WhitePawn <= toP && toP <= WhiteKing &&
7498              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7499              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7500             (BlackPawn <= fromP && fromP <= BlackKing &&
7501              BlackPawn <= toP && toP <= BlackKing &&
7502              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7503              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7504             /* Clicked again on same color piece -- changed his mind */
7505             second = (x == fromX && y == fromY);
7506             killX = killY = -1;
7507             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7508                 second = FALSE; // first double-click rather than scond click
7509                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7510             }
7511             promoDefaultAltered = FALSE;
7512             MarkTargetSquares(1);
7513            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7514             if (appData.highlightDragging) {
7515                 SetHighlights(x, y, -1, -1);
7516             } else {
7517                 ClearHighlights();
7518             }
7519             if (OKToStartUserMove(x, y)) {
7520                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7521                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7522                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7523                  gatingPiece = boards[currentMove][fromY][fromX];
7524                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7525                 fromX = x;
7526                 fromY = y; dragging = 1;
7527                 ReportClick("lift", x, y);
7528                 MarkTargetSquares(0);
7529                 DragPieceBegin(xPix, yPix, FALSE);
7530                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7531                     promoSweep = defaultPromoChoice;
7532                     selectFlag = 0; lastX = xPix; lastY = yPix;
7533                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7534                 }
7535             }
7536            }
7537            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7538            second = FALSE;
7539         }
7540         // ignore clicks on holdings
7541         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7542     }
7543
7544     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7545         DragPieceEnd(xPix, yPix); dragging = 0;
7546         if(clearFlag) {
7547             // a deferred attempt to click-click move an empty square on top of a piece
7548             boards[currentMove][y][x] = EmptySquare;
7549             ClearHighlights();
7550             DrawPosition(FALSE, boards[currentMove]);
7551             fromX = fromY = -1; clearFlag = 0;
7552             return;
7553         }
7554         if (appData.animateDragging) {
7555             /* Undo animation damage if any */
7556             DrawPosition(FALSE, NULL);
7557         }
7558         if (second || sweepSelecting) {
7559             /* Second up/down in same square; just abort move */
7560             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7561             second = sweepSelecting = 0;
7562             fromX = fromY = -1;
7563             gatingPiece = EmptySquare;
7564             MarkTargetSquares(1);
7565             ClearHighlights();
7566             gotPremove = 0;
7567             ClearPremoveHighlights();
7568         } else {
7569             /* First upclick in same square; start click-click mode */
7570             SetHighlights(x, y, -1, -1);
7571         }
7572         return;
7573     }
7574
7575     clearFlag = 0;
7576
7577     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7578         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7579         DisplayMessage(_("only marked squares are legal"),"");
7580         DrawPosition(TRUE, NULL);
7581         return; // ignore to-click
7582     }
7583
7584     /* we now have a different from- and (possibly off-board) to-square */
7585     /* Completed move */
7586     if(!sweepSelecting) {
7587         toX = x;
7588         toY = y;
7589     }
7590
7591     piece = boards[currentMove][fromY][fromX];
7592
7593     saveAnimate = appData.animate;
7594     if (clickType == Press) {
7595         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7596         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7597             // must be Edit Position mode with empty-square selected
7598             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7599             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7600             return;
7601         }
7602         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7603             return;
7604         }
7605         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7606             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7607         } else
7608         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7609         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7610           if(appData.sweepSelect) {
7611             promoSweep = defaultPromoChoice;
7612             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7613             selectFlag = 0; lastX = xPix; lastY = yPix;
7614             Sweep(0); // Pawn that is going to promote: preview promotion piece
7615             sweepSelecting = 1;
7616             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7617             MarkTargetSquares(1);
7618           }
7619           return; // promo popup appears on up-click
7620         }
7621         /* Finish clickclick move */
7622         if (appData.animate || appData.highlightLastMove) {
7623             SetHighlights(fromX, fromY, toX, toY);
7624         } else {
7625             ClearHighlights();
7626         }
7627     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7628         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7629         if (appData.animate || appData.highlightLastMove) {
7630             SetHighlights(fromX, fromY, toX, toY);
7631         } else {
7632             ClearHighlights();
7633         }
7634     } else {
7635 #if 0
7636 // [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
7637         /* Finish drag move */
7638         if (appData.highlightLastMove) {
7639             SetHighlights(fromX, fromY, toX, toY);
7640         } else {
7641             ClearHighlights();
7642         }
7643 #endif
7644         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7645         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7646           dragging *= 2;            // flag button-less dragging if we are dragging
7647           MarkTargetSquares(1);
7648           if(x == killX && y == killY) killX = killY = -1; else {
7649             killX = x; killY = y;     //remeber this square as intermediate
7650             ReportClick("put", x, y); // and inform engine
7651             ReportClick("lift", x, y);
7652             MarkTargetSquares(0);
7653             return;
7654           }
7655         }
7656         DragPieceEnd(xPix, yPix); dragging = 0;
7657         /* Don't animate move and drag both */
7658         appData.animate = FALSE;
7659     }
7660
7661     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7662     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7663         ChessSquare piece = boards[currentMove][fromY][fromX];
7664         if(gameMode == EditPosition && piece != EmptySquare &&
7665            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7666             int n;
7667
7668             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7669                 n = PieceToNumber(piece - (int)BlackPawn);
7670                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7671                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7672                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7673             } else
7674             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7675                 n = PieceToNumber(piece);
7676                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7677                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7678                 boards[currentMove][n][BOARD_WIDTH-2]++;
7679             }
7680             boards[currentMove][fromY][fromX] = EmptySquare;
7681         }
7682         ClearHighlights();
7683         fromX = fromY = -1;
7684         MarkTargetSquares(1);
7685         DrawPosition(TRUE, boards[currentMove]);
7686         return;
7687     }
7688
7689     // off-board moves should not be highlighted
7690     if(x < 0 || y < 0) ClearHighlights();
7691     else ReportClick("put", x, y);
7692
7693     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7694
7695     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7696         SetHighlights(fromX, fromY, toX, toY);
7697         MarkTargetSquares(1);
7698         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7699             // [HGM] super: promotion to captured piece selected from holdings
7700             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7701             promotionChoice = TRUE;
7702             // kludge follows to temporarily execute move on display, without promoting yet
7703             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7704             boards[currentMove][toY][toX] = p;
7705             DrawPosition(FALSE, boards[currentMove]);
7706             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7707             boards[currentMove][toY][toX] = q;
7708             DisplayMessage("Click in holdings to choose piece", "");
7709             return;
7710         }
7711         PromotionPopUp(promoChoice);
7712     } else {
7713         int oldMove = currentMove;
7714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7717         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7718            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7719             DrawPosition(TRUE, boards[currentMove]);
7720         MarkTargetSquares(1);
7721         fromX = fromY = -1;
7722     }
7723     appData.animate = saveAnimate;
7724     if (appData.animate || appData.animateDragging) {
7725         /* Undo animation damage if needed */
7726         DrawPosition(FALSE, NULL);
7727     }
7728 }
7729
7730 int
7731 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7732 {   // front-end-free part taken out of PieceMenuPopup
7733     int whichMenu; int xSqr, ySqr;
7734
7735     if(seekGraphUp) { // [HGM] seekgraph
7736         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7737         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7738         return -2;
7739     }
7740
7741     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7742          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7743         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7744         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7745         if(action == Press)   {
7746             originalFlip = flipView;
7747             flipView = !flipView; // temporarily flip board to see game from partners perspective
7748             DrawPosition(TRUE, partnerBoard);
7749             DisplayMessage(partnerStatus, "");
7750             partnerUp = TRUE;
7751         } else if(action == Release) {
7752             flipView = originalFlip;
7753             DrawPosition(TRUE, boards[currentMove]);
7754             partnerUp = FALSE;
7755         }
7756         return -2;
7757     }
7758
7759     xSqr = EventToSquare(x, BOARD_WIDTH);
7760     ySqr = EventToSquare(y, BOARD_HEIGHT);
7761     if (action == Release) {
7762         if(pieceSweep != EmptySquare) {
7763             EditPositionMenuEvent(pieceSweep, toX, toY);
7764             pieceSweep = EmptySquare;
7765         } else UnLoadPV(); // [HGM] pv
7766     }
7767     if (action != Press) return -2; // return code to be ignored
7768     switch (gameMode) {
7769       case IcsExamining:
7770         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7771       case EditPosition:
7772         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7773         if (xSqr < 0 || ySqr < 0) return -1;
7774         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7775         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7776         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7777         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7778         NextPiece(0);
7779         return 2; // grab
7780       case IcsObserving:
7781         if(!appData.icsEngineAnalyze) return -1;
7782       case IcsPlayingWhite:
7783       case IcsPlayingBlack:
7784         if(!appData.zippyPlay) goto noZip;
7785       case AnalyzeMode:
7786       case AnalyzeFile:
7787       case MachinePlaysWhite:
7788       case MachinePlaysBlack:
7789       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7790         if (!appData.dropMenu) {
7791           LoadPV(x, y);
7792           return 2; // flag front-end to grab mouse events
7793         }
7794         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7795            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7796       case EditGame:
7797       noZip:
7798         if (xSqr < 0 || ySqr < 0) return -1;
7799         if (!appData.dropMenu || appData.testLegality &&
7800             gameInfo.variant != VariantBughouse &&
7801             gameInfo.variant != VariantCrazyhouse) return -1;
7802         whichMenu = 1; // drop menu
7803         break;
7804       default:
7805         return -1;
7806     }
7807
7808     if (((*fromX = xSqr) < 0) ||
7809         ((*fromY = ySqr) < 0)) {
7810         *fromX = *fromY = -1;
7811         return -1;
7812     }
7813     if (flipView)
7814       *fromX = BOARD_WIDTH - 1 - *fromX;
7815     else
7816       *fromY = BOARD_HEIGHT - 1 - *fromY;
7817
7818     return whichMenu;
7819 }
7820
7821 void
7822 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7823 {
7824 //    char * hint = lastHint;
7825     FrontEndProgramStats stats;
7826
7827     stats.which = cps == &first ? 0 : 1;
7828     stats.depth = cpstats->depth;
7829     stats.nodes = cpstats->nodes;
7830     stats.score = cpstats->score;
7831     stats.time = cpstats->time;
7832     stats.pv = cpstats->movelist;
7833     stats.hint = lastHint;
7834     stats.an_move_index = 0;
7835     stats.an_move_count = 0;
7836
7837     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7838         stats.hint = cpstats->move_name;
7839         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7840         stats.an_move_count = cpstats->nr_moves;
7841     }
7842
7843     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
7844
7845     SetProgramStats( &stats );
7846 }
7847
7848 void
7849 ClearEngineOutputPane (int which)
7850 {
7851     static FrontEndProgramStats dummyStats;
7852     dummyStats.which = which;
7853     dummyStats.pv = "#";
7854     SetProgramStats( &dummyStats );
7855 }
7856
7857 #define MAXPLAYERS 500
7858
7859 char *
7860 TourneyStandings (int display)
7861 {
7862     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7863     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7864     char result, *p, *names[MAXPLAYERS];
7865
7866     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7867         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7868     names[0] = p = strdup(appData.participants);
7869     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7870
7871     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7872
7873     while(result = appData.results[nr]) {
7874         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7875         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7876         wScore = bScore = 0;
7877         switch(result) {
7878           case '+': wScore = 2; break;
7879           case '-': bScore = 2; break;
7880           case '=': wScore = bScore = 1; break;
7881           case ' ':
7882           case '*': return strdup("busy"); // tourney not finished
7883         }
7884         score[w] += wScore;
7885         score[b] += bScore;
7886         games[w]++;
7887         games[b]++;
7888         nr++;
7889     }
7890     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7891     for(w=0; w<nPlayers; w++) {
7892         bScore = -1;
7893         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7894         ranking[w] = b; points[w] = bScore; score[b] = -2;
7895     }
7896     p = malloc(nPlayers*34+1);
7897     for(w=0; w<nPlayers && w<display; w++)
7898         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7899     free(names[0]);
7900     return p;
7901 }
7902
7903 void
7904 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7905 {       // count all piece types
7906         int p, f, r;
7907         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7908         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7909         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7910                 p = board[r][f];
7911                 pCnt[p]++;
7912                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7913                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7914                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7915                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7916                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7917                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7918         }
7919 }
7920
7921 int
7922 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7923 {
7924         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7925         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7926
7927         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7928         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7929         if(myPawns == 2 && nMine == 3) // KPP
7930             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7931         if(myPawns == 1 && nMine == 2) // KP
7932             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7933         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7935         if(myPawns) return FALSE;
7936         if(pCnt[WhiteRook+side])
7937             return pCnt[BlackRook-side] ||
7938                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7939                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7940                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7941         if(pCnt[WhiteCannon+side]) {
7942             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7943             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7944         }
7945         if(pCnt[WhiteKnight+side])
7946             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7947         return FALSE;
7948 }
7949
7950 int
7951 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7952 {
7953         VariantClass v = gameInfo.variant;
7954
7955         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7956         if(v == VariantShatranj) return TRUE; // always winnable through baring
7957         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7958         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7959
7960         if(v == VariantXiangqi) {
7961                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7962
7963                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7964                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7965                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7966                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7967                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7968                 if(stale) // we have at least one last-rank P plus perhaps C
7969                     return majors // KPKX
7970                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7971                 else // KCA*E*
7972                     return pCnt[WhiteFerz+side] // KCAK
7973                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7974                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7975                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7976
7977         } else if(v == VariantKnightmate) {
7978                 if(nMine == 1) return FALSE;
7979                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7980         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7981                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7982
7983                 if(nMine == 1) return FALSE; // bare King
7984                 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
7985                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7986                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7987                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7988                 if(pCnt[WhiteKnight+side])
7989                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7990                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7991                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7992                 if(nBishops)
7993                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7994                 if(pCnt[WhiteAlfil+side])
7995                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7996                 if(pCnt[WhiteWazir+side])
7997                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7998         }
7999
8000         return TRUE;
8001 }
8002
8003 int
8004 CompareWithRights (Board b1, Board b2)
8005 {
8006     int rights = 0;
8007     if(!CompareBoards(b1, b2)) return FALSE;
8008     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8009     /* compare castling rights */
8010     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8011            rights++; /* King lost rights, while rook still had them */
8012     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8013         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8014            rights++; /* but at least one rook lost them */
8015     }
8016     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8017            rights++;
8018     if( b1[CASTLING][5] != NoRights ) {
8019         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8020            rights++;
8021     }
8022     return rights == 0;
8023 }
8024
8025 int
8026 Adjudicate (ChessProgramState *cps)
8027 {       // [HGM] some adjudications useful with buggy engines
8028         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8029         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8030         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8031         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8032         int k, drop, count = 0; static int bare = 1;
8033         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8034         Boolean canAdjudicate = !appData.icsActive;
8035
8036         // most tests only when we understand the game, i.e. legality-checking on
8037             if( appData.testLegality )
8038             {   /* [HGM] Some more adjudications for obstinate engines */
8039                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8040                 static int moveCount = 6;
8041                 ChessMove result;
8042                 char *reason = NULL;
8043
8044                 /* Count what is on board. */
8045                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8046
8047                 /* Some material-based adjudications that have to be made before stalemate test */
8048                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8049                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8050                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8051                      if(canAdjudicate && appData.checkMates) {
8052                          if(engineOpponent)
8053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8054                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8055                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8056                          return 1;
8057                      }
8058                 }
8059
8060                 /* Bare King in Shatranj (loses) or Losers (wins) */
8061                 if( nrW == 1 || nrB == 1) {
8062                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8063                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8064                      if(canAdjudicate && appData.checkMates) {
8065                          if(engineOpponent)
8066                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8067                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8069                          return 1;
8070                      }
8071                   } else
8072                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8073                   {    /* bare King */
8074                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8075                         if(canAdjudicate && appData.checkMates) {
8076                             /* but only adjudicate if adjudication enabled */
8077                             if(engineOpponent)
8078                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8079                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8080                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8081                             return 1;
8082                         }
8083                   }
8084                 } else bare = 1;
8085
8086
8087             // don't wait for engine to announce game end if we can judge ourselves
8088             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8089               case MT_CHECK:
8090                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8091                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8092                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8093                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8094                             checkCnt++;
8095                         if(checkCnt >= 2) {
8096                             reason = "Xboard adjudication: 3rd check";
8097                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8098                             break;
8099                         }
8100                     }
8101                 }
8102               case MT_NONE:
8103               default:
8104                 break;
8105               case MT_STEALMATE:
8106               case MT_STALEMATE:
8107               case MT_STAINMATE:
8108                 reason = "Xboard adjudication: Stalemate";
8109                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8110                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8111                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8112                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8113                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8114                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8115                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8116                                                                         EP_CHECKMATE : EP_WINS);
8117                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8118                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8119                 }
8120                 break;
8121               case MT_CHECKMATE:
8122                 reason = "Xboard adjudication: Checkmate";
8123                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8124                 if(gameInfo.variant == VariantShogi) {
8125                     if(forwardMostMove > backwardMostMove
8126                        && moveList[forwardMostMove-1][1] == '@'
8127                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8128                         reason = "XBoard adjudication: pawn-drop mate";
8129                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8130                     }
8131                 }
8132                 break;
8133             }
8134
8135                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8136                     case EP_STALEMATE:
8137                         result = GameIsDrawn; break;
8138                     case EP_CHECKMATE:
8139                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8140                     case EP_WINS:
8141                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8142                     default:
8143                         result = EndOfFile;
8144                 }
8145                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8146                     if(engineOpponent)
8147                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8148                     GameEnds( result, reason, GE_XBOARD );
8149                     return 1;
8150                 }
8151
8152                 /* Next absolutely insufficient mating material. */
8153                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8154                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8155                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8156
8157                      /* always flag draws, for judging claims */
8158                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8159
8160                      if(canAdjudicate && appData.materialDraws) {
8161                          /* but only adjudicate them if adjudication enabled */
8162                          if(engineOpponent) {
8163                            SendToProgram("force\n", engineOpponent); // suppress reply
8164                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8165                          }
8166                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8167                          return 1;
8168                      }
8169                 }
8170
8171                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8172                 if(gameInfo.variant == VariantXiangqi ?
8173                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8174                  : nrW + nrB == 4 &&
8175                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8176                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8177                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8178                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8179                    ) ) {
8180                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8181                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8182                           if(engineOpponent) {
8183                             SendToProgram("force\n", engineOpponent); // suppress reply
8184                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8185                           }
8186                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8187                           return 1;
8188                      }
8189                 } else moveCount = 6;
8190             }
8191
8192         // Repetition draws and 50-move rule can be applied independently of legality testing
8193
8194                 /* Check for rep-draws */
8195                 count = 0;
8196                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8197                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8198                 for(k = forwardMostMove-2;
8199                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8200                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8201                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8202                     k-=2)
8203                 {   int rights=0;
8204                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8205                         /* compare castling rights */
8206                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8207                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8208                                 rights++; /* King lost rights, while rook still had them */
8209                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8210                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8211                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8212                                    rights++; /* but at least one rook lost them */
8213                         }
8214                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8215                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8216                                 rights++;
8217                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8218                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8219                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8220                                    rights++;
8221                         }
8222                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8223                             && appData.drawRepeats > 1) {
8224                              /* adjudicate after user-specified nr of repeats */
8225                              int result = GameIsDrawn;
8226                              char *details = "XBoard adjudication: repetition draw";
8227                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8228                                 // [HGM] xiangqi: check for forbidden perpetuals
8229                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8230                                 for(m=forwardMostMove; m>k; m-=2) {
8231                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8232                                         ourPerpetual = 0; // the current mover did not always check
8233                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8234                                         hisPerpetual = 0; // the opponent did not always check
8235                                 }
8236                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8237                                                                         ourPerpetual, hisPerpetual);
8238                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8239                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8240                                     details = "Xboard adjudication: perpetual checking";
8241                                 } else
8242                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8243                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8244                                 } else
8245                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8246                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8247                                         result = BlackWins;
8248                                         details = "Xboard adjudication: repetition";
8249                                     }
8250                                 } else // it must be XQ
8251                                 // Now check for perpetual chases
8252                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8253                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8254                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8255                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8256                                         static char resdet[MSG_SIZ];
8257                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8258                                         details = resdet;
8259                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8260                                     } else
8261                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8262                                         break; // Abort repetition-checking loop.
8263                                 }
8264                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8265                              }
8266                              if(engineOpponent) {
8267                                SendToProgram("force\n", engineOpponent); // suppress reply
8268                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8269                              }
8270                              GameEnds( result, details, GE_XBOARD );
8271                              return 1;
8272                         }
8273                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8274                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8275                     }
8276                 }
8277
8278                 /* Now we test for 50-move draws. Determine ply count */
8279                 count = forwardMostMove;
8280                 /* look for last irreversble move */
8281                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8282                     count--;
8283                 /* if we hit starting position, add initial plies */
8284                 if( count == backwardMostMove )
8285                     count -= initialRulePlies;
8286                 count = forwardMostMove - count;
8287                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8288                         // adjust reversible move counter for checks in Xiangqi
8289                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8290                         if(i < backwardMostMove) i = backwardMostMove;
8291                         while(i <= forwardMostMove) {
8292                                 lastCheck = inCheck; // check evasion does not count
8293                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8294                                 if(inCheck || lastCheck) count--; // check does not count
8295                                 i++;
8296                         }
8297                 }
8298                 if( count >= 100)
8299                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8300                          /* this is used to judge if draw claims are legal */
8301                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8302                          if(engineOpponent) {
8303                            SendToProgram("force\n", engineOpponent); // suppress reply
8304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8305                          }
8306                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8307                          return 1;
8308                 }
8309
8310                 /* if draw offer is pending, treat it as a draw claim
8311                  * when draw condition present, to allow engines a way to
8312                  * claim draws before making their move to avoid a race
8313                  * condition occurring after their move
8314                  */
8315                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8316                          char *p = NULL;
8317                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8318                              p = "Draw claim: 50-move rule";
8319                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8320                              p = "Draw claim: 3-fold repetition";
8321                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8322                              p = "Draw claim: insufficient mating material";
8323                          if( p != NULL && canAdjudicate) {
8324                              if(engineOpponent) {
8325                                SendToProgram("force\n", engineOpponent); // suppress reply
8326                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8327                              }
8328                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8329                              return 1;
8330                          }
8331                 }
8332
8333                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8334                     if(engineOpponent) {
8335                       SendToProgram("force\n", engineOpponent); // suppress reply
8336                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                     }
8338                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8339                     return 1;
8340                 }
8341         return 0;
8342 }
8343
8344 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8345 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8346 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8347
8348 static int
8349 BitbaseProbe ()
8350 {
8351     int pieces[10], squares[10], cnt=0, r, f, res;
8352     static int loaded;
8353     static PPROBE_EGBB probeBB;
8354     if(!appData.testLegality) return 10;
8355     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8356     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8357     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8358     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8359         ChessSquare piece = boards[forwardMostMove][r][f];
8360         int black = (piece >= BlackPawn);
8361         int type = piece - black*BlackPawn;
8362         if(piece == EmptySquare) continue;
8363         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8364         if(type == WhiteKing) type = WhiteQueen + 1;
8365         type = egbbCode[type];
8366         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8367         pieces[cnt] = type + black*6;
8368         if(++cnt > 5) return 11;
8369     }
8370     pieces[cnt] = squares[cnt] = 0;
8371     // probe EGBB
8372     if(loaded == 2) return 13; // loading failed before
8373     if(loaded == 0) {
8374         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8375         HMODULE lib;
8376         PLOAD_EGBB loadBB;
8377         loaded = 2; // prepare for failure
8378         if(!path) return 13; // no egbb installed
8379         strncpy(buf, path + 8, MSG_SIZ);
8380         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8381         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8382         lib = LoadLibrary(buf);
8383         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8384         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8385         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8386         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8387         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8388         loaded = 1; // success!
8389     }
8390     res = probeBB(forwardMostMove & 1, pieces, squares);
8391     return res > 0 ? 1 : res < 0 ? -1 : 0;
8392 }
8393
8394 char *
8395 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8396 {   // [HGM] book: this routine intercepts moves to simulate book replies
8397     char *bookHit = NULL;
8398
8399     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8400         char buf[MSG_SIZ];
8401         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8402         SendToProgram(buf, cps);
8403     }
8404     //first determine if the incoming move brings opponent into his book
8405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8408     if(bookHit != NULL && !cps->bookSuspend) {
8409         // make sure opponent is not going to reply after receiving move to book position
8410         SendToProgram("force\n", cps);
8411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8412     }
8413     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8414     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8415     // now arrange restart after book miss
8416     if(bookHit) {
8417         // after a book hit we never send 'go', and the code after the call to this routine
8418         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8419         char buf[MSG_SIZ], *move = bookHit;
8420         if(cps->useSAN) {
8421             int fromX, fromY, toX, toY;
8422             char promoChar;
8423             ChessMove moveType;
8424             move = buf + 30;
8425             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8426                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8427                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8428                                     PosFlags(forwardMostMove),
8429                                     fromY, fromX, toY, toX, promoChar, move);
8430             } else {
8431                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8432                 bookHit = NULL;
8433             }
8434         }
8435         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8436         SendToProgram(buf, cps);
8437         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8438     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8439         SendToProgram("go\n", cps);
8440         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8441     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8442         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8443             SendToProgram("go\n", cps);
8444         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8445     }
8446     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8447 }
8448
8449 int
8450 LoadError (char *errmess, ChessProgramState *cps)
8451 {   // unloads engine and switches back to -ncp mode if it was first
8452     if(cps->initDone) return FALSE;
8453     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8454     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8455     cps->pr = NoProc;
8456     if(cps == &first) {
8457         appData.noChessProgram = TRUE;
8458         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8459         gameMode = BeginningOfGame; ModeHighlight();
8460         SetNCPMode();
8461     }
8462     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8463     DisplayMessage("", ""); // erase waiting message
8464     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8465     return TRUE;
8466 }
8467
8468 char *savedMessage;
8469 ChessProgramState *savedState;
8470 void
8471 DeferredBookMove (void)
8472 {
8473         if(savedState->lastPing != savedState->lastPong)
8474                     ScheduleDelayedEvent(DeferredBookMove, 10);
8475         else
8476         HandleMachineMove(savedMessage, savedState);
8477 }
8478
8479 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8480 static ChessProgramState *stalledEngine;
8481 static char stashedInputMove[MSG_SIZ];
8482
8483 void
8484 HandleMachineMove (char *message, ChessProgramState *cps)
8485 {
8486     static char firstLeg[20];
8487     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8488     char realname[MSG_SIZ];
8489     int fromX, fromY, toX, toY;
8490     ChessMove moveType;
8491     char promoChar, roar;
8492     char *p, *pv=buf1;
8493     int machineWhite, oldError;
8494     char *bookHit;
8495
8496     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8497         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8498         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8499             DisplayError(_("Invalid pairing from pairing engine"), 0);
8500             return;
8501         }
8502         pairingReceived = 1;
8503         NextMatchGame();
8504         return; // Skim the pairing messages here.
8505     }
8506
8507     oldError = cps->userError; cps->userError = 0;
8508
8509 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8510     /*
8511      * Kludge to ignore BEL characters
8512      */
8513     while (*message == '\007') message++;
8514
8515     /*
8516      * [HGM] engine debug message: ignore lines starting with '#' character
8517      */
8518     if(cps->debug && *message == '#') return;
8519
8520     /*
8521      * Look for book output
8522      */
8523     if (cps == &first && bookRequested) {
8524         if (message[0] == '\t' || message[0] == ' ') {
8525             /* Part of the book output is here; append it */
8526             strcat(bookOutput, message);
8527             strcat(bookOutput, "  \n");
8528             return;
8529         } else if (bookOutput[0] != NULLCHAR) {
8530             /* All of book output has arrived; display it */
8531             char *p = bookOutput;
8532             while (*p != NULLCHAR) {
8533                 if (*p == '\t') *p = ' ';
8534                 p++;
8535             }
8536             DisplayInformation(bookOutput);
8537             bookRequested = FALSE;
8538             /* Fall through to parse the current output */
8539         }
8540     }
8541
8542     /*
8543      * Look for machine move.
8544      */
8545     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8546         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8547     {
8548         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8549             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8550             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8551             stalledEngine = cps;
8552             if(appData.ponderNextMove) { // bring opponent out of ponder
8553                 if(gameMode == TwoMachinesPlay) {
8554                     if(cps->other->pause)
8555                         PauseEngine(cps->other);
8556                     else
8557                         SendToProgram("easy\n", cps->other);
8558                 }
8559             }
8560             StopClocks();
8561             return;
8562         }
8563
8564         /* This method is only useful on engines that support ping */
8565         if (cps->lastPing != cps->lastPong) {
8566           if (gameMode == BeginningOfGame) {
8567             /* Extra move from before last new; ignore */
8568             if (appData.debugMode) {
8569                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8570             }
8571           } else {
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8574                         cps->which, gameMode);
8575             }
8576
8577             SendToProgram("undo\n", cps);
8578           }
8579           return;
8580         }
8581
8582         switch (gameMode) {
8583           case BeginningOfGame:
8584             /* Extra move from before last reset; ignore */
8585             if (appData.debugMode) {
8586                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8587             }
8588             return;
8589
8590           case EndOfGame:
8591           case IcsIdle:
8592           default:
8593             /* Extra move after we tried to stop.  The mode test is
8594                not a reliable way of detecting this problem, but it's
8595                the best we can do on engines that don't support ping.
8596             */
8597             if (appData.debugMode) {
8598                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8599                         cps->which, gameMode);
8600             }
8601             SendToProgram("undo\n", cps);
8602             return;
8603
8604           case MachinePlaysWhite:
8605           case IcsPlayingWhite:
8606             machineWhite = TRUE;
8607             break;
8608
8609           case MachinePlaysBlack:
8610           case IcsPlayingBlack:
8611             machineWhite = FALSE;
8612             break;
8613
8614           case TwoMachinesPlay:
8615             machineWhite = (cps->twoMachinesColor[0] == 'w');
8616             break;
8617         }
8618         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8619             if (appData.debugMode) {
8620                 fprintf(debugFP,
8621                         "Ignoring move out of turn by %s, gameMode %d"
8622                         ", forwardMost %d\n",
8623                         cps->which, gameMode, forwardMostMove);
8624             }
8625             return;
8626         }
8627
8628         if(cps->alphaRank) AlphaRank(machineMove, 4);
8629
8630         // [HGM] lion: (some very limited) support for Alien protocol
8631         killX = killY = -1;
8632         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8633             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8634             return;
8635         } else if(firstLeg[0]) { // there was a previous leg;
8636             // only support case where same piece makes two step (and don't even test that!)
8637             char buf[20], *p = machineMove+1, *q = buf+1, f;
8638             safeStrCpy(buf, machineMove, 20);
8639             while(isdigit(*q)) q++; // find start of to-square
8640             safeStrCpy(machineMove, firstLeg, 20);
8641             while(isdigit(*p)) p++;
8642             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8643             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8644             firstLeg[0] = NULLCHAR;
8645         }
8646
8647         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8648                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8649             /* Machine move could not be parsed; ignore it. */
8650           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8651                     machineMove, _(cps->which));
8652             DisplayMoveError(buf1);
8653             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8654                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8655             if (gameMode == TwoMachinesPlay) {
8656               GameEnds(machineWhite ? BlackWins : WhiteWins,
8657                        buf1, GE_XBOARD);
8658             }
8659             return;
8660         }
8661
8662         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8663         /* So we have to redo legality test with true e.p. status here,  */
8664         /* to make sure an illegal e.p. capture does not slip through,   */
8665         /* to cause a forfeit on a justified illegal-move complaint      */
8666         /* of the opponent.                                              */
8667         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8668            ChessMove moveType;
8669            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8670                              fromY, fromX, toY, toX, promoChar);
8671             if(moveType == IllegalMove) {
8672               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8673                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8674                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8675                            buf1, GE_XBOARD);
8676                 return;
8677            } else if(!appData.fischerCastling)
8678            /* [HGM] Kludge to handle engines that send FRC-style castling
8679               when they shouldn't (like TSCP-Gothic) */
8680            switch(moveType) {
8681              case WhiteASideCastleFR:
8682              case BlackASideCastleFR:
8683                toX+=2;
8684                currentMoveString[2]++;
8685                break;
8686              case WhiteHSideCastleFR:
8687              case BlackHSideCastleFR:
8688                toX--;
8689                currentMoveString[2]--;
8690                break;
8691              default: ; // nothing to do, but suppresses warning of pedantic compilers
8692            }
8693         }
8694         hintRequested = FALSE;
8695         lastHint[0] = NULLCHAR;
8696         bookRequested = FALSE;
8697         /* Program may be pondering now */
8698         cps->maybeThinking = TRUE;
8699         if (cps->sendTime == 2) cps->sendTime = 1;
8700         if (cps->offeredDraw) cps->offeredDraw--;
8701
8702         /* [AS] Save move info*/
8703         pvInfoList[ forwardMostMove ].score = programStats.score;
8704         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8705         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8706
8707         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8708
8709         /* Test suites abort the 'game' after one move */
8710         if(*appData.finger) {
8711            static FILE *f;
8712            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8713            if(!f) f = fopen(appData.finger, "w");
8714            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8715            else { DisplayFatalError("Bad output file", errno, 0); return; }
8716            free(fen);
8717            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8718         }
8719
8720         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8721         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8722             int count = 0;
8723
8724             while( count < adjudicateLossPlies ) {
8725                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8726
8727                 if( count & 1 ) {
8728                     score = -score; /* Flip score for winning side */
8729                 }
8730 printf("score=%d count=%d\n",score,count);
8731                 if( score > appData.adjudicateLossThreshold ) {
8732                     break;
8733                 }
8734
8735                 count++;
8736             }
8737
8738             if( count >= adjudicateLossPlies ) {
8739                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8740
8741                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8742                     "Xboard adjudication",
8743                     GE_XBOARD );
8744
8745                 return;
8746             }
8747         }
8748
8749         if(Adjudicate(cps)) {
8750             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8751             return; // [HGM] adjudicate: for all automatic game ends
8752         }
8753
8754 #if ZIPPY
8755         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8756             first.initDone) {
8757           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8758                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8759                 SendToICS("draw ");
8760                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8761           }
8762           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8763           ics_user_moved = 1;
8764           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8765                 char buf[3*MSG_SIZ];
8766
8767                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8768                         programStats.score / 100.,
8769                         programStats.depth,
8770                         programStats.time / 100.,
8771                         (unsigned int)programStats.nodes,
8772                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8773                         programStats.movelist);
8774                 SendToICS(buf);
8775           }
8776         }
8777 #endif
8778
8779         /* [AS] Clear stats for next move */
8780         ClearProgramStats();
8781         thinkOutput[0] = NULLCHAR;
8782         hiddenThinkOutputState = 0;
8783
8784         bookHit = NULL;
8785         if (gameMode == TwoMachinesPlay) {
8786             /* [HGM] relaying draw offers moved to after reception of move */
8787             /* and interpreting offer as claim if it brings draw condition */
8788             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8789                 SendToProgram("draw\n", cps->other);
8790             }
8791             if (cps->other->sendTime) {
8792                 SendTimeRemaining(cps->other,
8793                                   cps->other->twoMachinesColor[0] == 'w');
8794             }
8795             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8796             if (firstMove && !bookHit) {
8797                 firstMove = FALSE;
8798                 if (cps->other->useColors) {
8799                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8800                 }
8801                 SendToProgram("go\n", cps->other);
8802             }
8803             cps->other->maybeThinking = TRUE;
8804         }
8805
8806         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8807
8808         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8809
8810         if (!pausing && appData.ringBellAfterMoves) {
8811             if(!roar) RingBell();
8812         }
8813
8814         /*
8815          * Reenable menu items that were disabled while
8816          * machine was thinking
8817          */
8818         if (gameMode != TwoMachinesPlay)
8819             SetUserThinkingEnables();
8820
8821         // [HGM] book: after book hit opponent has received move and is now in force mode
8822         // force the book reply into it, and then fake that it outputted this move by jumping
8823         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8824         if(bookHit) {
8825                 static char bookMove[MSG_SIZ]; // a bit generous?
8826
8827                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8828                 strcat(bookMove, bookHit);
8829                 message = bookMove;
8830                 cps = cps->other;
8831                 programStats.nodes = programStats.depth = programStats.time =
8832                 programStats.score = programStats.got_only_move = 0;
8833                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8834
8835                 if(cps->lastPing != cps->lastPong) {
8836                     savedMessage = message; // args for deferred call
8837                     savedState = cps;
8838                     ScheduleDelayedEvent(DeferredBookMove, 10);
8839                     return;
8840                 }
8841                 goto FakeBookMove;
8842         }
8843
8844         return;
8845     }
8846
8847     /* Set special modes for chess engines.  Later something general
8848      *  could be added here; for now there is just one kludge feature,
8849      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8850      *  when "xboard" is given as an interactive command.
8851      */
8852     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8853         cps->useSigint = FALSE;
8854         cps->useSigterm = FALSE;
8855     }
8856     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8857       ParseFeatures(message+8, cps);
8858       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8859     }
8860
8861     if (!strncmp(message, "setup ", 6) && 
8862         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8863           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8864                                         ) { // [HGM] allow first engine to define opening position
8865       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8866       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8867       *buf = NULLCHAR;
8868       if(sscanf(message, "setup (%s", buf) == 1) {
8869         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8870         ASSIGN(appData.pieceToCharTable, buf);
8871       }
8872       if(startedFromSetupPosition) return;
8873       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8874       if(dummy >= 3) {
8875         while(message[s] && message[s++] != ' ');
8876         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8877            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8878             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8879             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8880           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8881           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8882         }
8883       }
8884       ParseFEN(boards[0], &dummy, message+s, FALSE);
8885       DrawPosition(TRUE, boards[0]);
8886       startedFromSetupPosition = TRUE;
8887       return;
8888     }
8889     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8890      * want this, I was asked to put it in, and obliged.
8891      */
8892     if (!strncmp(message, "setboard ", 9)) {
8893         Board initial_position;
8894
8895         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8896
8897         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8898             DisplayError(_("Bad FEN received from engine"), 0);
8899             return ;
8900         } else {
8901            Reset(TRUE, FALSE);
8902            CopyBoard(boards[0], initial_position);
8903            initialRulePlies = FENrulePlies;
8904            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8905            else gameMode = MachinePlaysBlack;
8906            DrawPosition(FALSE, boards[currentMove]);
8907         }
8908         return;
8909     }
8910
8911     /*
8912      * Look for communication commands
8913      */
8914     if (!strncmp(message, "telluser ", 9)) {
8915         if(message[9] == '\\' && message[10] == '\\')
8916             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8917         PlayTellSound();
8918         DisplayNote(message + 9);
8919         return;
8920     }
8921     if (!strncmp(message, "tellusererror ", 14)) {
8922         cps->userError = 1;
8923         if(message[14] == '\\' && message[15] == '\\')
8924             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8925         PlayTellSound();
8926         DisplayError(message + 14, 0);
8927         return;
8928     }
8929     if (!strncmp(message, "tellopponent ", 13)) {
8930       if (appData.icsActive) {
8931         if (loggedOn) {
8932           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8933           SendToICS(buf1);
8934         }
8935       } else {
8936         DisplayNote(message + 13);
8937       }
8938       return;
8939     }
8940     if (!strncmp(message, "tellothers ", 11)) {
8941       if (appData.icsActive) {
8942         if (loggedOn) {
8943           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8944           SendToICS(buf1);
8945         }
8946       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8947       return;
8948     }
8949     if (!strncmp(message, "tellall ", 8)) {
8950       if (appData.icsActive) {
8951         if (loggedOn) {
8952           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8953           SendToICS(buf1);
8954         }
8955       } else {
8956         DisplayNote(message + 8);
8957       }
8958       return;
8959     }
8960     if (strncmp(message, "warning", 7) == 0) {
8961         /* Undocumented feature, use tellusererror in new code */
8962         DisplayError(message, 0);
8963         return;
8964     }
8965     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8966         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8967         strcat(realname, " query");
8968         AskQuestion(realname, buf2, buf1, cps->pr);
8969         return;
8970     }
8971     /* Commands from the engine directly to ICS.  We don't allow these to be
8972      *  sent until we are logged on. Crafty kibitzes have been known to
8973      *  interfere with the login process.
8974      */
8975     if (loggedOn) {
8976         if (!strncmp(message, "tellics ", 8)) {
8977             SendToICS(message + 8);
8978             SendToICS("\n");
8979             return;
8980         }
8981         if (!strncmp(message, "tellicsnoalias ", 15)) {
8982             SendToICS(ics_prefix);
8983             SendToICS(message + 15);
8984             SendToICS("\n");
8985             return;
8986         }
8987         /* The following are for backward compatibility only */
8988         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8989             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8990             SendToICS(ics_prefix);
8991             SendToICS(message);
8992             SendToICS("\n");
8993             return;
8994         }
8995     }
8996     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8997         if(initPing == cps->lastPong) {
8998             if(gameInfo.variant == VariantUnknown) {
8999                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9000                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9001                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9002             }
9003             initPing = -1;
9004         }
9005         return;
9006     }
9007     if(!strncmp(message, "highlight ", 10)) {
9008         if(appData.testLegality && appData.markers) return;
9009         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9010         return;
9011     }
9012     if(!strncmp(message, "click ", 6)) {
9013         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9014         if(appData.testLegality || !appData.oneClick) return;
9015         sscanf(message+6, "%c%d%c", &f, &y, &c);
9016         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9017         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9018         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9019         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9020         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9021         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9022             LeftClick(Release, lastLeftX, lastLeftY);
9023         controlKey  = (c == ',');
9024         LeftClick(Press, x, y);
9025         LeftClick(Release, x, y);
9026         first.highlight = f;
9027         return;
9028     }
9029     /*
9030      * If the move is illegal, cancel it and redraw the board.
9031      * Also deal with other error cases.  Matching is rather loose
9032      * here to accommodate engines written before the spec.
9033      */
9034     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9035         strncmp(message, "Error", 5) == 0) {
9036         if (StrStr(message, "name") ||
9037             StrStr(message, "rating") || StrStr(message, "?") ||
9038             StrStr(message, "result") || StrStr(message, "board") ||
9039             StrStr(message, "bk") || StrStr(message, "computer") ||
9040             StrStr(message, "variant") || StrStr(message, "hint") ||
9041             StrStr(message, "random") || StrStr(message, "depth") ||
9042             StrStr(message, "accepted")) {
9043             return;
9044         }
9045         if (StrStr(message, "protover")) {
9046           /* Program is responding to input, so it's apparently done
9047              initializing, and this error message indicates it is
9048              protocol version 1.  So we don't need to wait any longer
9049              for it to initialize and send feature commands. */
9050           FeatureDone(cps, 1);
9051           cps->protocolVersion = 1;
9052           return;
9053         }
9054         cps->maybeThinking = FALSE;
9055
9056         if (StrStr(message, "draw")) {
9057             /* Program doesn't have "draw" command */
9058             cps->sendDrawOffers = 0;
9059             return;
9060         }
9061         if (cps->sendTime != 1 &&
9062             (StrStr(message, "time") || StrStr(message, "otim"))) {
9063           /* Program apparently doesn't have "time" or "otim" command */
9064           cps->sendTime = 0;
9065           return;
9066         }
9067         if (StrStr(message, "analyze")) {
9068             cps->analysisSupport = FALSE;
9069             cps->analyzing = FALSE;
9070 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9071             EditGameEvent(); // [HGM] try to preserve loaded game
9072             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9073             DisplayError(buf2, 0);
9074             return;
9075         }
9076         if (StrStr(message, "(no matching move)st")) {
9077           /* Special kludge for GNU Chess 4 only */
9078           cps->stKludge = TRUE;
9079           SendTimeControl(cps, movesPerSession, timeControl,
9080                           timeIncrement, appData.searchDepth,
9081                           searchTime);
9082           return;
9083         }
9084         if (StrStr(message, "(no matching move)sd")) {
9085           /* Special kludge for GNU Chess 4 only */
9086           cps->sdKludge = TRUE;
9087           SendTimeControl(cps, movesPerSession, timeControl,
9088                           timeIncrement, appData.searchDepth,
9089                           searchTime);
9090           return;
9091         }
9092         if (!StrStr(message, "llegal")) {
9093             return;
9094         }
9095         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9096             gameMode == IcsIdle) return;
9097         if (forwardMostMove <= backwardMostMove) return;
9098         if (pausing) PauseEvent();
9099       if(appData.forceIllegal) {
9100             // [HGM] illegal: machine refused move; force position after move into it
9101           SendToProgram("force\n", cps);
9102           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9103                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9104                 // when black is to move, while there might be nothing on a2 or black
9105                 // might already have the move. So send the board as if white has the move.
9106                 // But first we must change the stm of the engine, as it refused the last move
9107                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9108                 if(WhiteOnMove(forwardMostMove)) {
9109                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9110                     SendBoard(cps, forwardMostMove); // kludgeless board
9111                 } else {
9112                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9113                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9114                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9115                 }
9116           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9117             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9118                  gameMode == TwoMachinesPlay)
9119               SendToProgram("go\n", cps);
9120             return;
9121       } else
9122         if (gameMode == PlayFromGameFile) {
9123             /* Stop reading this game file */
9124             gameMode = EditGame;
9125             ModeHighlight();
9126         }
9127         /* [HGM] illegal-move claim should forfeit game when Xboard */
9128         /* only passes fully legal moves                            */
9129         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9130             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9131                                 "False illegal-move claim", GE_XBOARD );
9132             return; // do not take back move we tested as valid
9133         }
9134         currentMove = forwardMostMove-1;
9135         DisplayMove(currentMove-1); /* before DisplayMoveError */
9136         SwitchClocks(forwardMostMove-1); // [HGM] race
9137         DisplayBothClocks();
9138         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9139                 parseList[currentMove], _(cps->which));
9140         DisplayMoveError(buf1);
9141         DrawPosition(FALSE, boards[currentMove]);
9142
9143         SetUserThinkingEnables();
9144         return;
9145     }
9146     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9147         /* Program has a broken "time" command that
9148            outputs a string not ending in newline.
9149            Don't use it. */
9150         cps->sendTime = 0;
9151     }
9152     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9153         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9154             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9155     }
9156
9157     /*
9158      * If chess program startup fails, exit with an error message.
9159      * Attempts to recover here are futile. [HGM] Well, we try anyway
9160      */
9161     if ((StrStr(message, "unknown host") != NULL)
9162         || (StrStr(message, "No remote directory") != NULL)
9163         || (StrStr(message, "not found") != NULL)
9164         || (StrStr(message, "No such file") != NULL)
9165         || (StrStr(message, "can't alloc") != NULL)
9166         || (StrStr(message, "Permission denied") != NULL)) {
9167
9168         cps->maybeThinking = FALSE;
9169         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9170                 _(cps->which), cps->program, cps->host, message);
9171         RemoveInputSource(cps->isr);
9172         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9173             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9174             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9175         }
9176         return;
9177     }
9178
9179     /*
9180      * Look for hint output
9181      */
9182     if (sscanf(message, "Hint: %s", buf1) == 1) {
9183         if (cps == &first && hintRequested) {
9184             hintRequested = FALSE;
9185             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9186                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9187                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9188                                     PosFlags(forwardMostMove),
9189                                     fromY, fromX, toY, toX, promoChar, buf1);
9190                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9191                 DisplayInformation(buf2);
9192             } else {
9193                 /* Hint move could not be parsed!? */
9194               snprintf(buf2, sizeof(buf2),
9195                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9196                         buf1, _(cps->which));
9197                 DisplayError(buf2, 0);
9198             }
9199         } else {
9200           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9201         }
9202         return;
9203     }
9204
9205     /*
9206      * Ignore other messages if game is not in progress
9207      */
9208     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9209         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9210
9211     /*
9212      * look for win, lose, draw, or draw offer
9213      */
9214     if (strncmp(message, "1-0", 3) == 0) {
9215         char *p, *q, *r = "";
9216         p = strchr(message, '{');
9217         if (p) {
9218             q = strchr(p, '}');
9219             if (q) {
9220                 *q = NULLCHAR;
9221                 r = p + 1;
9222             }
9223         }
9224         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9225         return;
9226     } else if (strncmp(message, "0-1", 3) == 0) {
9227         char *p, *q, *r = "";
9228         p = strchr(message, '{');
9229         if (p) {
9230             q = strchr(p, '}');
9231             if (q) {
9232                 *q = NULLCHAR;
9233                 r = p + 1;
9234             }
9235         }
9236         /* Kludge for Arasan 4.1 bug */
9237         if (strcmp(r, "Black resigns") == 0) {
9238             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9239             return;
9240         }
9241         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9242         return;
9243     } else if (strncmp(message, "1/2", 3) == 0) {
9244         char *p, *q, *r = "";
9245         p = strchr(message, '{');
9246         if (p) {
9247             q = strchr(p, '}');
9248             if (q) {
9249                 *q = NULLCHAR;
9250                 r = p + 1;
9251             }
9252         }
9253
9254         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9255         return;
9256
9257     } else if (strncmp(message, "White resign", 12) == 0) {
9258         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9259         return;
9260     } else if (strncmp(message, "Black resign", 12) == 0) {
9261         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262         return;
9263     } else if (strncmp(message, "White matches", 13) == 0 ||
9264                strncmp(message, "Black matches", 13) == 0   ) {
9265         /* [HGM] ignore GNUShogi noises */
9266         return;
9267     } else if (strncmp(message, "White", 5) == 0 &&
9268                message[5] != '(' &&
9269                StrStr(message, "Black") == NULL) {
9270         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9271         return;
9272     } else if (strncmp(message, "Black", 5) == 0 &&
9273                message[5] != '(') {
9274         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strcmp(message, "resign") == 0 ||
9277                strcmp(message, "computer resigns") == 0) {
9278         switch (gameMode) {
9279           case MachinePlaysBlack:
9280           case IcsPlayingBlack:
9281             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9282             break;
9283           case MachinePlaysWhite:
9284           case IcsPlayingWhite:
9285             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9286             break;
9287           case TwoMachinesPlay:
9288             if (cps->twoMachinesColor[0] == 'w')
9289               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9290             else
9291               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9292             break;
9293           default:
9294             /* can't happen */
9295             break;
9296         }
9297         return;
9298     } else if (strncmp(message, "opponent mates", 14) == 0) {
9299         switch (gameMode) {
9300           case MachinePlaysBlack:
9301           case IcsPlayingBlack:
9302             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9303             break;
9304           case MachinePlaysWhite:
9305           case IcsPlayingWhite:
9306             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9307             break;
9308           case TwoMachinesPlay:
9309             if (cps->twoMachinesColor[0] == 'w')
9310               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9311             else
9312               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9313             break;
9314           default:
9315             /* can't happen */
9316             break;
9317         }
9318         return;
9319     } else if (strncmp(message, "computer mates", 14) == 0) {
9320         switch (gameMode) {
9321           case MachinePlaysBlack:
9322           case IcsPlayingBlack:
9323             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9324             break;
9325           case MachinePlaysWhite:
9326           case IcsPlayingWhite:
9327             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9328             break;
9329           case TwoMachinesPlay:
9330             if (cps->twoMachinesColor[0] == 'w')
9331               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9332             else
9333               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9334             break;
9335           default:
9336             /* can't happen */
9337             break;
9338         }
9339         return;
9340     } else if (strncmp(message, "checkmate", 9) == 0) {
9341         if (WhiteOnMove(forwardMostMove)) {
9342             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9343         } else {
9344             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9345         }
9346         return;
9347     } else if (strstr(message, "Draw") != NULL ||
9348                strstr(message, "game is a draw") != NULL) {
9349         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9350         return;
9351     } else if (strstr(message, "offer") != NULL &&
9352                strstr(message, "draw") != NULL) {
9353 #if ZIPPY
9354         if (appData.zippyPlay && first.initDone) {
9355             /* Relay offer to ICS */
9356             SendToICS(ics_prefix);
9357             SendToICS("draw\n");
9358         }
9359 #endif
9360         cps->offeredDraw = 2; /* valid until this engine moves twice */
9361         if (gameMode == TwoMachinesPlay) {
9362             if (cps->other->offeredDraw) {
9363                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9364             /* [HGM] in two-machine mode we delay relaying draw offer      */
9365             /* until after we also have move, to see if it is really claim */
9366             }
9367         } else if (gameMode == MachinePlaysWhite ||
9368                    gameMode == MachinePlaysBlack) {
9369           if (userOfferedDraw) {
9370             DisplayInformation(_("Machine accepts your draw offer"));
9371             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9372           } else {
9373             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9374           }
9375         }
9376     }
9377
9378
9379     /*
9380      * Look for thinking output
9381      */
9382     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9383           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9384                                 ) {
9385         int plylev, mvleft, mvtot, curscore, time;
9386         char mvname[MOVE_LEN];
9387         u64 nodes; // [DM]
9388         char plyext;
9389         int ignore = FALSE;
9390         int prefixHint = FALSE;
9391         mvname[0] = NULLCHAR;
9392
9393         switch (gameMode) {
9394           case MachinePlaysBlack:
9395           case IcsPlayingBlack:
9396             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9397             break;
9398           case MachinePlaysWhite:
9399           case IcsPlayingWhite:
9400             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9401             break;
9402           case AnalyzeMode:
9403           case AnalyzeFile:
9404             break;
9405           case IcsObserving: /* [DM] icsEngineAnalyze */
9406             if (!appData.icsEngineAnalyze) ignore = TRUE;
9407             break;
9408           case TwoMachinesPlay:
9409             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9410                 ignore = TRUE;
9411             }
9412             break;
9413           default:
9414             ignore = TRUE;
9415             break;
9416         }
9417
9418         if (!ignore) {
9419             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9420             buf1[0] = NULLCHAR;
9421             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9422                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9423
9424                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9425                     nodes += u64Const(0x100000000);
9426
9427                 if (plyext != ' ' && plyext != '\t') {
9428                     time *= 100;
9429                 }
9430
9431                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9432                 if( cps->scoreIsAbsolute &&
9433                     ( gameMode == MachinePlaysBlack ||
9434                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9435                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9436                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9437                      !WhiteOnMove(currentMove)
9438                     ) )
9439                 {
9440                     curscore = -curscore;
9441                 }
9442
9443                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9444
9445                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9446                         char buf[MSG_SIZ];
9447                         FILE *f;
9448                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9449                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9450                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9451                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9452                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9453                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9454                                 fclose(f);
9455                         }
9456                         else
9457                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9458                           DisplayError(_("failed writing PV"), 0);
9459                 }
9460
9461                 tempStats.depth = plylev;
9462                 tempStats.nodes = nodes;
9463                 tempStats.time = time;
9464                 tempStats.score = curscore;
9465                 tempStats.got_only_move = 0;
9466
9467                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9468                         int ticklen;
9469
9470                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9471                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9472                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9473                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9474                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9475                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9476                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9477                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9478                 }
9479
9480                 /* Buffer overflow protection */
9481                 if (pv[0] != NULLCHAR) {
9482                     if (strlen(pv) >= sizeof(tempStats.movelist)
9483                         && appData.debugMode) {
9484                         fprintf(debugFP,
9485                                 "PV is too long; using the first %u bytes.\n",
9486                                 (unsigned) sizeof(tempStats.movelist) - 1);
9487                     }
9488
9489                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9490                 } else {
9491                     sprintf(tempStats.movelist, " no PV\n");
9492                 }
9493
9494                 if (tempStats.seen_stat) {
9495                     tempStats.ok_to_send = 1;
9496                 }
9497
9498                 if (strchr(tempStats.movelist, '(') != NULL) {
9499                     tempStats.line_is_book = 1;
9500                     tempStats.nr_moves = 0;
9501                     tempStats.moves_left = 0;
9502                 } else {
9503                     tempStats.line_is_book = 0;
9504                 }
9505
9506                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9507                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9508
9509                 SendProgramStatsToFrontend( cps, &tempStats );
9510
9511                 /*
9512                     [AS] Protect the thinkOutput buffer from overflow... this
9513                     is only useful if buf1 hasn't overflowed first!
9514                 */
9515                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9516                          plylev,
9517                          (gameMode == TwoMachinesPlay ?
9518                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9519                          ((double) curscore) / 100.0,
9520                          prefixHint ? lastHint : "",
9521                          prefixHint ? " " : "" );
9522
9523                 if( buf1[0] != NULLCHAR ) {
9524                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9525
9526                     if( strlen(pv) > max_len ) {
9527                         if( appData.debugMode) {
9528                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9529                         }
9530                         pv[max_len+1] = '\0';
9531                     }
9532
9533                     strcat( thinkOutput, pv);
9534                 }
9535
9536                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9537                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9538                     DisplayMove(currentMove - 1);
9539                 }
9540                 return;
9541
9542             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9543                 /* crafty (9.25+) says "(only move) <move>"
9544                  * if there is only 1 legal move
9545                  */
9546                 sscanf(p, "(only move) %s", buf1);
9547                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9548                 sprintf(programStats.movelist, "%s (only move)", buf1);
9549                 programStats.depth = 1;
9550                 programStats.nr_moves = 1;
9551                 programStats.moves_left = 1;
9552                 programStats.nodes = 1;
9553                 programStats.time = 1;
9554                 programStats.got_only_move = 1;
9555
9556                 /* Not really, but we also use this member to
9557                    mean "line isn't going to change" (Crafty
9558                    isn't searching, so stats won't change) */
9559                 programStats.line_is_book = 1;
9560
9561                 SendProgramStatsToFrontend( cps, &programStats );
9562
9563                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9564                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9565                     DisplayMove(currentMove - 1);
9566                 }
9567                 return;
9568             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9569                               &time, &nodes, &plylev, &mvleft,
9570                               &mvtot, mvname) >= 5) {
9571                 /* The stat01: line is from Crafty (9.29+) in response
9572                    to the "." command */
9573                 programStats.seen_stat = 1;
9574                 cps->maybeThinking = TRUE;
9575
9576                 if (programStats.got_only_move || !appData.periodicUpdates)
9577                   return;
9578
9579                 programStats.depth = plylev;
9580                 programStats.time = time;
9581                 programStats.nodes = nodes;
9582                 programStats.moves_left = mvleft;
9583                 programStats.nr_moves = mvtot;
9584                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9585                 programStats.ok_to_send = 1;
9586                 programStats.movelist[0] = '\0';
9587
9588                 SendProgramStatsToFrontend( cps, &programStats );
9589
9590                 return;
9591
9592             } else if (strncmp(message,"++",2) == 0) {
9593                 /* Crafty 9.29+ outputs this */
9594                 programStats.got_fail = 2;
9595                 return;
9596
9597             } else if (strncmp(message,"--",2) == 0) {
9598                 /* Crafty 9.29+ outputs this */
9599                 programStats.got_fail = 1;
9600                 return;
9601
9602             } else if (thinkOutput[0] != NULLCHAR &&
9603                        strncmp(message, "    ", 4) == 0) {
9604                 unsigned message_len;
9605
9606                 p = message;
9607                 while (*p && *p == ' ') p++;
9608
9609                 message_len = strlen( p );
9610
9611                 /* [AS] Avoid buffer overflow */
9612                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9613                     strcat(thinkOutput, " ");
9614                     strcat(thinkOutput, p);
9615                 }
9616
9617                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9618                     strcat(programStats.movelist, " ");
9619                     strcat(programStats.movelist, p);
9620                 }
9621
9622                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9623                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9624                     DisplayMove(currentMove - 1);
9625                 }
9626                 return;
9627             }
9628         }
9629         else {
9630             buf1[0] = NULLCHAR;
9631
9632             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9633                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9634             {
9635                 ChessProgramStats cpstats;
9636
9637                 if (plyext != ' ' && plyext != '\t') {
9638                     time *= 100;
9639                 }
9640
9641                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9642                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9643                     curscore = -curscore;
9644                 }
9645
9646                 cpstats.depth = plylev;
9647                 cpstats.nodes = nodes;
9648                 cpstats.time = time;
9649                 cpstats.score = curscore;
9650                 cpstats.got_only_move = 0;
9651                 cpstats.movelist[0] = '\0';
9652
9653                 if (buf1[0] != NULLCHAR) {
9654                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9655                 }
9656
9657                 cpstats.ok_to_send = 0;
9658                 cpstats.line_is_book = 0;
9659                 cpstats.nr_moves = 0;
9660                 cpstats.moves_left = 0;
9661
9662                 SendProgramStatsToFrontend( cps, &cpstats );
9663             }
9664         }
9665     }
9666 }
9667
9668
9669 /* Parse a game score from the character string "game", and
9670    record it as the history of the current game.  The game
9671    score is NOT assumed to start from the standard position.
9672    The display is not updated in any way.
9673    */
9674 void
9675 ParseGameHistory (char *game)
9676 {
9677     ChessMove moveType;
9678     int fromX, fromY, toX, toY, boardIndex;
9679     char promoChar;
9680     char *p, *q;
9681     char buf[MSG_SIZ];
9682
9683     if (appData.debugMode)
9684       fprintf(debugFP, "Parsing game history: %s\n", game);
9685
9686     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9687     gameInfo.site = StrSave(appData.icsHost);
9688     gameInfo.date = PGNDate();
9689     gameInfo.round = StrSave("-");
9690
9691     /* Parse out names of players */
9692     while (*game == ' ') game++;
9693     p = buf;
9694     while (*game != ' ') *p++ = *game++;
9695     *p = NULLCHAR;
9696     gameInfo.white = StrSave(buf);
9697     while (*game == ' ') game++;
9698     p = buf;
9699     while (*game != ' ' && *game != '\n') *p++ = *game++;
9700     *p = NULLCHAR;
9701     gameInfo.black = StrSave(buf);
9702
9703     /* Parse moves */
9704     boardIndex = blackPlaysFirst ? 1 : 0;
9705     yynewstr(game);
9706     for (;;) {
9707         yyboardindex = boardIndex;
9708         moveType = (ChessMove) Myylex();
9709         switch (moveType) {
9710           case IllegalMove:             /* maybe suicide chess, etc. */
9711   if (appData.debugMode) {
9712     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9713     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9714     setbuf(debugFP, NULL);
9715   }
9716           case WhitePromotion:
9717           case BlackPromotion:
9718           case WhiteNonPromotion:
9719           case BlackNonPromotion:
9720           case NormalMove:
9721           case FirstLeg:
9722           case WhiteCapturesEnPassant:
9723           case BlackCapturesEnPassant:
9724           case WhiteKingSideCastle:
9725           case WhiteQueenSideCastle:
9726           case BlackKingSideCastle:
9727           case BlackQueenSideCastle:
9728           case WhiteKingSideCastleWild:
9729           case WhiteQueenSideCastleWild:
9730           case BlackKingSideCastleWild:
9731           case BlackQueenSideCastleWild:
9732           /* PUSH Fabien */
9733           case WhiteHSideCastleFR:
9734           case WhiteASideCastleFR:
9735           case BlackHSideCastleFR:
9736           case BlackASideCastleFR:
9737           /* POP Fabien */
9738             fromX = currentMoveString[0] - AAA;
9739             fromY = currentMoveString[1] - ONE;
9740             toX = currentMoveString[2] - AAA;
9741             toY = currentMoveString[3] - ONE;
9742             promoChar = currentMoveString[4];
9743             break;
9744           case WhiteDrop:
9745           case BlackDrop:
9746             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9747             fromX = moveType == WhiteDrop ?
9748               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9749             (int) CharToPiece(ToLower(currentMoveString[0]));
9750             fromY = DROP_RANK;
9751             toX = currentMoveString[2] - AAA;
9752             toY = currentMoveString[3] - ONE;
9753             promoChar = NULLCHAR;
9754             break;
9755           case AmbiguousMove:
9756             /* bug? */
9757             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9758   if (appData.debugMode) {
9759     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9761     setbuf(debugFP, NULL);
9762   }
9763             DisplayError(buf, 0);
9764             return;
9765           case ImpossibleMove:
9766             /* bug? */
9767             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9768   if (appData.debugMode) {
9769     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9770     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9771     setbuf(debugFP, NULL);
9772   }
9773             DisplayError(buf, 0);
9774             return;
9775           case EndOfFile:
9776             if (boardIndex < backwardMostMove) {
9777                 /* Oops, gap.  How did that happen? */
9778                 DisplayError(_("Gap in move list"), 0);
9779                 return;
9780             }
9781             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9782             if (boardIndex > forwardMostMove) {
9783                 forwardMostMove = boardIndex;
9784             }
9785             return;
9786           case ElapsedTime:
9787             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9788                 strcat(parseList[boardIndex-1], " ");
9789                 strcat(parseList[boardIndex-1], yy_text);
9790             }
9791             continue;
9792           case Comment:
9793           case PGNTag:
9794           case NAG:
9795           default:
9796             /* ignore */
9797             continue;
9798           case WhiteWins:
9799           case BlackWins:
9800           case GameIsDrawn:
9801           case GameUnfinished:
9802             if (gameMode == IcsExamining) {
9803                 if (boardIndex < backwardMostMove) {
9804                     /* Oops, gap.  How did that happen? */
9805                     return;
9806                 }
9807                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9808                 return;
9809             }
9810             gameInfo.result = moveType;
9811             p = strchr(yy_text, '{');
9812             if (p == NULL) p = strchr(yy_text, '(');
9813             if (p == NULL) {
9814                 p = yy_text;
9815                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9816             } else {
9817                 q = strchr(p, *p == '{' ? '}' : ')');
9818                 if (q != NULL) *q = NULLCHAR;
9819                 p++;
9820             }
9821             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9822             gameInfo.resultDetails = StrSave(p);
9823             continue;
9824         }
9825         if (boardIndex >= forwardMostMove &&
9826             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9827             backwardMostMove = blackPlaysFirst ? 1 : 0;
9828             return;
9829         }
9830         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9831                                  fromY, fromX, toY, toX, promoChar,
9832                                  parseList[boardIndex]);
9833         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9834         /* currentMoveString is set as a side-effect of yylex */
9835         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9836         strcat(moveList[boardIndex], "\n");
9837         boardIndex++;
9838         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9839         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9840           case MT_NONE:
9841           case MT_STALEMATE:
9842           default:
9843             break;
9844           case MT_CHECK:
9845             if(!IS_SHOGI(gameInfo.variant))
9846                 strcat(parseList[boardIndex - 1], "+");
9847             break;
9848           case MT_CHECKMATE:
9849           case MT_STAINMATE:
9850             strcat(parseList[boardIndex - 1], "#");
9851             break;
9852         }
9853     }
9854 }
9855
9856
9857 /* Apply a move to the given board  */
9858 void
9859 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9860 {
9861   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9862   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9863
9864     /* [HGM] compute & store e.p. status and castling rights for new position */
9865     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9866
9867       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9868       oldEP = (signed char)board[EP_STATUS];
9869       board[EP_STATUS] = EP_NONE;
9870
9871   if (fromY == DROP_RANK) {
9872         /* must be first */
9873         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9874             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9875             return;
9876         }
9877         piece = board[toY][toX] = (ChessSquare) fromX;
9878   } else {
9879 //      ChessSquare victim;
9880       int i;
9881
9882       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9883 //           victim = board[killY][killX],
9884            board[killY][killX] = EmptySquare,
9885            board[EP_STATUS] = EP_CAPTURE;
9886
9887       if( board[toY][toX] != EmptySquare ) {
9888            board[EP_STATUS] = EP_CAPTURE;
9889            if( (fromX != toX || fromY != toY) && // not igui!
9890                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9891                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9892                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9893            }
9894       }
9895
9896       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9897            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9898                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9899       } else
9900       if( board[fromY][fromX] == WhitePawn ) {
9901            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9902                board[EP_STATUS] = EP_PAWN_MOVE;
9903            if( toY-fromY==2) {
9904                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9905                         gameInfo.variant != VariantBerolina || toX < fromX)
9906                       board[EP_STATUS] = toX | berolina;
9907                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9908                         gameInfo.variant != VariantBerolina || toX > fromX)
9909                       board[EP_STATUS] = toX;
9910            }
9911       } else
9912       if( board[fromY][fromX] == BlackPawn ) {
9913            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9914                board[EP_STATUS] = EP_PAWN_MOVE;
9915            if( toY-fromY== -2) {
9916                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9917                         gameInfo.variant != VariantBerolina || toX < fromX)
9918                       board[EP_STATUS] = toX | berolina;
9919                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9920                         gameInfo.variant != VariantBerolina || toX > fromX)
9921                       board[EP_STATUS] = toX;
9922            }
9923        }
9924
9925        for(i=0; i<nrCastlingRights; i++) {
9926            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9927               board[CASTLING][i] == toX   && castlingRank[i] == toY
9928              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9929        }
9930
9931        if(gameInfo.variant == VariantSChess) { // update virginity
9932            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9933            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9934            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9935            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9936        }
9937
9938      if (fromX == toX && fromY == toY) return;
9939
9940      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9941      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9942      if(gameInfo.variant == VariantKnightmate)
9943          king += (int) WhiteUnicorn - (int) WhiteKing;
9944
9945     /* Code added by Tord: */
9946     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9947     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9948         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9949       board[fromY][fromX] = EmptySquare;
9950       board[toY][toX] = EmptySquare;
9951       if((toX > fromX) != (piece == WhiteRook)) {
9952         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9953       } else {
9954         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9955       }
9956     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9957                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9958       board[fromY][fromX] = EmptySquare;
9959       board[toY][toX] = EmptySquare;
9960       if((toX > fromX) != (piece == BlackRook)) {
9961         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9962       } else {
9963         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9964       }
9965     /* End of code added by Tord */
9966
9967     } else if (board[fromY][fromX] == king
9968         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9969         && toY == fromY && toX > fromX+1) {
9970         board[fromY][fromX] = EmptySquare;
9971         board[toY][toX] = king;
9972         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9973         board[fromY][BOARD_RGHT-1] = EmptySquare;
9974     } else if (board[fromY][fromX] == king
9975         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9976                && toY == fromY && toX < fromX-1) {
9977         board[fromY][fromX] = EmptySquare;
9978         board[toY][toX] = king;
9979         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9980         board[fromY][BOARD_LEFT] = EmptySquare;
9981     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9982                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9983                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9984                ) {
9985         /* white pawn promotion */
9986         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9987         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9988             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9989         board[fromY][fromX] = EmptySquare;
9990     } else if ((fromY >= BOARD_HEIGHT>>1)
9991                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9992                && (toX != fromX)
9993                && gameInfo.variant != VariantXiangqi
9994                && gameInfo.variant != VariantBerolina
9995                && (board[fromY][fromX] == WhitePawn)
9996                && (board[toY][toX] == EmptySquare)) {
9997         board[fromY][fromX] = EmptySquare;
9998         board[toY][toX] = WhitePawn;
9999         captured = board[toY - 1][toX];
10000         board[toY - 1][toX] = EmptySquare;
10001     } else if ((fromY == BOARD_HEIGHT-4)
10002                && (toX == fromX)
10003                && gameInfo.variant == VariantBerolina
10004                && (board[fromY][fromX] == WhitePawn)
10005                && (board[toY][toX] == EmptySquare)) {
10006         board[fromY][fromX] = EmptySquare;
10007         board[toY][toX] = WhitePawn;
10008         if(oldEP & EP_BEROLIN_A) {
10009                 captured = board[fromY][fromX-1];
10010                 board[fromY][fromX-1] = EmptySquare;
10011         }else{  captured = board[fromY][fromX+1];
10012                 board[fromY][fromX+1] = EmptySquare;
10013         }
10014     } else if (board[fromY][fromX] == king
10015         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10016                && toY == fromY && toX > fromX+1) {
10017         board[fromY][fromX] = EmptySquare;
10018         board[toY][toX] = king;
10019         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10020         board[fromY][BOARD_RGHT-1] = EmptySquare;
10021     } else if (board[fromY][fromX] == king
10022         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10023                && toY == fromY && toX < fromX-1) {
10024         board[fromY][fromX] = EmptySquare;
10025         board[toY][toX] = king;
10026         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10027         board[fromY][BOARD_LEFT] = EmptySquare;
10028     } else if (fromY == 7 && fromX == 3
10029                && board[fromY][fromX] == BlackKing
10030                && toY == 7 && toX == 5) {
10031         board[fromY][fromX] = EmptySquare;
10032         board[toY][toX] = BlackKing;
10033         board[fromY][7] = EmptySquare;
10034         board[toY][4] = BlackRook;
10035     } else if (fromY == 7 && fromX == 3
10036                && board[fromY][fromX] == BlackKing
10037                && toY == 7 && toX == 1) {
10038         board[fromY][fromX] = EmptySquare;
10039         board[toY][toX] = BlackKing;
10040         board[fromY][0] = EmptySquare;
10041         board[toY][2] = BlackRook;
10042     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10043                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10044                && toY < promoRank && promoChar
10045                ) {
10046         /* black pawn promotion */
10047         board[toY][toX] = CharToPiece(ToLower(promoChar));
10048         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10049             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10050         board[fromY][fromX] = EmptySquare;
10051     } else if ((fromY < BOARD_HEIGHT>>1)
10052                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10053                && (toX != fromX)
10054                && gameInfo.variant != VariantXiangqi
10055                && gameInfo.variant != VariantBerolina
10056                && (board[fromY][fromX] == BlackPawn)
10057                && (board[toY][toX] == EmptySquare)) {
10058         board[fromY][fromX] = EmptySquare;
10059         board[toY][toX] = BlackPawn;
10060         captured = board[toY + 1][toX];
10061         board[toY + 1][toX] = EmptySquare;
10062     } else if ((fromY == 3)
10063                && (toX == fromX)
10064                && gameInfo.variant == VariantBerolina
10065                && (board[fromY][fromX] == BlackPawn)
10066                && (board[toY][toX] == EmptySquare)) {
10067         board[fromY][fromX] = EmptySquare;
10068         board[toY][toX] = BlackPawn;
10069         if(oldEP & EP_BEROLIN_A) {
10070                 captured = board[fromY][fromX-1];
10071                 board[fromY][fromX-1] = EmptySquare;
10072         }else{  captured = board[fromY][fromX+1];
10073                 board[fromY][fromX+1] = EmptySquare;
10074         }
10075     } else {
10076         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10077         board[fromY][fromX] = EmptySquare;
10078         board[toY][toX] = piece;
10079     }
10080   }
10081
10082     if (gameInfo.holdingsWidth != 0) {
10083
10084       /* !!A lot more code needs to be written to support holdings  */
10085       /* [HGM] OK, so I have written it. Holdings are stored in the */
10086       /* penultimate board files, so they are automaticlly stored   */
10087       /* in the game history.                                       */
10088       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10089                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10090         /* Delete from holdings, by decreasing count */
10091         /* and erasing image if necessary            */
10092         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10093         if(p < (int) BlackPawn) { /* white drop */
10094              p -= (int)WhitePawn;
10095                  p = PieceToNumber((ChessSquare)p);
10096              if(p >= gameInfo.holdingsSize) p = 0;
10097              if(--board[p][BOARD_WIDTH-2] <= 0)
10098                   board[p][BOARD_WIDTH-1] = EmptySquare;
10099              if((int)board[p][BOARD_WIDTH-2] < 0)
10100                         board[p][BOARD_WIDTH-2] = 0;
10101         } else {                  /* black drop */
10102              p -= (int)BlackPawn;
10103                  p = PieceToNumber((ChessSquare)p);
10104              if(p >= gameInfo.holdingsSize) p = 0;
10105              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10106                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10107              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10108                         board[BOARD_HEIGHT-1-p][1] = 0;
10109         }
10110       }
10111       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10112           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10113         /* [HGM] holdings: Add to holdings, if holdings exist */
10114         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10115                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10116                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10117         }
10118         p = (int) captured;
10119         if (p >= (int) BlackPawn) {
10120           p -= (int)BlackPawn;
10121           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10122                   /* in Shogi restore piece to its original  first */
10123                   captured = (ChessSquare) (DEMOTED captured);
10124                   p = DEMOTED p;
10125           }
10126           p = PieceToNumber((ChessSquare)p);
10127           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10128           board[p][BOARD_WIDTH-2]++;
10129           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10130         } else {
10131           p -= (int)WhitePawn;
10132           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10133                   captured = (ChessSquare) (DEMOTED captured);
10134                   p = DEMOTED p;
10135           }
10136           p = PieceToNumber((ChessSquare)p);
10137           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10138           board[BOARD_HEIGHT-1-p][1]++;
10139           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10140         }
10141       }
10142     } else if (gameInfo.variant == VariantAtomic) {
10143       if (captured != EmptySquare) {
10144         int y, x;
10145         for (y = toY-1; y <= toY+1; y++) {
10146           for (x = toX-1; x <= toX+1; x++) {
10147             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10148                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10149               board[y][x] = EmptySquare;
10150             }
10151           }
10152         }
10153         board[toY][toX] = EmptySquare;
10154       }
10155     }
10156
10157     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10158         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10159     } else
10160     if(promoChar == '+') {
10161         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10162         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10163         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10164           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10165     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10166         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10167         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10168            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10169         board[toY][toX] = newPiece;
10170     }
10171     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10172                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10173         // [HGM] superchess: take promotion piece out of holdings
10174         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10175         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10176             if(!--board[k][BOARD_WIDTH-2])
10177                 board[k][BOARD_WIDTH-1] = EmptySquare;
10178         } else {
10179             if(!--board[BOARD_HEIGHT-1-k][1])
10180                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10181         }
10182     }
10183 }
10184
10185 /* Updates forwardMostMove */
10186 void
10187 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10188 {
10189     int x = toX, y = toY;
10190     char *s = parseList[forwardMostMove];
10191     ChessSquare p = boards[forwardMostMove][toY][toX];
10192 //    forwardMostMove++; // [HGM] bare: moved downstream
10193
10194     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10195     (void) CoordsToAlgebraic(boards[forwardMostMove],
10196                              PosFlags(forwardMostMove),
10197                              fromY, fromX, y, x, promoChar,
10198                              s);
10199     if(killX >= 0 && killY >= 0)
10200         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10201
10202     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10203         int timeLeft; static int lastLoadFlag=0; int king, piece;
10204         piece = boards[forwardMostMove][fromY][fromX];
10205         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10206         if(gameInfo.variant == VariantKnightmate)
10207             king += (int) WhiteUnicorn - (int) WhiteKing;
10208         if(forwardMostMove == 0) {
10209             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10210                 fprintf(serverMoves, "%s;", UserName());
10211             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10212                 fprintf(serverMoves, "%s;", second.tidy);
10213             fprintf(serverMoves, "%s;", first.tidy);
10214             if(gameMode == MachinePlaysWhite)
10215                 fprintf(serverMoves, "%s;", UserName());
10216             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10217                 fprintf(serverMoves, "%s;", second.tidy);
10218         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10219         lastLoadFlag = loadFlag;
10220         // print base move
10221         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10222         // print castling suffix
10223         if( toY == fromY && piece == king ) {
10224             if(toX-fromX > 1)
10225                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10226             if(fromX-toX >1)
10227                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10228         }
10229         // e.p. suffix
10230         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10231              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10232              boards[forwardMostMove][toY][toX] == EmptySquare
10233              && fromX != toX && fromY != toY)
10234                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10235         // promotion suffix
10236         if(promoChar != NULLCHAR) {
10237             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10238                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10239                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10240             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10241         }
10242         if(!loadFlag) {
10243                 char buf[MOVE_LEN*2], *p; int len;
10244             fprintf(serverMoves, "/%d/%d",
10245                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10246             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10247             else                      timeLeft = blackTimeRemaining/1000;
10248             fprintf(serverMoves, "/%d", timeLeft);
10249                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10250                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10251                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10252                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10253             fprintf(serverMoves, "/%s", buf);
10254         }
10255         fflush(serverMoves);
10256     }
10257
10258     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10259         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10260       return;
10261     }
10262     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10263     if (commentList[forwardMostMove+1] != NULL) {
10264         free(commentList[forwardMostMove+1]);
10265         commentList[forwardMostMove+1] = NULL;
10266     }
10267     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10268     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10269     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10270     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10271     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10272     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10273     adjustedClock = FALSE;
10274     gameInfo.result = GameUnfinished;
10275     if (gameInfo.resultDetails != NULL) {
10276         free(gameInfo.resultDetails);
10277         gameInfo.resultDetails = NULL;
10278     }
10279     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10280                               moveList[forwardMostMove - 1]);
10281     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10282       case MT_NONE:
10283       case MT_STALEMATE:
10284       default:
10285         break;
10286       case MT_CHECK:
10287         if(!IS_SHOGI(gameInfo.variant))
10288             strcat(parseList[forwardMostMove - 1], "+");
10289         break;
10290       case MT_CHECKMATE:
10291       case MT_STAINMATE:
10292         strcat(parseList[forwardMostMove - 1], "#");
10293         break;
10294     }
10295 }
10296
10297 /* Updates currentMove if not pausing */
10298 void
10299 ShowMove (int fromX, int fromY, int toX, int toY)
10300 {
10301     int instant = (gameMode == PlayFromGameFile) ?
10302         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10303     if(appData.noGUI) return;
10304     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10305         if (!instant) {
10306             if (forwardMostMove == currentMove + 1) {
10307                 AnimateMove(boards[forwardMostMove - 1],
10308                             fromX, fromY, toX, toY);
10309             }
10310         }
10311         currentMove = forwardMostMove;
10312     }
10313
10314     killX = killY = -1; // [HGM] lion: used up
10315
10316     if (instant) return;
10317
10318     DisplayMove(currentMove - 1);
10319     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10320             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10321                 SetHighlights(fromX, fromY, toX, toY);
10322             }
10323     }
10324     DrawPosition(FALSE, boards[currentMove]);
10325     DisplayBothClocks();
10326     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10327 }
10328
10329 void
10330 SendEgtPath (ChessProgramState *cps)
10331 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10332         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10333
10334         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10335
10336         while(*p) {
10337             char c, *q = name+1, *r, *s;
10338
10339             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10340             while(*p && *p != ',') *q++ = *p++;
10341             *q++ = ':'; *q = 0;
10342             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10343                 strcmp(name, ",nalimov:") == 0 ) {
10344                 // take nalimov path from the menu-changeable option first, if it is defined
10345               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10346                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10347             } else
10348             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10349                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10350                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10351                 s = r = StrStr(s, ":") + 1; // beginning of path info
10352                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10353                 c = *r; *r = 0;             // temporarily null-terminate path info
10354                     *--q = 0;               // strip of trailig ':' from name
10355                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10356                 *r = c;
10357                 SendToProgram(buf,cps);     // send egtbpath command for this format
10358             }
10359             if(*p == ',') p++; // read away comma to position for next format name
10360         }
10361 }
10362
10363 static int
10364 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10365 {
10366       int width = 8, height = 8, holdings = 0;             // most common sizes
10367       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10368       // correct the deviations default for each variant
10369       if( v == VariantXiangqi ) width = 9,  height = 10;
10370       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10371       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10372       if( v == VariantCapablanca || v == VariantCapaRandom ||
10373           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10374                                 width = 10;
10375       if( v == VariantCourier ) width = 12;
10376       if( v == VariantSuper )                            holdings = 8;
10377       if( v == VariantGreat )   width = 10,              holdings = 8;
10378       if( v == VariantSChess )                           holdings = 7;
10379       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10380       if( v == VariantChuChess) width = 10, height = 10;
10381       if( v == VariantChu )     width = 12, height = 12;
10382       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10383              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10384              holdingsSize >= 0 && holdingsSize != holdings;
10385 }
10386
10387 char variantError[MSG_SIZ];
10388
10389 char *
10390 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10391 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10392       char *p, *variant = VariantName(v);
10393       static char b[MSG_SIZ];
10394       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10395            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10396                                                holdingsSize, variant); // cook up sized variant name
10397            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10398            if(StrStr(list, b) == NULL) {
10399                // specific sized variant not known, check if general sizing allowed
10400                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10401                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10402                             boardWidth, boardHeight, holdingsSize, engine);
10403                    return NULL;
10404                }
10405                /* [HGM] here we really should compare with the maximum supported board size */
10406            }
10407       } else snprintf(b, MSG_SIZ,"%s", variant);
10408       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10409       p = StrStr(list, b);
10410       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10411       if(p == NULL) {
10412           // occurs not at all in list, or only as sub-string
10413           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10414           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10415               int l = strlen(variantError);
10416               char *q;
10417               while(p != list && p[-1] != ',') p--;
10418               q = strchr(p, ',');
10419               if(q) *q = NULLCHAR;
10420               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10421               if(q) *q= ',';
10422           }
10423           return NULL;
10424       }
10425       return b;
10426 }
10427
10428 void
10429 InitChessProgram (ChessProgramState *cps, int setup)
10430 /* setup needed to setup FRC opening position */
10431 {
10432     char buf[MSG_SIZ], *b;
10433     if (appData.noChessProgram) return;
10434     hintRequested = FALSE;
10435     bookRequested = FALSE;
10436
10437     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10438     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10439     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10440     if(cps->memSize) { /* [HGM] memory */
10441       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10442         SendToProgram(buf, cps);
10443     }
10444     SendEgtPath(cps); /* [HGM] EGT */
10445     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10446       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10447         SendToProgram(buf, cps);
10448     }
10449
10450     setboardSpoiledMachineBlack = FALSE;
10451     SendToProgram(cps->initString, cps);
10452     if (gameInfo.variant != VariantNormal &&
10453         gameInfo.variant != VariantLoadable
10454         /* [HGM] also send variant if board size non-standard */
10455         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10456
10457       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10458                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10459       if (b == NULL) {
10460         DisplayFatalError(variantError, 0, 1);
10461         return;
10462       }
10463
10464       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10465       SendToProgram(buf, cps);
10466     }
10467     currentlyInitializedVariant = gameInfo.variant;
10468
10469     /* [HGM] send opening position in FRC to first engine */
10470     if(setup) {
10471           SendToProgram("force\n", cps);
10472           SendBoard(cps, 0);
10473           /* engine is now in force mode! Set flag to wake it up after first move. */
10474           setboardSpoiledMachineBlack = 1;
10475     }
10476
10477     if (cps->sendICS) {
10478       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10479       SendToProgram(buf, cps);
10480     }
10481     cps->maybeThinking = FALSE;
10482     cps->offeredDraw = 0;
10483     if (!appData.icsActive) {
10484         SendTimeControl(cps, movesPerSession, timeControl,
10485                         timeIncrement, appData.searchDepth,
10486                         searchTime);
10487     }
10488     if (appData.showThinking
10489         // [HGM] thinking: four options require thinking output to be sent
10490         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10491                                 ) {
10492         SendToProgram("post\n", cps);
10493     }
10494     SendToProgram("hard\n", cps);
10495     if (!appData.ponderNextMove) {
10496         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10497            it without being sure what state we are in first.  "hard"
10498            is not a toggle, so that one is OK.
10499          */
10500         SendToProgram("easy\n", cps);
10501     }
10502     if (cps->usePing) {
10503       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10504       SendToProgram(buf, cps);
10505     }
10506     cps->initDone = TRUE;
10507     ClearEngineOutputPane(cps == &second);
10508 }
10509
10510
10511 void
10512 ResendOptions (ChessProgramState *cps)
10513 { // send the stored value of the options
10514   int i;
10515   char buf[MSG_SIZ];
10516   Option *opt = cps->option;
10517   for(i=0; i<cps->nrOptions; i++, opt++) {
10518       switch(opt->type) {
10519         case Spin:
10520         case Slider:
10521         case CheckBox:
10522             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10523           break;
10524         case ComboBox:
10525           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10526           break;
10527         default:
10528             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10529           break;
10530         case Button:
10531         case SaveButton:
10532           continue;
10533       }
10534       SendToProgram(buf, cps);
10535   }
10536 }
10537
10538 void
10539 StartChessProgram (ChessProgramState *cps)
10540 {
10541     char buf[MSG_SIZ];
10542     int err;
10543
10544     if (appData.noChessProgram) return;
10545     cps->initDone = FALSE;
10546
10547     if (strcmp(cps->host, "localhost") == 0) {
10548         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10549     } else if (*appData.remoteShell == NULLCHAR) {
10550         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10551     } else {
10552         if (*appData.remoteUser == NULLCHAR) {
10553           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10554                     cps->program);
10555         } else {
10556           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10557                     cps->host, appData.remoteUser, cps->program);
10558         }
10559         err = StartChildProcess(buf, "", &cps->pr);
10560     }
10561
10562     if (err != 0) {
10563       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10564         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10565         if(cps != &first) return;
10566         appData.noChessProgram = TRUE;
10567         ThawUI();
10568         SetNCPMode();
10569 //      DisplayFatalError(buf, err, 1);
10570 //      cps->pr = NoProc;
10571 //      cps->isr = NULL;
10572         return;
10573     }
10574
10575     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10576     if (cps->protocolVersion > 1) {
10577       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10578       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10579         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10580         cps->comboCnt = 0;  //                and values of combo boxes
10581       }
10582       SendToProgram(buf, cps);
10583       if(cps->reload) ResendOptions(cps);
10584     } else {
10585       SendToProgram("xboard\n", cps);
10586     }
10587 }
10588
10589 void
10590 TwoMachinesEventIfReady P((void))
10591 {
10592   static int curMess = 0;
10593   if (first.lastPing != first.lastPong) {
10594     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10595     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10596     return;
10597   }
10598   if (second.lastPing != second.lastPong) {
10599     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10600     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10601     return;
10602   }
10603   DisplayMessage("", ""); curMess = 0;
10604   TwoMachinesEvent();
10605 }
10606
10607 char *
10608 MakeName (char *template)
10609 {
10610     time_t clock;
10611     struct tm *tm;
10612     static char buf[MSG_SIZ];
10613     char *p = buf;
10614     int i;
10615
10616     clock = time((time_t *)NULL);
10617     tm = localtime(&clock);
10618
10619     while(*p++ = *template++) if(p[-1] == '%') {
10620         switch(*template++) {
10621           case 0:   *p = 0; return buf;
10622           case 'Y': i = tm->tm_year+1900; break;
10623           case 'y': i = tm->tm_year-100; break;
10624           case 'M': i = tm->tm_mon+1; break;
10625           case 'd': i = tm->tm_mday; break;
10626           case 'h': i = tm->tm_hour; break;
10627           case 'm': i = tm->tm_min; break;
10628           case 's': i = tm->tm_sec; break;
10629           default:  i = 0;
10630         }
10631         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10632     }
10633     return buf;
10634 }
10635
10636 int
10637 CountPlayers (char *p)
10638 {
10639     int n = 0;
10640     while(p = strchr(p, '\n')) p++, n++; // count participants
10641     return n;
10642 }
10643
10644 FILE *
10645 WriteTourneyFile (char *results, FILE *f)
10646 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10647     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10648     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10649         // create a file with tournament description
10650         fprintf(f, "-participants {%s}\n", appData.participants);
10651         fprintf(f, "-seedBase %d\n", appData.seedBase);
10652         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10653         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10654         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10655         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10656         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10657         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10658         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10659         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10660         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10661         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10662         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10663         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10664         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10665         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10666         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10667         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10668         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10669         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10670         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10671         fprintf(f, "-smpCores %d\n", appData.smpCores);
10672         if(searchTime > 0)
10673                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10674         else {
10675                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10676                 fprintf(f, "-tc %s\n", appData.timeControl);
10677                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10678         }
10679         fprintf(f, "-results \"%s\"\n", results);
10680     }
10681     return f;
10682 }
10683
10684 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10685
10686 void
10687 Substitute (char *participants, int expunge)
10688 {
10689     int i, changed, changes=0, nPlayers=0;
10690     char *p, *q, *r, buf[MSG_SIZ];
10691     if(participants == NULL) return;
10692     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10693     r = p = participants; q = appData.participants;
10694     while(*p && *p == *q) {
10695         if(*p == '\n') r = p+1, nPlayers++;
10696         p++; q++;
10697     }
10698     if(*p) { // difference
10699         while(*p && *p++ != '\n');
10700         while(*q && *q++ != '\n');
10701       changed = nPlayers;
10702         changes = 1 + (strcmp(p, q) != 0);
10703     }
10704     if(changes == 1) { // a single engine mnemonic was changed
10705         q = r; while(*q) nPlayers += (*q++ == '\n');
10706         p = buf; while(*r && (*p = *r++) != '\n') p++;
10707         *p = NULLCHAR;
10708         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10709         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10710         if(mnemonic[i]) { // The substitute is valid
10711             FILE *f;
10712             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10713                 flock(fileno(f), LOCK_EX);
10714                 ParseArgsFromFile(f);
10715                 fseek(f, 0, SEEK_SET);
10716                 FREE(appData.participants); appData.participants = participants;
10717                 if(expunge) { // erase results of replaced engine
10718                     int len = strlen(appData.results), w, b, dummy;
10719                     for(i=0; i<len; i++) {
10720                         Pairing(i, nPlayers, &w, &b, &dummy);
10721                         if((w == changed || b == changed) && appData.results[i] == '*') {
10722                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10723                             fclose(f);
10724                             return;
10725                         }
10726                     }
10727                     for(i=0; i<len; i++) {
10728                         Pairing(i, nPlayers, &w, &b, &dummy);
10729                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10730                     }
10731                 }
10732                 WriteTourneyFile(appData.results, f);
10733                 fclose(f); // release lock
10734                 return;
10735             }
10736         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10737     }
10738     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10739     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10740     free(participants);
10741     return;
10742 }
10743
10744 int
10745 CheckPlayers (char *participants)
10746 {
10747         int i;
10748         char buf[MSG_SIZ], *p;
10749         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10750         while(p = strchr(participants, '\n')) {
10751             *p = NULLCHAR;
10752             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10753             if(!mnemonic[i]) {
10754                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10755                 *p = '\n';
10756                 DisplayError(buf, 0);
10757                 return 1;
10758             }
10759             *p = '\n';
10760             participants = p + 1;
10761         }
10762         return 0;
10763 }
10764
10765 int
10766 CreateTourney (char *name)
10767 {
10768         FILE *f;
10769         if(matchMode && strcmp(name, appData.tourneyFile)) {
10770              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10771         }
10772         if(name[0] == NULLCHAR) {
10773             if(appData.participants[0])
10774                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10775             return 0;
10776         }
10777         f = fopen(name, "r");
10778         if(f) { // file exists
10779             ASSIGN(appData.tourneyFile, name);
10780             ParseArgsFromFile(f); // parse it
10781         } else {
10782             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10783             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10784                 DisplayError(_("Not enough participants"), 0);
10785                 return 0;
10786             }
10787             if(CheckPlayers(appData.participants)) return 0;
10788             ASSIGN(appData.tourneyFile, name);
10789             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10790             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10791         }
10792         fclose(f);
10793         appData.noChessProgram = FALSE;
10794         appData.clockMode = TRUE;
10795         SetGNUMode();
10796         return 1;
10797 }
10798
10799 int
10800 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10801 {
10802     char buf[MSG_SIZ], *p, *q;
10803     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10804     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10805     skip = !all && group[0]; // if group requested, we start in skip mode
10806     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10807         p = names; q = buf; header = 0;
10808         while(*p && *p != '\n') *q++ = *p++;
10809         *q = 0;
10810         if(*p == '\n') p++;
10811         if(buf[0] == '#') {
10812             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10813             depth++; // we must be entering a new group
10814             if(all) continue; // suppress printing group headers when complete list requested
10815             header = 1;
10816             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10817         }
10818         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10819         if(engineList[i]) free(engineList[i]);
10820         engineList[i] = strdup(buf);
10821         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10822         if(engineMnemonic[i]) free(engineMnemonic[i]);
10823         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10824             strcat(buf, " (");
10825             sscanf(q + 8, "%s", buf + strlen(buf));
10826             strcat(buf, ")");
10827         }
10828         engineMnemonic[i] = strdup(buf);
10829         i++;
10830     }
10831     engineList[i] = engineMnemonic[i] = NULL;
10832     return i;
10833 }
10834
10835 // following implemented as macro to avoid type limitations
10836 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10837
10838 void
10839 SwapEngines (int n)
10840 {   // swap settings for first engine and other engine (so far only some selected options)
10841     int h;
10842     char *p;
10843     if(n == 0) return;
10844     SWAP(directory, p)
10845     SWAP(chessProgram, p)
10846     SWAP(isUCI, h)
10847     SWAP(hasOwnBookUCI, h)
10848     SWAP(protocolVersion, h)
10849     SWAP(reuse, h)
10850     SWAP(scoreIsAbsolute, h)
10851     SWAP(timeOdds, h)
10852     SWAP(logo, p)
10853     SWAP(pgnName, p)
10854     SWAP(pvSAN, h)
10855     SWAP(engOptions, p)
10856     SWAP(engInitString, p)
10857     SWAP(computerString, p)
10858     SWAP(features, p)
10859     SWAP(fenOverride, p)
10860     SWAP(NPS, h)
10861     SWAP(accumulateTC, h)
10862     SWAP(drawDepth, h)
10863     SWAP(host, p)
10864     SWAP(pseudo, h)
10865 }
10866
10867 int
10868 GetEngineLine (char *s, int n)
10869 {
10870     int i;
10871     char buf[MSG_SIZ];
10872     extern char *icsNames;
10873     if(!s || !*s) return 0;
10874     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10875     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10876     if(!mnemonic[i]) return 0;
10877     if(n == 11) return 1; // just testing if there was a match
10878     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10879     if(n == 1) SwapEngines(n);
10880     ParseArgsFromString(buf);
10881     if(n == 1) SwapEngines(n);
10882     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10883         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10884         ParseArgsFromString(buf);
10885     }
10886     return 1;
10887 }
10888
10889 int
10890 SetPlayer (int player, char *p)
10891 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10892     int i;
10893     char buf[MSG_SIZ], *engineName;
10894     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10895     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10896     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10897     if(mnemonic[i]) {
10898         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10899         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10900         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10901         ParseArgsFromString(buf);
10902     } else { // no engine with this nickname is installed!
10903         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10904         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10905         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10906         ModeHighlight();
10907         DisplayError(buf, 0);
10908         return 0;
10909     }
10910     free(engineName);
10911     return i;
10912 }
10913
10914 char *recentEngines;
10915
10916 void
10917 RecentEngineEvent (int nr)
10918 {
10919     int n;
10920 //    SwapEngines(1); // bump first to second
10921 //    ReplaceEngine(&second, 1); // and load it there
10922     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10923     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10924     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10925         ReplaceEngine(&first, 0);
10926         FloatToFront(&appData.recentEngineList, command[n]);
10927     }
10928 }
10929
10930 int
10931 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10932 {   // determine players from game number
10933     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10934
10935     if(appData.tourneyType == 0) {
10936         roundsPerCycle = (nPlayers - 1) | 1;
10937         pairingsPerRound = nPlayers / 2;
10938     } else if(appData.tourneyType > 0) {
10939         roundsPerCycle = nPlayers - appData.tourneyType;
10940         pairingsPerRound = appData.tourneyType;
10941     }
10942     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10943     gamesPerCycle = gamesPerRound * roundsPerCycle;
10944     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10945     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10946     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10947     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10948     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10949     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10950
10951     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10952     if(appData.roundSync) *syncInterval = gamesPerRound;
10953
10954     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10955
10956     if(appData.tourneyType == 0) {
10957         if(curPairing == (nPlayers-1)/2 ) {
10958             *whitePlayer = curRound;
10959             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10960         } else {
10961             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10962             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10963             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10964             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10965         }
10966     } else if(appData.tourneyType > 1) {
10967         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10968         *whitePlayer = curRound + appData.tourneyType;
10969     } else if(appData.tourneyType > 0) {
10970         *whitePlayer = curPairing;
10971         *blackPlayer = curRound + appData.tourneyType;
10972     }
10973
10974     // take care of white/black alternation per round.
10975     // For cycles and games this is already taken care of by default, derived from matchGame!
10976     return curRound & 1;
10977 }
10978
10979 int
10980 NextTourneyGame (int nr, int *swapColors)
10981 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10982     char *p, *q;
10983     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10984     FILE *tf;
10985     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10986     tf = fopen(appData.tourneyFile, "r");
10987     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10988     ParseArgsFromFile(tf); fclose(tf);
10989     InitTimeControls(); // TC might be altered from tourney file
10990
10991     nPlayers = CountPlayers(appData.participants); // count participants
10992     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10993     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10994
10995     if(syncInterval) {
10996         p = q = appData.results;
10997         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10998         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10999             DisplayMessage(_("Waiting for other game(s)"),"");
11000             waitingForGame = TRUE;
11001             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11002             return 0;
11003         }
11004         waitingForGame = FALSE;
11005     }
11006
11007     if(appData.tourneyType < 0) {
11008         if(nr>=0 && !pairingReceived) {
11009             char buf[1<<16];
11010             if(pairing.pr == NoProc) {
11011                 if(!appData.pairingEngine[0]) {
11012                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11013                     return 0;
11014                 }
11015                 StartChessProgram(&pairing); // starts the pairing engine
11016             }
11017             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11018             SendToProgram(buf, &pairing);
11019             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11020             SendToProgram(buf, &pairing);
11021             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11022         }
11023         pairingReceived = 0;                              // ... so we continue here
11024         *swapColors = 0;
11025         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11026         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11027         matchGame = 1; roundNr = nr / syncInterval + 1;
11028     }
11029
11030     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11031
11032     // redefine engines, engine dir, etc.
11033     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11034     if(first.pr == NoProc) {
11035       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11036       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11037     }
11038     if(second.pr == NoProc) {
11039       SwapEngines(1);
11040       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11041       SwapEngines(1);         // and make that valid for second engine by swapping
11042       InitEngine(&second, 1);
11043     }
11044     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11045     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11046     return OK;
11047 }
11048
11049 void
11050 NextMatchGame ()
11051 {   // performs game initialization that does not invoke engines, and then tries to start the game
11052     int res, firstWhite, swapColors = 0;
11053     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11054     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
11055         char buf[MSG_SIZ];
11056         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11057         if(strcmp(buf, currentDebugFile)) { // name has changed
11058             FILE *f = fopen(buf, "w");
11059             if(f) { // if opening the new file failed, just keep using the old one
11060                 ASSIGN(currentDebugFile, buf);
11061                 fclose(debugFP);
11062                 debugFP = f;
11063             }
11064             if(appData.serverFileName) {
11065                 if(serverFP) fclose(serverFP);
11066                 serverFP = fopen(appData.serverFileName, "w");
11067                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11068                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11069             }
11070         }
11071     }
11072     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11073     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11074     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11075     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11076     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11077     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11078     Reset(FALSE, first.pr != NoProc);
11079     res = LoadGameOrPosition(matchGame); // setup game
11080     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11081     if(!res) return; // abort when bad game/pos file
11082     TwoMachinesEvent();
11083 }
11084
11085 void
11086 UserAdjudicationEvent (int result)
11087 {
11088     ChessMove gameResult = GameIsDrawn;
11089
11090     if( result > 0 ) {
11091         gameResult = WhiteWins;
11092     }
11093     else if( result < 0 ) {
11094         gameResult = BlackWins;
11095     }
11096
11097     if( gameMode == TwoMachinesPlay ) {
11098         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11099     }
11100 }
11101
11102
11103 // [HGM] save: calculate checksum of game to make games easily identifiable
11104 int
11105 StringCheckSum (char *s)
11106 {
11107         int i = 0;
11108         if(s==NULL) return 0;
11109         while(*s) i = i*259 + *s++;
11110         return i;
11111 }
11112
11113 int
11114 GameCheckSum ()
11115 {
11116         int i, sum=0;
11117         for(i=backwardMostMove; i<forwardMostMove; i++) {
11118                 sum += pvInfoList[i].depth;
11119                 sum += StringCheckSum(parseList[i]);
11120                 sum += StringCheckSum(commentList[i]);
11121                 sum *= 261;
11122         }
11123         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11124         return sum + StringCheckSum(commentList[i]);
11125 } // end of save patch
11126
11127 void
11128 GameEnds (ChessMove result, char *resultDetails, int whosays)
11129 {
11130     GameMode nextGameMode;
11131     int isIcsGame;
11132     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11133
11134     if(endingGame) return; /* [HGM] crash: forbid recursion */
11135     endingGame = 1;
11136     if(twoBoards) { // [HGM] dual: switch back to one board
11137         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11138         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11139     }
11140     if (appData.debugMode) {
11141       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11142               result, resultDetails ? resultDetails : "(null)", whosays);
11143     }
11144
11145     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11146
11147     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11148
11149     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11150         /* If we are playing on ICS, the server decides when the
11151            game is over, but the engine can offer to draw, claim
11152            a draw, or resign.
11153          */
11154 #if ZIPPY
11155         if (appData.zippyPlay && first.initDone) {
11156             if (result == GameIsDrawn) {
11157                 /* In case draw still needs to be claimed */
11158                 SendToICS(ics_prefix);
11159                 SendToICS("draw\n");
11160             } else if (StrCaseStr(resultDetails, "resign")) {
11161                 SendToICS(ics_prefix);
11162                 SendToICS("resign\n");
11163             }
11164         }
11165 #endif
11166         endingGame = 0; /* [HGM] crash */
11167         return;
11168     }
11169
11170     /* If we're loading the game from a file, stop */
11171     if (whosays == GE_FILE) {
11172       (void) StopLoadGameTimer();
11173       gameFileFP = NULL;
11174     }
11175
11176     /* Cancel draw offers */
11177     first.offeredDraw = second.offeredDraw = 0;
11178
11179     /* If this is an ICS game, only ICS can really say it's done;
11180        if not, anyone can. */
11181     isIcsGame = (gameMode == IcsPlayingWhite ||
11182                  gameMode == IcsPlayingBlack ||
11183                  gameMode == IcsObserving    ||
11184                  gameMode == IcsExamining);
11185
11186     if (!isIcsGame || whosays == GE_ICS) {
11187         /* OK -- not an ICS game, or ICS said it was done */
11188         StopClocks();
11189         if (!isIcsGame && !appData.noChessProgram)
11190           SetUserThinkingEnables();
11191
11192         /* [HGM] if a machine claims the game end we verify this claim */
11193         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11194             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11195                 char claimer;
11196                 ChessMove trueResult = (ChessMove) -1;
11197
11198                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11199                                             first.twoMachinesColor[0] :
11200                                             second.twoMachinesColor[0] ;
11201
11202                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11204                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11205                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11206                 } else
11207                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11208                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11209                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11210                 } else
11211                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11212                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11213                 }
11214
11215                 // now verify win claims, but not in drop games, as we don't understand those yet
11216                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11217                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11218                     (result == WhiteWins && claimer == 'w' ||
11219                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11220                       if (appData.debugMode) {
11221                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11222                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11223                       }
11224                       if(result != trueResult) {
11225                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11226                               result = claimer == 'w' ? BlackWins : WhiteWins;
11227                               resultDetails = buf;
11228                       }
11229                 } else
11230                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11231                     && (forwardMostMove <= backwardMostMove ||
11232                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11233                         (claimer=='b')==(forwardMostMove&1))
11234                                                                                   ) {
11235                       /* [HGM] verify: draws that were not flagged are false claims */
11236                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11237                       result = claimer == 'w' ? BlackWins : WhiteWins;
11238                       resultDetails = buf;
11239                 }
11240                 /* (Claiming a loss is accepted no questions asked!) */
11241             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11242                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11243                 result = GameUnfinished;
11244                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11245             }
11246             /* [HGM] bare: don't allow bare King to win */
11247             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11248                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11249                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11250                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11251                && result != GameIsDrawn)
11252             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11253                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11254                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11255                         if(p >= 0 && p <= (int)WhiteKing) k++;
11256                 }
11257                 if (appData.debugMode) {
11258                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11259                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11260                 }
11261                 if(k <= 1) {
11262                         result = GameIsDrawn;
11263                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11264                         resultDetails = buf;
11265                 }
11266             }
11267         }
11268
11269
11270         if(serverMoves != NULL && !loadFlag) { char c = '=';
11271             if(result==WhiteWins) c = '+';
11272             if(result==BlackWins) c = '-';
11273             if(resultDetails != NULL)
11274                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11275         }
11276         if (resultDetails != NULL) {
11277             gameInfo.result = result;
11278             gameInfo.resultDetails = StrSave(resultDetails);
11279
11280             /* display last move only if game was not loaded from file */
11281             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11282                 DisplayMove(currentMove - 1);
11283
11284             if (forwardMostMove != 0) {
11285                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11286                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11287                                                                 ) {
11288                     if (*appData.saveGameFile != NULLCHAR) {
11289                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11290                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11291                         else
11292                         SaveGameToFile(appData.saveGameFile, TRUE);
11293                     } else if (appData.autoSaveGames) {
11294                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11295                     }
11296                     if (*appData.savePositionFile != NULLCHAR) {
11297                         SavePositionToFile(appData.savePositionFile);
11298                     }
11299                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11300                 }
11301             }
11302
11303             /* Tell program how game ended in case it is learning */
11304             /* [HGM] Moved this to after saving the PGN, just in case */
11305             /* engine died and we got here through time loss. In that */
11306             /* case we will get a fatal error writing the pipe, which */
11307             /* would otherwise lose us the PGN.                       */
11308             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11309             /* output during GameEnds should never be fatal anymore   */
11310             if (gameMode == MachinePlaysWhite ||
11311                 gameMode == MachinePlaysBlack ||
11312                 gameMode == TwoMachinesPlay ||
11313                 gameMode == IcsPlayingWhite ||
11314                 gameMode == IcsPlayingBlack ||
11315                 gameMode == BeginningOfGame) {
11316                 char buf[MSG_SIZ];
11317                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11318                         resultDetails);
11319                 if (first.pr != NoProc) {
11320                     SendToProgram(buf, &first);
11321                 }
11322                 if (second.pr != NoProc &&
11323                     gameMode == TwoMachinesPlay) {
11324                     SendToProgram(buf, &second);
11325                 }
11326             }
11327         }
11328
11329         if (appData.icsActive) {
11330             if (appData.quietPlay &&
11331                 (gameMode == IcsPlayingWhite ||
11332                  gameMode == IcsPlayingBlack)) {
11333                 SendToICS(ics_prefix);
11334                 SendToICS("set shout 1\n");
11335             }
11336             nextGameMode = IcsIdle;
11337             ics_user_moved = FALSE;
11338             /* clean up premove.  It's ugly when the game has ended and the
11339              * premove highlights are still on the board.
11340              */
11341             if (gotPremove) {
11342               gotPremove = FALSE;
11343               ClearPremoveHighlights();
11344               DrawPosition(FALSE, boards[currentMove]);
11345             }
11346             if (whosays == GE_ICS) {
11347                 switch (result) {
11348                 case WhiteWins:
11349                     if (gameMode == IcsPlayingWhite)
11350                         PlayIcsWinSound();
11351                     else if(gameMode == IcsPlayingBlack)
11352                         PlayIcsLossSound();
11353                     break;
11354                 case BlackWins:
11355                     if (gameMode == IcsPlayingBlack)
11356                         PlayIcsWinSound();
11357                     else if(gameMode == IcsPlayingWhite)
11358                         PlayIcsLossSound();
11359                     break;
11360                 case GameIsDrawn:
11361                     PlayIcsDrawSound();
11362                     break;
11363                 default:
11364                     PlayIcsUnfinishedSound();
11365                 }
11366             }
11367             if(appData.quitNext) { ExitEvent(0); return; }
11368         } else if (gameMode == EditGame ||
11369                    gameMode == PlayFromGameFile ||
11370                    gameMode == AnalyzeMode ||
11371                    gameMode == AnalyzeFile) {
11372             nextGameMode = gameMode;
11373         } else {
11374             nextGameMode = EndOfGame;
11375         }
11376         pausing = FALSE;
11377         ModeHighlight();
11378     } else {
11379         nextGameMode = gameMode;
11380     }
11381
11382     if (appData.noChessProgram) {
11383         gameMode = nextGameMode;
11384         ModeHighlight();
11385         endingGame = 0; /* [HGM] crash */
11386         return;
11387     }
11388
11389     if (first.reuse) {
11390         /* Put first chess program into idle state */
11391         if (first.pr != NoProc &&
11392             (gameMode == MachinePlaysWhite ||
11393              gameMode == MachinePlaysBlack ||
11394              gameMode == TwoMachinesPlay ||
11395              gameMode == IcsPlayingWhite ||
11396              gameMode == IcsPlayingBlack ||
11397              gameMode == BeginningOfGame)) {
11398             SendToProgram("force\n", &first);
11399             if (first.usePing) {
11400               char buf[MSG_SIZ];
11401               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11402               SendToProgram(buf, &first);
11403             }
11404         }
11405     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11406         /* Kill off first chess program */
11407         if (first.isr != NULL)
11408           RemoveInputSource(first.isr);
11409         first.isr = NULL;
11410
11411         if (first.pr != NoProc) {
11412             ExitAnalyzeMode();
11413             DoSleep( appData.delayBeforeQuit );
11414             SendToProgram("quit\n", &first);
11415             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11416             first.reload = TRUE;
11417         }
11418         first.pr = NoProc;
11419     }
11420     if (second.reuse) {
11421         /* Put second chess program into idle state */
11422         if (second.pr != NoProc &&
11423             gameMode == TwoMachinesPlay) {
11424             SendToProgram("force\n", &second);
11425             if (second.usePing) {
11426               char buf[MSG_SIZ];
11427               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11428               SendToProgram(buf, &second);
11429             }
11430         }
11431     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11432         /* Kill off second chess program */
11433         if (second.isr != NULL)
11434           RemoveInputSource(second.isr);
11435         second.isr = NULL;
11436
11437         if (second.pr != NoProc) {
11438             DoSleep( appData.delayBeforeQuit );
11439             SendToProgram("quit\n", &second);
11440             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11441             second.reload = TRUE;
11442         }
11443         second.pr = NoProc;
11444     }
11445
11446     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11447         char resChar = '=';
11448         switch (result) {
11449         case WhiteWins:
11450           resChar = '+';
11451           if (first.twoMachinesColor[0] == 'w') {
11452             first.matchWins++;
11453           } else {
11454             second.matchWins++;
11455           }
11456           break;
11457         case BlackWins:
11458           resChar = '-';
11459           if (first.twoMachinesColor[0] == 'b') {
11460             first.matchWins++;
11461           } else {
11462             second.matchWins++;
11463           }
11464           break;
11465         case GameUnfinished:
11466           resChar = ' ';
11467         default:
11468           break;
11469         }
11470
11471         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11472         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11473             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11474             ReserveGame(nextGame, resChar); // sets nextGame
11475             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11476             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11477         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11478
11479         if (nextGame <= appData.matchGames && !abortMatch) {
11480             gameMode = nextGameMode;
11481             matchGame = nextGame; // this will be overruled in tourney mode!
11482             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11483             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11484             endingGame = 0; /* [HGM] crash */
11485             return;
11486         } else {
11487             gameMode = nextGameMode;
11488             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11489                      first.tidy, second.tidy,
11490                      first.matchWins, second.matchWins,
11491                      appData.matchGames - (first.matchWins + second.matchWins));
11492             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11493             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11494             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11495             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11496                 first.twoMachinesColor = "black\n";
11497                 second.twoMachinesColor = "white\n";
11498             } else {
11499                 first.twoMachinesColor = "white\n";
11500                 second.twoMachinesColor = "black\n";
11501             }
11502         }
11503     }
11504     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11505         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11506       ExitAnalyzeMode();
11507     gameMode = nextGameMode;
11508     ModeHighlight();
11509     endingGame = 0;  /* [HGM] crash */
11510     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11511         if(matchMode == TRUE) { // match through command line: exit with or without popup
11512             if(ranking) {
11513                 ToNrEvent(forwardMostMove);
11514                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11515                 else ExitEvent(0);
11516             } else DisplayFatalError(buf, 0, 0);
11517         } else { // match through menu; just stop, with or without popup
11518             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11519             ModeHighlight();
11520             if(ranking){
11521                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11522             } else DisplayNote(buf);
11523       }
11524       if(ranking) free(ranking);
11525     }
11526 }
11527
11528 /* Assumes program was just initialized (initString sent).
11529    Leaves program in force mode. */
11530 void
11531 FeedMovesToProgram (ChessProgramState *cps, int upto)
11532 {
11533     int i;
11534
11535     if (appData.debugMode)
11536       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11537               startedFromSetupPosition ? "position and " : "",
11538               backwardMostMove, upto, cps->which);
11539     if(currentlyInitializedVariant != gameInfo.variant) {
11540       char buf[MSG_SIZ];
11541         // [HGM] variantswitch: make engine aware of new variant
11542         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11543                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11544                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11545         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11546         SendToProgram(buf, cps);
11547         currentlyInitializedVariant = gameInfo.variant;
11548     }
11549     SendToProgram("force\n", cps);
11550     if (startedFromSetupPosition) {
11551         SendBoard(cps, backwardMostMove);
11552     if (appData.debugMode) {
11553         fprintf(debugFP, "feedMoves\n");
11554     }
11555     }
11556     for (i = backwardMostMove; i < upto; i++) {
11557         SendMoveToProgram(i, cps);
11558     }
11559 }
11560
11561
11562 int
11563 ResurrectChessProgram ()
11564 {
11565      /* The chess program may have exited.
11566         If so, restart it and feed it all the moves made so far. */
11567     static int doInit = 0;
11568
11569     if (appData.noChessProgram) return 1;
11570
11571     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11572         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11573         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11574         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11575     } else {
11576         if (first.pr != NoProc) return 1;
11577         StartChessProgram(&first);
11578     }
11579     InitChessProgram(&first, FALSE);
11580     FeedMovesToProgram(&first, currentMove);
11581
11582     if (!first.sendTime) {
11583         /* can't tell gnuchess what its clock should read,
11584            so we bow to its notion. */
11585         ResetClocks();
11586         timeRemaining[0][currentMove] = whiteTimeRemaining;
11587         timeRemaining[1][currentMove] = blackTimeRemaining;
11588     }
11589
11590     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11591                 appData.icsEngineAnalyze) && first.analysisSupport) {
11592       SendToProgram("analyze\n", &first);
11593       first.analyzing = TRUE;
11594     }
11595     return 1;
11596 }
11597
11598 /*
11599  * Button procedures
11600  */
11601 void
11602 Reset (int redraw, int init)
11603 {
11604     int i;
11605
11606     if (appData.debugMode) {
11607         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11608                 redraw, init, gameMode);
11609     }
11610     CleanupTail(); // [HGM] vari: delete any stored variations
11611     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11612     pausing = pauseExamInvalid = FALSE;
11613     startedFromSetupPosition = blackPlaysFirst = FALSE;
11614     firstMove = TRUE;
11615     whiteFlag = blackFlag = FALSE;
11616     userOfferedDraw = FALSE;
11617     hintRequested = bookRequested = FALSE;
11618     first.maybeThinking = FALSE;
11619     second.maybeThinking = FALSE;
11620     first.bookSuspend = FALSE; // [HGM] book
11621     second.bookSuspend = FALSE;
11622     thinkOutput[0] = NULLCHAR;
11623     lastHint[0] = NULLCHAR;
11624     ClearGameInfo(&gameInfo);
11625     gameInfo.variant = StringToVariant(appData.variant);
11626     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11627     ics_user_moved = ics_clock_paused = FALSE;
11628     ics_getting_history = H_FALSE;
11629     ics_gamenum = -1;
11630     white_holding[0] = black_holding[0] = NULLCHAR;
11631     ClearProgramStats();
11632     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11633
11634     ResetFrontEnd();
11635     ClearHighlights();
11636     flipView = appData.flipView;
11637     ClearPremoveHighlights();
11638     gotPremove = FALSE;
11639     alarmSounded = FALSE;
11640     killX = killY = -1; // [HGM] lion
11641
11642     GameEnds(EndOfFile, NULL, GE_PLAYER);
11643     if(appData.serverMovesName != NULL) {
11644         /* [HGM] prepare to make moves file for broadcasting */
11645         clock_t t = clock();
11646         if(serverMoves != NULL) fclose(serverMoves);
11647         serverMoves = fopen(appData.serverMovesName, "r");
11648         if(serverMoves != NULL) {
11649             fclose(serverMoves);
11650             /* delay 15 sec before overwriting, so all clients can see end */
11651             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11652         }
11653         serverMoves = fopen(appData.serverMovesName, "w");
11654     }
11655
11656     ExitAnalyzeMode();
11657     gameMode = BeginningOfGame;
11658     ModeHighlight();
11659     if(appData.icsActive) gameInfo.variant = VariantNormal;
11660     currentMove = forwardMostMove = backwardMostMove = 0;
11661     MarkTargetSquares(1);
11662     InitPosition(redraw);
11663     for (i = 0; i < MAX_MOVES; i++) {
11664         if (commentList[i] != NULL) {
11665             free(commentList[i]);
11666             commentList[i] = NULL;
11667         }
11668     }
11669     ResetClocks();
11670     timeRemaining[0][0] = whiteTimeRemaining;
11671     timeRemaining[1][0] = blackTimeRemaining;
11672
11673     if (first.pr == NoProc) {
11674         StartChessProgram(&first);
11675     }
11676     if (init) {
11677             InitChessProgram(&first, startedFromSetupPosition);
11678     }
11679     DisplayTitle("");
11680     DisplayMessage("", "");
11681     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11682     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11683     ClearMap();        // [HGM] exclude: invalidate map
11684 }
11685
11686 void
11687 AutoPlayGameLoop ()
11688 {
11689     for (;;) {
11690         if (!AutoPlayOneMove())
11691           return;
11692         if (matchMode || appData.timeDelay == 0)
11693           continue;
11694         if (appData.timeDelay < 0)
11695           return;
11696         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11697         break;
11698     }
11699 }
11700
11701 void
11702 AnalyzeNextGame()
11703 {
11704     ReloadGame(1); // next game
11705 }
11706
11707 int
11708 AutoPlayOneMove ()
11709 {
11710     int fromX, fromY, toX, toY;
11711
11712     if (appData.debugMode) {
11713       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11714     }
11715
11716     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11717       return FALSE;
11718
11719     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11720       pvInfoList[currentMove].depth = programStats.depth;
11721       pvInfoList[currentMove].score = programStats.score;
11722       pvInfoList[currentMove].time  = 0;
11723       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11724       else { // append analysis of final position as comment
11725         char buf[MSG_SIZ];
11726         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11727         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11728       }
11729       programStats.depth = 0;
11730     }
11731
11732     if (currentMove >= forwardMostMove) {
11733       if(gameMode == AnalyzeFile) {
11734           if(appData.loadGameIndex == -1) {
11735             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11736           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11737           } else {
11738           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11739         }
11740       }
11741 //      gameMode = EndOfGame;
11742 //      ModeHighlight();
11743
11744       /* [AS] Clear current move marker at the end of a game */
11745       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11746
11747       return FALSE;
11748     }
11749
11750     toX = moveList[currentMove][2] - AAA;
11751     toY = moveList[currentMove][3] - ONE;
11752
11753     if (moveList[currentMove][1] == '@') {
11754         if (appData.highlightLastMove) {
11755             SetHighlights(-1, -1, toX, toY);
11756         }
11757     } else {
11758         fromX = moveList[currentMove][0] - AAA;
11759         fromY = moveList[currentMove][1] - ONE;
11760
11761         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11762
11763         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11764
11765         if (appData.highlightLastMove) {
11766             SetHighlights(fromX, fromY, toX, toY);
11767         }
11768     }
11769     DisplayMove(currentMove);
11770     SendMoveToProgram(currentMove++, &first);
11771     DisplayBothClocks();
11772     DrawPosition(FALSE, boards[currentMove]);
11773     // [HGM] PV info: always display, routine tests if empty
11774     DisplayComment(currentMove - 1, commentList[currentMove]);
11775     return TRUE;
11776 }
11777
11778
11779 int
11780 LoadGameOneMove (ChessMove readAhead)
11781 {
11782     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11783     char promoChar = NULLCHAR;
11784     ChessMove moveType;
11785     char move[MSG_SIZ];
11786     char *p, *q;
11787
11788     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11789         gameMode != AnalyzeMode && gameMode != Training) {
11790         gameFileFP = NULL;
11791         return FALSE;
11792     }
11793
11794     yyboardindex = forwardMostMove;
11795     if (readAhead != EndOfFile) {
11796       moveType = readAhead;
11797     } else {
11798       if (gameFileFP == NULL)
11799           return FALSE;
11800       moveType = (ChessMove) Myylex();
11801     }
11802
11803     done = FALSE;
11804     switch (moveType) {
11805       case Comment:
11806         if (appData.debugMode)
11807           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11808         p = yy_text;
11809
11810         /* append the comment but don't display it */
11811         AppendComment(currentMove, p, FALSE);
11812         return TRUE;
11813
11814       case WhiteCapturesEnPassant:
11815       case BlackCapturesEnPassant:
11816       case WhitePromotion:
11817       case BlackPromotion:
11818       case WhiteNonPromotion:
11819       case BlackNonPromotion:
11820       case NormalMove:
11821       case FirstLeg:
11822       case WhiteKingSideCastle:
11823       case WhiteQueenSideCastle:
11824       case BlackKingSideCastle:
11825       case BlackQueenSideCastle:
11826       case WhiteKingSideCastleWild:
11827       case WhiteQueenSideCastleWild:
11828       case BlackKingSideCastleWild:
11829       case BlackQueenSideCastleWild:
11830       /* PUSH Fabien */
11831       case WhiteHSideCastleFR:
11832       case WhiteASideCastleFR:
11833       case BlackHSideCastleFR:
11834       case BlackASideCastleFR:
11835       /* POP Fabien */
11836         if (appData.debugMode)
11837           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11838         fromX = currentMoveString[0] - AAA;
11839         fromY = currentMoveString[1] - ONE;
11840         toX = currentMoveString[2] - AAA;
11841         toY = currentMoveString[3] - ONE;
11842         promoChar = currentMoveString[4];
11843         if(promoChar == ';') promoChar = NULLCHAR;
11844         break;
11845
11846       case WhiteDrop:
11847       case BlackDrop:
11848         if (appData.debugMode)
11849           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11850         fromX = moveType == WhiteDrop ?
11851           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11852         (int) CharToPiece(ToLower(currentMoveString[0]));
11853         fromY = DROP_RANK;
11854         toX = currentMoveString[2] - AAA;
11855         toY = currentMoveString[3] - ONE;
11856         break;
11857
11858       case WhiteWins:
11859       case BlackWins:
11860       case GameIsDrawn:
11861       case GameUnfinished:
11862         if (appData.debugMode)
11863           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11864         p = strchr(yy_text, '{');
11865         if (p == NULL) p = strchr(yy_text, '(');
11866         if (p == NULL) {
11867             p = yy_text;
11868             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11869         } else {
11870             q = strchr(p, *p == '{' ? '}' : ')');
11871             if (q != NULL) *q = NULLCHAR;
11872             p++;
11873         }
11874         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11875         GameEnds(moveType, p, GE_FILE);
11876         done = TRUE;
11877         if (cmailMsgLoaded) {
11878             ClearHighlights();
11879             flipView = WhiteOnMove(currentMove);
11880             if (moveType == GameUnfinished) flipView = !flipView;
11881             if (appData.debugMode)
11882               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11883         }
11884         break;
11885
11886       case EndOfFile:
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parser hit end of file\n");
11889         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11890           case MT_NONE:
11891           case MT_CHECK:
11892             break;
11893           case MT_CHECKMATE:
11894           case MT_STAINMATE:
11895             if (WhiteOnMove(currentMove)) {
11896                 GameEnds(BlackWins, "Black mates", GE_FILE);
11897             } else {
11898                 GameEnds(WhiteWins, "White mates", GE_FILE);
11899             }
11900             break;
11901           case MT_STALEMATE:
11902             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11903             break;
11904         }
11905         done = TRUE;
11906         break;
11907
11908       case MoveNumberOne:
11909         if (lastLoadGameStart == GNUChessGame) {
11910             /* GNUChessGames have numbers, but they aren't move numbers */
11911             if (appData.debugMode)
11912               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11913                       yy_text, (int) moveType);
11914             return LoadGameOneMove(EndOfFile); /* tail recursion */
11915         }
11916         /* else fall thru */
11917
11918       case XBoardGame:
11919       case GNUChessGame:
11920       case PGNTag:
11921         /* Reached start of next game in file */
11922         if (appData.debugMode)
11923           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11924         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11925           case MT_NONE:
11926           case MT_CHECK:
11927             break;
11928           case MT_CHECKMATE:
11929           case MT_STAINMATE:
11930             if (WhiteOnMove(currentMove)) {
11931                 GameEnds(BlackWins, "Black mates", GE_FILE);
11932             } else {
11933                 GameEnds(WhiteWins, "White mates", GE_FILE);
11934             }
11935             break;
11936           case MT_STALEMATE:
11937             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11938             break;
11939         }
11940         done = TRUE;
11941         break;
11942
11943       case PositionDiagram:     /* should not happen; ignore */
11944       case ElapsedTime:         /* ignore */
11945       case NAG:                 /* ignore */
11946         if (appData.debugMode)
11947           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11948                   yy_text, (int) moveType);
11949         return LoadGameOneMove(EndOfFile); /* tail recursion */
11950
11951       case IllegalMove:
11952         if (appData.testLegality) {
11953             if (appData.debugMode)
11954               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11955             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11956                     (forwardMostMove / 2) + 1,
11957                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11958             DisplayError(move, 0);
11959             done = TRUE;
11960         } else {
11961             if (appData.debugMode)
11962               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11963                       yy_text, currentMoveString);
11964             fromX = currentMoveString[0] - AAA;
11965             fromY = currentMoveString[1] - ONE;
11966             toX = currentMoveString[2] - AAA;
11967             toY = currentMoveString[3] - ONE;
11968             promoChar = currentMoveString[4];
11969         }
11970         break;
11971
11972       case AmbiguousMove:
11973         if (appData.debugMode)
11974           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11975         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11976                 (forwardMostMove / 2) + 1,
11977                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11978         DisplayError(move, 0);
11979         done = TRUE;
11980         break;
11981
11982       default:
11983       case ImpossibleMove:
11984         if (appData.debugMode)
11985           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11986         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11987                 (forwardMostMove / 2) + 1,
11988                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11989         DisplayError(move, 0);
11990         done = TRUE;
11991         break;
11992     }
11993
11994     if (done) {
11995         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11996             DrawPosition(FALSE, boards[currentMove]);
11997             DisplayBothClocks();
11998             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11999               DisplayComment(currentMove - 1, commentList[currentMove]);
12000         }
12001         (void) StopLoadGameTimer();
12002         gameFileFP = NULL;
12003         cmailOldMove = forwardMostMove;
12004         return FALSE;
12005     } else {
12006         /* currentMoveString is set as a side-effect of yylex */
12007
12008         thinkOutput[0] = NULLCHAR;
12009         MakeMove(fromX, fromY, toX, toY, promoChar);
12010         killX = killY = -1; // [HGM] lion: used up
12011         currentMove = forwardMostMove;
12012         return TRUE;
12013     }
12014 }
12015
12016 /* Load the nth game from the given file */
12017 int
12018 LoadGameFromFile (char *filename, int n, char *title, int useList)
12019 {
12020     FILE *f;
12021     char buf[MSG_SIZ];
12022
12023     if (strcmp(filename, "-") == 0) {
12024         f = stdin;
12025         title = "stdin";
12026     } else {
12027         f = fopen(filename, "rb");
12028         if (f == NULL) {
12029           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12030             DisplayError(buf, errno);
12031             return FALSE;
12032         }
12033     }
12034     if (fseek(f, 0, 0) == -1) {
12035         /* f is not seekable; probably a pipe */
12036         useList = FALSE;
12037     }
12038     if (useList && n == 0) {
12039         int error = GameListBuild(f);
12040         if (error) {
12041             DisplayError(_("Cannot build game list"), error);
12042         } else if (!ListEmpty(&gameList) &&
12043                    ((ListGame *) gameList.tailPred)->number > 1) {
12044             GameListPopUp(f, title);
12045             return TRUE;
12046         }
12047         GameListDestroy();
12048         n = 1;
12049     }
12050     if (n == 0) n = 1;
12051     return LoadGame(f, n, title, FALSE);
12052 }
12053
12054
12055 void
12056 MakeRegisteredMove ()
12057 {
12058     int fromX, fromY, toX, toY;
12059     char promoChar;
12060     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12061         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12062           case CMAIL_MOVE:
12063           case CMAIL_DRAW:
12064             if (appData.debugMode)
12065               fprintf(debugFP, "Restoring %s for game %d\n",
12066                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12067
12068             thinkOutput[0] = NULLCHAR;
12069             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12070             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12071             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12072             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12073             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12074             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12075             MakeMove(fromX, fromY, toX, toY, promoChar);
12076             ShowMove(fromX, fromY, toX, toY);
12077
12078             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12079               case MT_NONE:
12080               case MT_CHECK:
12081                 break;
12082
12083               case MT_CHECKMATE:
12084               case MT_STAINMATE:
12085                 if (WhiteOnMove(currentMove)) {
12086                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12087                 } else {
12088                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12089                 }
12090                 break;
12091
12092               case MT_STALEMATE:
12093                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12094                 break;
12095             }
12096
12097             break;
12098
12099           case CMAIL_RESIGN:
12100             if (WhiteOnMove(currentMove)) {
12101                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12102             } else {
12103                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12104             }
12105             break;
12106
12107           case CMAIL_ACCEPT:
12108             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12109             break;
12110
12111           default:
12112             break;
12113         }
12114     }
12115
12116     return;
12117 }
12118
12119 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12120 int
12121 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12122 {
12123     int retVal;
12124
12125     if (gameNumber > nCmailGames) {
12126         DisplayError(_("No more games in this message"), 0);
12127         return FALSE;
12128     }
12129     if (f == lastLoadGameFP) {
12130         int offset = gameNumber - lastLoadGameNumber;
12131         if (offset == 0) {
12132             cmailMsg[0] = NULLCHAR;
12133             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12134                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12135                 nCmailMovesRegistered--;
12136             }
12137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12138             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12139                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12140             }
12141         } else {
12142             if (! RegisterMove()) return FALSE;
12143         }
12144     }
12145
12146     retVal = LoadGame(f, gameNumber, title, useList);
12147
12148     /* Make move registered during previous look at this game, if any */
12149     MakeRegisteredMove();
12150
12151     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12152         commentList[currentMove]
12153           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12154         DisplayComment(currentMove - 1, commentList[currentMove]);
12155     }
12156
12157     return retVal;
12158 }
12159
12160 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12161 int
12162 ReloadGame (int offset)
12163 {
12164     int gameNumber = lastLoadGameNumber + offset;
12165     if (lastLoadGameFP == NULL) {
12166         DisplayError(_("No game has been loaded yet"), 0);
12167         return FALSE;
12168     }
12169     if (gameNumber <= 0) {
12170         DisplayError(_("Can't back up any further"), 0);
12171         return FALSE;
12172     }
12173     if (cmailMsgLoaded) {
12174         return CmailLoadGame(lastLoadGameFP, gameNumber,
12175                              lastLoadGameTitle, lastLoadGameUseList);
12176     } else {
12177         return LoadGame(lastLoadGameFP, gameNumber,
12178                         lastLoadGameTitle, lastLoadGameUseList);
12179     }
12180 }
12181
12182 int keys[EmptySquare+1];
12183
12184 int
12185 PositionMatches (Board b1, Board b2)
12186 {
12187     int r, f, sum=0;
12188     switch(appData.searchMode) {
12189         case 1: return CompareWithRights(b1, b2);
12190         case 2:
12191             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12192                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12193             }
12194             return TRUE;
12195         case 3:
12196             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12197               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12198                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12199             }
12200             return sum==0;
12201         case 4:
12202             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12203                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12204             }
12205             return sum==0;
12206     }
12207     return TRUE;
12208 }
12209
12210 #define Q_PROMO  4
12211 #define Q_EP     3
12212 #define Q_BCASTL 2
12213 #define Q_WCASTL 1
12214
12215 int pieceList[256], quickBoard[256];
12216 ChessSquare pieceType[256] = { EmptySquare };
12217 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12218 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12219 int soughtTotal, turn;
12220 Boolean epOK, flipSearch;
12221
12222 typedef struct {
12223     unsigned char piece, to;
12224 } Move;
12225
12226 #define DSIZE (250000)
12227
12228 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12229 Move *moveDatabase = initialSpace;
12230 unsigned int movePtr, dataSize = DSIZE;
12231
12232 int
12233 MakePieceList (Board board, int *counts)
12234 {
12235     int r, f, n=Q_PROMO, total=0;
12236     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12237     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12238         int sq = f + (r<<4);
12239         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12240             quickBoard[sq] = ++n;
12241             pieceList[n] = sq;
12242             pieceType[n] = board[r][f];
12243             counts[board[r][f]]++;
12244             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12245             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12246             total++;
12247         }
12248     }
12249     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12250     return total;
12251 }
12252
12253 void
12254 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12255 {
12256     int sq = fromX + (fromY<<4);
12257     int piece = quickBoard[sq], rook;
12258     quickBoard[sq] = 0;
12259     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12260     if(piece == pieceList[1] && fromY == toY) {
12261       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12262         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12263         moveDatabase[movePtr++].piece = Q_WCASTL;
12264         quickBoard[sq] = piece;
12265         piece = quickBoard[from]; quickBoard[from] = 0;
12266         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12267       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12268         quickBoard[sq] = 0; // remove Rook
12269         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12270         moveDatabase[movePtr++].piece = Q_WCASTL;
12271         quickBoard[sq] = pieceList[1]; // put King
12272         piece = rook;
12273         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12274       }
12275     } else
12276     if(piece == pieceList[2] && fromY == toY) {
12277       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12278         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12279         moveDatabase[movePtr++].piece = Q_BCASTL;
12280         quickBoard[sq] = piece;
12281         piece = quickBoard[from]; quickBoard[from] = 0;
12282         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12283       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12284         quickBoard[sq] = 0; // remove Rook
12285         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12286         moveDatabase[movePtr++].piece = Q_BCASTL;
12287         quickBoard[sq] = pieceList[2]; // put King
12288         piece = rook;
12289         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12290       }
12291     } else
12292     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12293         quickBoard[(fromY<<4)+toX] = 0;
12294         moveDatabase[movePtr].piece = Q_EP;
12295         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12296         moveDatabase[movePtr].to = sq;
12297     } else
12298     if(promoPiece != pieceType[piece]) {
12299         moveDatabase[movePtr++].piece = Q_PROMO;
12300         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12301     }
12302     moveDatabase[movePtr].piece = piece;
12303     quickBoard[sq] = piece;
12304     movePtr++;
12305 }
12306
12307 int
12308 PackGame (Board board)
12309 {
12310     Move *newSpace = NULL;
12311     moveDatabase[movePtr].piece = 0; // terminate previous game
12312     if(movePtr > dataSize) {
12313         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12314         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12315         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12316         if(newSpace) {
12317             int i;
12318             Move *p = moveDatabase, *q = newSpace;
12319             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12320             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12321             moveDatabase = newSpace;
12322         } else { // calloc failed, we must be out of memory. Too bad...
12323             dataSize = 0; // prevent calloc events for all subsequent games
12324             return 0;     // and signal this one isn't cached
12325         }
12326     }
12327     movePtr++;
12328     MakePieceList(board, counts);
12329     return movePtr;
12330 }
12331
12332 int
12333 QuickCompare (Board board, int *minCounts, int *maxCounts)
12334 {   // compare according to search mode
12335     int r, f;
12336     switch(appData.searchMode)
12337     {
12338       case 1: // exact position match
12339         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12340         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12341             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12342         }
12343         break;
12344       case 2: // can have extra material on empty squares
12345         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12346             if(board[r][f] == EmptySquare) continue;
12347             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12348         }
12349         break;
12350       case 3: // material with exact Pawn structure
12351         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12352             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12353             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12354         } // fall through to material comparison
12355       case 4: // exact material
12356         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12357         break;
12358       case 6: // material range with given imbalance
12359         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12360         // fall through to range comparison
12361       case 5: // material range
12362         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12363     }
12364     return TRUE;
12365 }
12366
12367 int
12368 QuickScan (Board board, Move *move)
12369 {   // reconstruct game,and compare all positions in it
12370     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12371     do {
12372         int piece = move->piece;
12373         int to = move->to, from = pieceList[piece];
12374         if(found < 0) { // if already found just scan to game end for final piece count
12375           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12376            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12377            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12378                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12379             ) {
12380             static int lastCounts[EmptySquare+1];
12381             int i;
12382             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12383             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12384           } else stretch = 0;
12385           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12386           if(found >= 0 && !appData.minPieces) return found;
12387         }
12388         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12389           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12390           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12391             piece = (++move)->piece;
12392             from = pieceList[piece];
12393             counts[pieceType[piece]]--;
12394             pieceType[piece] = (ChessSquare) move->to;
12395             counts[move->to]++;
12396           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12397             counts[pieceType[quickBoard[to]]]--;
12398             quickBoard[to] = 0; total--;
12399             move++;
12400             continue;
12401           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12402             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12403             from  = pieceList[piece]; // so this must be King
12404             quickBoard[from] = 0;
12405             pieceList[piece] = to;
12406             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12407             quickBoard[from] = 0; // rook
12408             quickBoard[to] = piece;
12409             to = move->to; piece = move->piece;
12410             goto aftercastle;
12411           }
12412         }
12413         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12414         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12415         quickBoard[from] = 0;
12416       aftercastle:
12417         quickBoard[to] = piece;
12418         pieceList[piece] = to;
12419         cnt++; turn ^= 3;
12420         move++;
12421     } while(1);
12422 }
12423
12424 void
12425 InitSearch ()
12426 {
12427     int r, f;
12428     flipSearch = FALSE;
12429     CopyBoard(soughtBoard, boards[currentMove]);
12430     soughtTotal = MakePieceList(soughtBoard, maxSought);
12431     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12432     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12433     CopyBoard(reverseBoard, boards[currentMove]);
12434     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12435         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12436         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12437         reverseBoard[r][f] = piece;
12438     }
12439     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12440     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12441     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12442                  || (boards[currentMove][CASTLING][2] == NoRights ||
12443                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12444                  && (boards[currentMove][CASTLING][5] == NoRights ||
12445                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12446       ) {
12447         flipSearch = TRUE;
12448         CopyBoard(flipBoard, soughtBoard);
12449         CopyBoard(rotateBoard, reverseBoard);
12450         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12451             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12452             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12453         }
12454     }
12455     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12456     if(appData.searchMode >= 5) {
12457         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12458         MakePieceList(soughtBoard, minSought);
12459         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12460     }
12461     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12462         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12463 }
12464
12465 GameInfo dummyInfo;
12466 static int creatingBook;
12467
12468 int
12469 GameContainsPosition (FILE *f, ListGame *lg)
12470 {
12471     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12472     int fromX, fromY, toX, toY;
12473     char promoChar;
12474     static int initDone=FALSE;
12475
12476     // weed out games based on numerical tag comparison
12477     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12478     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12479     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12480     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12481     if(!initDone) {
12482         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12483         initDone = TRUE;
12484     }
12485     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12486     else CopyBoard(boards[scratch], initialPosition); // default start position
12487     if(lg->moves) {
12488         turn = btm + 1;
12489         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12490         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12491     }
12492     if(btm) plyNr++;
12493     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12494     fseek(f, lg->offset, 0);
12495     yynewfile(f);
12496     while(1) {
12497         yyboardindex = scratch;
12498         quickFlag = plyNr+1;
12499         next = Myylex();
12500         quickFlag = 0;
12501         switch(next) {
12502             case PGNTag:
12503                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12504             default:
12505                 continue;
12506
12507             case XBoardGame:
12508             case GNUChessGame:
12509                 if(plyNr) return -1; // after we have seen moves, this is for new game
12510               continue;
12511
12512             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12513             case ImpossibleMove:
12514             case WhiteWins: // game ends here with these four
12515             case BlackWins:
12516             case GameIsDrawn:
12517             case GameUnfinished:
12518                 return -1;
12519
12520             case IllegalMove:
12521                 if(appData.testLegality) return -1;
12522             case WhiteCapturesEnPassant:
12523             case BlackCapturesEnPassant:
12524             case WhitePromotion:
12525             case BlackPromotion:
12526             case WhiteNonPromotion:
12527             case BlackNonPromotion:
12528             case NormalMove:
12529             case FirstLeg:
12530             case WhiteKingSideCastle:
12531             case WhiteQueenSideCastle:
12532             case BlackKingSideCastle:
12533             case BlackQueenSideCastle:
12534             case WhiteKingSideCastleWild:
12535             case WhiteQueenSideCastleWild:
12536             case BlackKingSideCastleWild:
12537             case BlackQueenSideCastleWild:
12538             case WhiteHSideCastleFR:
12539             case WhiteASideCastleFR:
12540             case BlackHSideCastleFR:
12541             case BlackASideCastleFR:
12542                 fromX = currentMoveString[0] - AAA;
12543                 fromY = currentMoveString[1] - ONE;
12544                 toX = currentMoveString[2] - AAA;
12545                 toY = currentMoveString[3] - ONE;
12546                 promoChar = currentMoveString[4];
12547                 break;
12548             case WhiteDrop:
12549             case BlackDrop:
12550                 fromX = next == WhiteDrop ?
12551                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12552                   (int) CharToPiece(ToLower(currentMoveString[0]));
12553                 fromY = DROP_RANK;
12554                 toX = currentMoveString[2] - AAA;
12555                 toY = currentMoveString[3] - ONE;
12556                 promoChar = 0;
12557                 break;
12558         }
12559         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12560         plyNr++;
12561         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12562         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12563         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12564         if(appData.findMirror) {
12565             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12566             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12567         }
12568     }
12569 }
12570
12571 /* Load the nth game from open file f */
12572 int
12573 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12574 {
12575     ChessMove cm;
12576     char buf[MSG_SIZ];
12577     int gn = gameNumber;
12578     ListGame *lg = NULL;
12579     int numPGNTags = 0;
12580     int err, pos = -1;
12581     GameMode oldGameMode;
12582     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12583
12584     if (appData.debugMode)
12585         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12586
12587     if (gameMode == Training )
12588         SetTrainingModeOff();
12589
12590     oldGameMode = gameMode;
12591     if (gameMode != BeginningOfGame) {
12592       Reset(FALSE, TRUE);
12593     }
12594     killX = killY = -1; // [HGM] lion: in case we did not Reset
12595
12596     gameFileFP = f;
12597     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12598         fclose(lastLoadGameFP);
12599     }
12600
12601     if (useList) {
12602         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12603
12604         if (lg) {
12605             fseek(f, lg->offset, 0);
12606             GameListHighlight(gameNumber);
12607             pos = lg->position;
12608             gn = 1;
12609         }
12610         else {
12611             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12612               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12613             else
12614             DisplayError(_("Game number out of range"), 0);
12615             return FALSE;
12616         }
12617     } else {
12618         GameListDestroy();
12619         if (fseek(f, 0, 0) == -1) {
12620             if (f == lastLoadGameFP ?
12621                 gameNumber == lastLoadGameNumber + 1 :
12622                 gameNumber == 1) {
12623                 gn = 1;
12624             } else {
12625                 DisplayError(_("Can't seek on game file"), 0);
12626                 return FALSE;
12627             }
12628         }
12629     }
12630     lastLoadGameFP = f;
12631     lastLoadGameNumber = gameNumber;
12632     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12633     lastLoadGameUseList = useList;
12634
12635     yynewfile(f);
12636
12637     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12638       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12639                 lg->gameInfo.black);
12640             DisplayTitle(buf);
12641     } else if (*title != NULLCHAR) {
12642         if (gameNumber > 1) {
12643           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12644             DisplayTitle(buf);
12645         } else {
12646             DisplayTitle(title);
12647         }
12648     }
12649
12650     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12651         gameMode = PlayFromGameFile;
12652         ModeHighlight();
12653     }
12654
12655     currentMove = forwardMostMove = backwardMostMove = 0;
12656     CopyBoard(boards[0], initialPosition);
12657     StopClocks();
12658
12659     /*
12660      * Skip the first gn-1 games in the file.
12661      * Also skip over anything that precedes an identifiable
12662      * start of game marker, to avoid being confused by
12663      * garbage at the start of the file.  Currently
12664      * recognized start of game markers are the move number "1",
12665      * the pattern "gnuchess .* game", the pattern
12666      * "^[#;%] [^ ]* game file", and a PGN tag block.
12667      * A game that starts with one of the latter two patterns
12668      * will also have a move number 1, possibly
12669      * following a position diagram.
12670      * 5-4-02: Let's try being more lenient and allowing a game to
12671      * start with an unnumbered move.  Does that break anything?
12672      */
12673     cm = lastLoadGameStart = EndOfFile;
12674     while (gn > 0) {
12675         yyboardindex = forwardMostMove;
12676         cm = (ChessMove) Myylex();
12677         switch (cm) {
12678           case EndOfFile:
12679             if (cmailMsgLoaded) {
12680                 nCmailGames = CMAIL_MAX_GAMES - gn;
12681             } else {
12682                 Reset(TRUE, TRUE);
12683                 DisplayError(_("Game not found in file"), 0);
12684             }
12685             return FALSE;
12686
12687           case GNUChessGame:
12688           case XBoardGame:
12689             gn--;
12690             lastLoadGameStart = cm;
12691             break;
12692
12693           case MoveNumberOne:
12694             switch (lastLoadGameStart) {
12695               case GNUChessGame:
12696               case XBoardGame:
12697               case PGNTag:
12698                 break;
12699               case MoveNumberOne:
12700               case EndOfFile:
12701                 gn--;           /* count this game */
12702                 lastLoadGameStart = cm;
12703                 break;
12704               default:
12705                 /* impossible */
12706                 break;
12707             }
12708             break;
12709
12710           case PGNTag:
12711             switch (lastLoadGameStart) {
12712               case GNUChessGame:
12713               case PGNTag:
12714               case MoveNumberOne:
12715               case EndOfFile:
12716                 gn--;           /* count this game */
12717                 lastLoadGameStart = cm;
12718                 break;
12719               case XBoardGame:
12720                 lastLoadGameStart = cm; /* game counted already */
12721                 break;
12722               default:
12723                 /* impossible */
12724                 break;
12725             }
12726             if (gn > 0) {
12727                 do {
12728                     yyboardindex = forwardMostMove;
12729                     cm = (ChessMove) Myylex();
12730                 } while (cm == PGNTag || cm == Comment);
12731             }
12732             break;
12733
12734           case WhiteWins:
12735           case BlackWins:
12736           case GameIsDrawn:
12737             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12738                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12739                     != CMAIL_OLD_RESULT) {
12740                     nCmailResults ++ ;
12741                     cmailResult[  CMAIL_MAX_GAMES
12742                                 - gn - 1] = CMAIL_OLD_RESULT;
12743                 }
12744             }
12745             break;
12746
12747           case NormalMove:
12748           case FirstLeg:
12749             /* Only a NormalMove can be at the start of a game
12750              * without a position diagram. */
12751             if (lastLoadGameStart == EndOfFile ) {
12752               gn--;
12753               lastLoadGameStart = MoveNumberOne;
12754             }
12755             break;
12756
12757           default:
12758             break;
12759         }
12760     }
12761
12762     if (appData.debugMode)
12763       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12764
12765     if (cm == XBoardGame) {
12766         /* Skip any header junk before position diagram and/or move 1 */
12767         for (;;) {
12768             yyboardindex = forwardMostMove;
12769             cm = (ChessMove) Myylex();
12770
12771             if (cm == EndOfFile ||
12772                 cm == GNUChessGame || cm == XBoardGame) {
12773                 /* Empty game; pretend end-of-file and handle later */
12774                 cm = EndOfFile;
12775                 break;
12776             }
12777
12778             if (cm == MoveNumberOne || cm == PositionDiagram ||
12779                 cm == PGNTag || cm == Comment)
12780               break;
12781         }
12782     } else if (cm == GNUChessGame) {
12783         if (gameInfo.event != NULL) {
12784             free(gameInfo.event);
12785         }
12786         gameInfo.event = StrSave(yy_text);
12787     }
12788
12789     startedFromSetupPosition = FALSE;
12790     while (cm == PGNTag) {
12791         if (appData.debugMode)
12792           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12793         err = ParsePGNTag(yy_text, &gameInfo);
12794         if (!err) numPGNTags++;
12795
12796         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12797         if(gameInfo.variant != oldVariant) {
12798             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12799             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12800             InitPosition(TRUE);
12801             oldVariant = gameInfo.variant;
12802             if (appData.debugMode)
12803               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12804         }
12805
12806
12807         if (gameInfo.fen != NULL) {
12808           Board initial_position;
12809           startedFromSetupPosition = TRUE;
12810           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12811             Reset(TRUE, TRUE);
12812             DisplayError(_("Bad FEN position in file"), 0);
12813             return FALSE;
12814           }
12815           CopyBoard(boards[0], initial_position);
12816           if (blackPlaysFirst) {
12817             currentMove = forwardMostMove = backwardMostMove = 1;
12818             CopyBoard(boards[1], initial_position);
12819             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12820             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12821             timeRemaining[0][1] = whiteTimeRemaining;
12822             timeRemaining[1][1] = blackTimeRemaining;
12823             if (commentList[0] != NULL) {
12824               commentList[1] = commentList[0];
12825               commentList[0] = NULL;
12826             }
12827           } else {
12828             currentMove = forwardMostMove = backwardMostMove = 0;
12829           }
12830           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12831           {   int i;
12832               initialRulePlies = FENrulePlies;
12833               for( i=0; i< nrCastlingRights; i++ )
12834                   initialRights[i] = initial_position[CASTLING][i];
12835           }
12836           yyboardindex = forwardMostMove;
12837           free(gameInfo.fen);
12838           gameInfo.fen = NULL;
12839         }
12840
12841         yyboardindex = forwardMostMove;
12842         cm = (ChessMove) Myylex();
12843
12844         /* Handle comments interspersed among the tags */
12845         while (cm == Comment) {
12846             char *p;
12847             if (appData.debugMode)
12848               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12849             p = yy_text;
12850             AppendComment(currentMove, p, FALSE);
12851             yyboardindex = forwardMostMove;
12852             cm = (ChessMove) Myylex();
12853         }
12854     }
12855
12856     /* don't rely on existence of Event tag since if game was
12857      * pasted from clipboard the Event tag may not exist
12858      */
12859     if (numPGNTags > 0){
12860         char *tags;
12861         if (gameInfo.variant == VariantNormal) {
12862           VariantClass v = StringToVariant(gameInfo.event);
12863           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12864           if(v < VariantShogi) gameInfo.variant = v;
12865         }
12866         if (!matchMode) {
12867           if( appData.autoDisplayTags ) {
12868             tags = PGNTags(&gameInfo);
12869             TagsPopUp(tags, CmailMsg());
12870             free(tags);
12871           }
12872         }
12873     } else {
12874         /* Make something up, but don't display it now */
12875         SetGameInfo();
12876         TagsPopDown();
12877     }
12878
12879     if (cm == PositionDiagram) {
12880         int i, j;
12881         char *p;
12882         Board initial_position;
12883
12884         if (appData.debugMode)
12885           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12886
12887         if (!startedFromSetupPosition) {
12888             p = yy_text;
12889             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12890               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12891                 switch (*p) {
12892                   case '{':
12893                   case '[':
12894                   case '-':
12895                   case ' ':
12896                   case '\t':
12897                   case '\n':
12898                   case '\r':
12899                     break;
12900                   default:
12901                     initial_position[i][j++] = CharToPiece(*p);
12902                     break;
12903                 }
12904             while (*p == ' ' || *p == '\t' ||
12905                    *p == '\n' || *p == '\r') p++;
12906
12907             if (strncmp(p, "black", strlen("black"))==0)
12908               blackPlaysFirst = TRUE;
12909             else
12910               blackPlaysFirst = FALSE;
12911             startedFromSetupPosition = TRUE;
12912
12913             CopyBoard(boards[0], initial_position);
12914             if (blackPlaysFirst) {
12915                 currentMove = forwardMostMove = backwardMostMove = 1;
12916                 CopyBoard(boards[1], initial_position);
12917                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12918                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12919                 timeRemaining[0][1] = whiteTimeRemaining;
12920                 timeRemaining[1][1] = blackTimeRemaining;
12921                 if (commentList[0] != NULL) {
12922                     commentList[1] = commentList[0];
12923                     commentList[0] = NULL;
12924                 }
12925             } else {
12926                 currentMove = forwardMostMove = backwardMostMove = 0;
12927             }
12928         }
12929         yyboardindex = forwardMostMove;
12930         cm = (ChessMove) Myylex();
12931     }
12932
12933   if(!creatingBook) {
12934     if (first.pr == NoProc) {
12935         StartChessProgram(&first);
12936     }
12937     InitChessProgram(&first, FALSE);
12938     SendToProgram("force\n", &first);
12939     if (startedFromSetupPosition) {
12940         SendBoard(&first, forwardMostMove);
12941     if (appData.debugMode) {
12942         fprintf(debugFP, "Load Game\n");
12943     }
12944         DisplayBothClocks();
12945     }
12946   }
12947
12948     /* [HGM] server: flag to write setup moves in broadcast file as one */
12949     loadFlag = appData.suppressLoadMoves;
12950
12951     while (cm == Comment) {
12952         char *p;
12953         if (appData.debugMode)
12954           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12955         p = yy_text;
12956         AppendComment(currentMove, p, FALSE);
12957         yyboardindex = forwardMostMove;
12958         cm = (ChessMove) Myylex();
12959     }
12960
12961     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12962         cm == WhiteWins || cm == BlackWins ||
12963         cm == GameIsDrawn || cm == GameUnfinished) {
12964         DisplayMessage("", _("No moves in game"));
12965         if (cmailMsgLoaded) {
12966             if (appData.debugMode)
12967               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12968             ClearHighlights();
12969             flipView = FALSE;
12970         }
12971         DrawPosition(FALSE, boards[currentMove]);
12972         DisplayBothClocks();
12973         gameMode = EditGame;
12974         ModeHighlight();
12975         gameFileFP = NULL;
12976         cmailOldMove = 0;
12977         return TRUE;
12978     }
12979
12980     // [HGM] PV info: routine tests if comment empty
12981     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12982         DisplayComment(currentMove - 1, commentList[currentMove]);
12983     }
12984     if (!matchMode && appData.timeDelay != 0)
12985       DrawPosition(FALSE, boards[currentMove]);
12986
12987     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12988       programStats.ok_to_send = 1;
12989     }
12990
12991     /* if the first token after the PGN tags is a move
12992      * and not move number 1, retrieve it from the parser
12993      */
12994     if (cm != MoveNumberOne)
12995         LoadGameOneMove(cm);
12996
12997     /* load the remaining moves from the file */
12998     while (LoadGameOneMove(EndOfFile)) {
12999       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13000       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13001     }
13002
13003     /* rewind to the start of the game */
13004     currentMove = backwardMostMove;
13005
13006     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13007
13008     if (oldGameMode == AnalyzeFile) {
13009       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13010       AnalyzeFileEvent();
13011     } else
13012     if (oldGameMode == AnalyzeMode) {
13013       AnalyzeFileEvent();
13014     }
13015
13016     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13017         long int w, b; // [HGM] adjourn: restore saved clock times
13018         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13019         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13020             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13021             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13022         }
13023     }
13024
13025     if(creatingBook) return TRUE;
13026     if (!matchMode && pos > 0) {
13027         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13028     } else
13029     if (matchMode || appData.timeDelay == 0) {
13030       ToEndEvent();
13031     } else if (appData.timeDelay > 0) {
13032       AutoPlayGameLoop();
13033     }
13034
13035     if (appData.debugMode)
13036         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13037
13038     loadFlag = 0; /* [HGM] true game starts */
13039     return TRUE;
13040 }
13041
13042 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13043 int
13044 ReloadPosition (int offset)
13045 {
13046     int positionNumber = lastLoadPositionNumber + offset;
13047     if (lastLoadPositionFP == NULL) {
13048         DisplayError(_("No position has been loaded yet"), 0);
13049         return FALSE;
13050     }
13051     if (positionNumber <= 0) {
13052         DisplayError(_("Can't back up any further"), 0);
13053         return FALSE;
13054     }
13055     return LoadPosition(lastLoadPositionFP, positionNumber,
13056                         lastLoadPositionTitle);
13057 }
13058
13059 /* Load the nth position from the given file */
13060 int
13061 LoadPositionFromFile (char *filename, int n, char *title)
13062 {
13063     FILE *f;
13064     char buf[MSG_SIZ];
13065
13066     if (strcmp(filename, "-") == 0) {
13067         return LoadPosition(stdin, n, "stdin");
13068     } else {
13069         f = fopen(filename, "rb");
13070         if (f == NULL) {
13071             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13072             DisplayError(buf, errno);
13073             return FALSE;
13074         } else {
13075             return LoadPosition(f, n, title);
13076         }
13077     }
13078 }
13079
13080 /* Load the nth position from the given open file, and close it */
13081 int
13082 LoadPosition (FILE *f, int positionNumber, char *title)
13083 {
13084     char *p, line[MSG_SIZ];
13085     Board initial_position;
13086     int i, j, fenMode, pn;
13087
13088     if (gameMode == Training )
13089         SetTrainingModeOff();
13090
13091     if (gameMode != BeginningOfGame) {
13092         Reset(FALSE, TRUE);
13093     }
13094     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13095         fclose(lastLoadPositionFP);
13096     }
13097     if (positionNumber == 0) positionNumber = 1;
13098     lastLoadPositionFP = f;
13099     lastLoadPositionNumber = positionNumber;
13100     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13101     if (first.pr == NoProc && !appData.noChessProgram) {
13102       StartChessProgram(&first);
13103       InitChessProgram(&first, FALSE);
13104     }
13105     pn = positionNumber;
13106     if (positionNumber < 0) {
13107         /* Negative position number means to seek to that byte offset */
13108         if (fseek(f, -positionNumber, 0) == -1) {
13109             DisplayError(_("Can't seek on position file"), 0);
13110             return FALSE;
13111         };
13112         pn = 1;
13113     } else {
13114         if (fseek(f, 0, 0) == -1) {
13115             if (f == lastLoadPositionFP ?
13116                 positionNumber == lastLoadPositionNumber + 1 :
13117                 positionNumber == 1) {
13118                 pn = 1;
13119             } else {
13120                 DisplayError(_("Can't seek on position file"), 0);
13121                 return FALSE;
13122             }
13123         }
13124     }
13125     /* See if this file is FEN or old-style xboard */
13126     if (fgets(line, MSG_SIZ, f) == NULL) {
13127         DisplayError(_("Position not found in file"), 0);
13128         return FALSE;
13129     }
13130     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13131     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13132
13133     if (pn >= 2) {
13134         if (fenMode || line[0] == '#') pn--;
13135         while (pn > 0) {
13136             /* skip positions before number pn */
13137             if (fgets(line, MSG_SIZ, f) == NULL) {
13138                 Reset(TRUE, TRUE);
13139                 DisplayError(_("Position not found in file"), 0);
13140                 return FALSE;
13141             }
13142             if (fenMode || line[0] == '#') pn--;
13143         }
13144     }
13145
13146     if (fenMode) {
13147         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13148             DisplayError(_("Bad FEN position in file"), 0);
13149             return FALSE;
13150         }
13151     } else {
13152         (void) fgets(line, MSG_SIZ, f);
13153         (void) fgets(line, MSG_SIZ, f);
13154
13155         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13156             (void) fgets(line, MSG_SIZ, f);
13157             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13158                 if (*p == ' ')
13159                   continue;
13160                 initial_position[i][j++] = CharToPiece(*p);
13161             }
13162         }
13163
13164         blackPlaysFirst = FALSE;
13165         if (!feof(f)) {
13166             (void) fgets(line, MSG_SIZ, f);
13167             if (strncmp(line, "black", strlen("black"))==0)
13168               blackPlaysFirst = TRUE;
13169         }
13170     }
13171     startedFromSetupPosition = TRUE;
13172
13173     CopyBoard(boards[0], initial_position);
13174     if (blackPlaysFirst) {
13175         currentMove = forwardMostMove = backwardMostMove = 1;
13176         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13177         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13178         CopyBoard(boards[1], initial_position);
13179         DisplayMessage("", _("Black to play"));
13180     } else {
13181         currentMove = forwardMostMove = backwardMostMove = 0;
13182         DisplayMessage("", _("White to play"));
13183     }
13184     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13185     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13186         SendToProgram("force\n", &first);
13187         SendBoard(&first, forwardMostMove);
13188     }
13189     if (appData.debugMode) {
13190 int i, j;
13191   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13192   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13193         fprintf(debugFP, "Load Position\n");
13194     }
13195
13196     if (positionNumber > 1) {
13197       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13198         DisplayTitle(line);
13199     } else {
13200         DisplayTitle(title);
13201     }
13202     gameMode = EditGame;
13203     ModeHighlight();
13204     ResetClocks();
13205     timeRemaining[0][1] = whiteTimeRemaining;
13206     timeRemaining[1][1] = blackTimeRemaining;
13207     DrawPosition(FALSE, boards[currentMove]);
13208
13209     return TRUE;
13210 }
13211
13212
13213 void
13214 CopyPlayerNameIntoFileName (char **dest, char *src)
13215 {
13216     while (*src != NULLCHAR && *src != ',') {
13217         if (*src == ' ') {
13218             *(*dest)++ = '_';
13219             src++;
13220         } else {
13221             *(*dest)++ = *src++;
13222         }
13223     }
13224 }
13225
13226 char *
13227 DefaultFileName (char *ext)
13228 {
13229     static char def[MSG_SIZ];
13230     char *p;
13231
13232     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13233         p = def;
13234         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13235         *p++ = '-';
13236         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13237         *p++ = '.';
13238         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13239     } else {
13240         def[0] = NULLCHAR;
13241     }
13242     return def;
13243 }
13244
13245 /* Save the current game to the given file */
13246 int
13247 SaveGameToFile (char *filename, int append)
13248 {
13249     FILE *f;
13250     char buf[MSG_SIZ];
13251     int result, i, t,tot=0;
13252
13253     if (strcmp(filename, "-") == 0) {
13254         return SaveGame(stdout, 0, NULL);
13255     } else {
13256         for(i=0; i<10; i++) { // upto 10 tries
13257              f = fopen(filename, append ? "a" : "w");
13258              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13259              if(f || errno != 13) break;
13260              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13261              tot += t;
13262         }
13263         if (f == NULL) {
13264             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13265             DisplayError(buf, errno);
13266             return FALSE;
13267         } else {
13268             safeStrCpy(buf, lastMsg, MSG_SIZ);
13269             DisplayMessage(_("Waiting for access to save file"), "");
13270             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13271             DisplayMessage(_("Saving game"), "");
13272             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13273             result = SaveGame(f, 0, NULL);
13274             DisplayMessage(buf, "");
13275             return result;
13276         }
13277     }
13278 }
13279
13280 char *
13281 SavePart (char *str)
13282 {
13283     static char buf[MSG_SIZ];
13284     char *p;
13285
13286     p = strchr(str, ' ');
13287     if (p == NULL) return str;
13288     strncpy(buf, str, p - str);
13289     buf[p - str] = NULLCHAR;
13290     return buf;
13291 }
13292
13293 #define PGN_MAX_LINE 75
13294
13295 #define PGN_SIDE_WHITE  0
13296 #define PGN_SIDE_BLACK  1
13297
13298 static int
13299 FindFirstMoveOutOfBook (int side)
13300 {
13301     int result = -1;
13302
13303     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13304         int index = backwardMostMove;
13305         int has_book_hit = 0;
13306
13307         if( (index % 2) != side ) {
13308             index++;
13309         }
13310
13311         while( index < forwardMostMove ) {
13312             /* Check to see if engine is in book */
13313             int depth = pvInfoList[index].depth;
13314             int score = pvInfoList[index].score;
13315             int in_book = 0;
13316
13317             if( depth <= 2 ) {
13318                 in_book = 1;
13319             }
13320             else if( score == 0 && depth == 63 ) {
13321                 in_book = 1; /* Zappa */
13322             }
13323             else if( score == 2 && depth == 99 ) {
13324                 in_book = 1; /* Abrok */
13325             }
13326
13327             has_book_hit += in_book;
13328
13329             if( ! in_book ) {
13330                 result = index;
13331
13332                 break;
13333             }
13334
13335             index += 2;
13336         }
13337     }
13338
13339     return result;
13340 }
13341
13342 void
13343 GetOutOfBookInfo (char * buf)
13344 {
13345     int oob[2];
13346     int i;
13347     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13348
13349     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13350     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13351
13352     *buf = '\0';
13353
13354     if( oob[0] >= 0 || oob[1] >= 0 ) {
13355         for( i=0; i<2; i++ ) {
13356             int idx = oob[i];
13357
13358             if( idx >= 0 ) {
13359                 if( i > 0 && oob[0] >= 0 ) {
13360                     strcat( buf, "   " );
13361                 }
13362
13363                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13364                 sprintf( buf+strlen(buf), "%s%.2f",
13365                     pvInfoList[idx].score >= 0 ? "+" : "",
13366                     pvInfoList[idx].score / 100.0 );
13367             }
13368         }
13369     }
13370 }
13371
13372 /* Save game in PGN style */
13373 static void
13374 SaveGamePGN2 (FILE *f)
13375 {
13376     int i, offset, linelen, newblock;
13377 //    char *movetext;
13378     char numtext[32];
13379     int movelen, numlen, blank;
13380     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13381
13382     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13383
13384     PrintPGNTags(f, &gameInfo);
13385
13386     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13387
13388     if (backwardMostMove > 0 || startedFromSetupPosition) {
13389         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13390         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13391         fprintf(f, "\n{--------------\n");
13392         PrintPosition(f, backwardMostMove);
13393         fprintf(f, "--------------}\n");
13394         free(fen);
13395     }
13396     else {
13397         /* [AS] Out of book annotation */
13398         if( appData.saveOutOfBookInfo ) {
13399             char buf[64];
13400
13401             GetOutOfBookInfo( buf );
13402
13403             if( buf[0] != '\0' ) {
13404                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13405             }
13406         }
13407
13408         fprintf(f, "\n");
13409     }
13410
13411     i = backwardMostMove;
13412     linelen = 0;
13413     newblock = TRUE;
13414
13415     while (i < forwardMostMove) {
13416         /* Print comments preceding this move */
13417         if (commentList[i] != NULL) {
13418             if (linelen > 0) fprintf(f, "\n");
13419             fprintf(f, "%s", commentList[i]);
13420             linelen = 0;
13421             newblock = TRUE;
13422         }
13423
13424         /* Format move number */
13425         if ((i % 2) == 0)
13426           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13427         else
13428           if (newblock)
13429             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13430           else
13431             numtext[0] = NULLCHAR;
13432
13433         numlen = strlen(numtext);
13434         newblock = FALSE;
13435
13436         /* Print move number */
13437         blank = linelen > 0 && numlen > 0;
13438         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13439             fprintf(f, "\n");
13440             linelen = 0;
13441             blank = 0;
13442         }
13443         if (blank) {
13444             fprintf(f, " ");
13445             linelen++;
13446         }
13447         fprintf(f, "%s", numtext);
13448         linelen += numlen;
13449
13450         /* Get move */
13451         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13452         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13453
13454         /* Print move */
13455         blank = linelen > 0 && movelen > 0;
13456         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13457             fprintf(f, "\n");
13458             linelen = 0;
13459             blank = 0;
13460         }
13461         if (blank) {
13462             fprintf(f, " ");
13463             linelen++;
13464         }
13465         fprintf(f, "%s", move_buffer);
13466         linelen += movelen;
13467
13468         /* [AS] Add PV info if present */
13469         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13470             /* [HGM] add time */
13471             char buf[MSG_SIZ]; int seconds;
13472
13473             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13474
13475             if( seconds <= 0)
13476               buf[0] = 0;
13477             else
13478               if( seconds < 30 )
13479                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13480               else
13481                 {
13482                   seconds = (seconds + 4)/10; // round to full seconds
13483                   if( seconds < 60 )
13484                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13485                   else
13486                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13487                 }
13488
13489             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13490                       pvInfoList[i].score >= 0 ? "+" : "",
13491                       pvInfoList[i].score / 100.0,
13492                       pvInfoList[i].depth,
13493                       buf );
13494
13495             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13496
13497             /* Print score/depth */
13498             blank = linelen > 0 && movelen > 0;
13499             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13500                 fprintf(f, "\n");
13501                 linelen = 0;
13502                 blank = 0;
13503             }
13504             if (blank) {
13505                 fprintf(f, " ");
13506                 linelen++;
13507             }
13508             fprintf(f, "%s", move_buffer);
13509             linelen += movelen;
13510         }
13511
13512         i++;
13513     }
13514
13515     /* Start a new line */
13516     if (linelen > 0) fprintf(f, "\n");
13517
13518     /* Print comments after last move */
13519     if (commentList[i] != NULL) {
13520         fprintf(f, "%s\n", commentList[i]);
13521     }
13522
13523     /* Print result */
13524     if (gameInfo.resultDetails != NULL &&
13525         gameInfo.resultDetails[0] != NULLCHAR) {
13526         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13527         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13528            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13529             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13530         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13531     } else {
13532         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13533     }
13534 }
13535
13536 /* Save game in PGN style and close the file */
13537 int
13538 SaveGamePGN (FILE *f)
13539 {
13540     SaveGamePGN2(f);
13541     fclose(f);
13542     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13543     return TRUE;
13544 }
13545
13546 /* Save game in old style and close the file */
13547 int
13548 SaveGameOldStyle (FILE *f)
13549 {
13550     int i, offset;
13551     time_t tm;
13552
13553     tm = time((time_t *) NULL);
13554
13555     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13556     PrintOpponents(f);
13557
13558     if (backwardMostMove > 0 || startedFromSetupPosition) {
13559         fprintf(f, "\n[--------------\n");
13560         PrintPosition(f, backwardMostMove);
13561         fprintf(f, "--------------]\n");
13562     } else {
13563         fprintf(f, "\n");
13564     }
13565
13566     i = backwardMostMove;
13567     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13568
13569     while (i < forwardMostMove) {
13570         if (commentList[i] != NULL) {
13571             fprintf(f, "[%s]\n", commentList[i]);
13572         }
13573
13574         if ((i % 2) == 1) {
13575             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13576             i++;
13577         } else {
13578             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13579             i++;
13580             if (commentList[i] != NULL) {
13581                 fprintf(f, "\n");
13582                 continue;
13583             }
13584             if (i >= forwardMostMove) {
13585                 fprintf(f, "\n");
13586                 break;
13587             }
13588             fprintf(f, "%s\n", parseList[i]);
13589             i++;
13590         }
13591     }
13592
13593     if (commentList[i] != NULL) {
13594         fprintf(f, "[%s]\n", commentList[i]);
13595     }
13596
13597     /* This isn't really the old style, but it's close enough */
13598     if (gameInfo.resultDetails != NULL &&
13599         gameInfo.resultDetails[0] != NULLCHAR) {
13600         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13601                 gameInfo.resultDetails);
13602     } else {
13603         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13604     }
13605
13606     fclose(f);
13607     return TRUE;
13608 }
13609
13610 /* Save the current game to open file f and close the file */
13611 int
13612 SaveGame (FILE *f, int dummy, char *dummy2)
13613 {
13614     if (gameMode == EditPosition) EditPositionDone(TRUE);
13615     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13616     if (appData.oldSaveStyle)
13617       return SaveGameOldStyle(f);
13618     else
13619       return SaveGamePGN(f);
13620 }
13621
13622 /* Save the current position to the given file */
13623 int
13624 SavePositionToFile (char *filename)
13625 {
13626     FILE *f;
13627     char buf[MSG_SIZ];
13628
13629     if (strcmp(filename, "-") == 0) {
13630         return SavePosition(stdout, 0, NULL);
13631     } else {
13632         f = fopen(filename, "a");
13633         if (f == NULL) {
13634             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13635             DisplayError(buf, errno);
13636             return FALSE;
13637         } else {
13638             safeStrCpy(buf, lastMsg, MSG_SIZ);
13639             DisplayMessage(_("Waiting for access to save file"), "");
13640             flock(fileno(f), LOCK_EX); // [HGM] lock
13641             DisplayMessage(_("Saving position"), "");
13642             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13643             SavePosition(f, 0, NULL);
13644             DisplayMessage(buf, "");
13645             return TRUE;
13646         }
13647     }
13648 }
13649
13650 /* Save the current position to the given open file and close the file */
13651 int
13652 SavePosition (FILE *f, int dummy, char *dummy2)
13653 {
13654     time_t tm;
13655     char *fen;
13656
13657     if (gameMode == EditPosition) EditPositionDone(TRUE);
13658     if (appData.oldSaveStyle) {
13659         tm = time((time_t *) NULL);
13660
13661         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13662         PrintOpponents(f);
13663         fprintf(f, "[--------------\n");
13664         PrintPosition(f, currentMove);
13665         fprintf(f, "--------------]\n");
13666     } else {
13667         fen = PositionToFEN(currentMove, NULL, 1);
13668         fprintf(f, "%s\n", fen);
13669         free(fen);
13670     }
13671     fclose(f);
13672     return TRUE;
13673 }
13674
13675 void
13676 ReloadCmailMsgEvent (int unregister)
13677 {
13678 #if !WIN32
13679     static char *inFilename = NULL;
13680     static char *outFilename;
13681     int i;
13682     struct stat inbuf, outbuf;
13683     int status;
13684
13685     /* Any registered moves are unregistered if unregister is set, */
13686     /* i.e. invoked by the signal handler */
13687     if (unregister) {
13688         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13689             cmailMoveRegistered[i] = FALSE;
13690             if (cmailCommentList[i] != NULL) {
13691                 free(cmailCommentList[i]);
13692                 cmailCommentList[i] = NULL;
13693             }
13694         }
13695         nCmailMovesRegistered = 0;
13696     }
13697
13698     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13699         cmailResult[i] = CMAIL_NOT_RESULT;
13700     }
13701     nCmailResults = 0;
13702
13703     if (inFilename == NULL) {
13704         /* Because the filenames are static they only get malloced once  */
13705         /* and they never get freed                                      */
13706         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13707         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13708
13709         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13710         sprintf(outFilename, "%s.out", appData.cmailGameName);
13711     }
13712
13713     status = stat(outFilename, &outbuf);
13714     if (status < 0) {
13715         cmailMailedMove = FALSE;
13716     } else {
13717         status = stat(inFilename, &inbuf);
13718         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13719     }
13720
13721     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13722        counts the games, notes how each one terminated, etc.
13723
13724        It would be nice to remove this kludge and instead gather all
13725        the information while building the game list.  (And to keep it
13726        in the game list nodes instead of having a bunch of fixed-size
13727        parallel arrays.)  Note this will require getting each game's
13728        termination from the PGN tags, as the game list builder does
13729        not process the game moves.  --mann
13730        */
13731     cmailMsgLoaded = TRUE;
13732     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13733
13734     /* Load first game in the file or popup game menu */
13735     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13736
13737 #endif /* !WIN32 */
13738     return;
13739 }
13740
13741 int
13742 RegisterMove ()
13743 {
13744     FILE *f;
13745     char string[MSG_SIZ];
13746
13747     if (   cmailMailedMove
13748         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13749         return TRUE;            /* Allow free viewing  */
13750     }
13751
13752     /* Unregister move to ensure that we don't leave RegisterMove        */
13753     /* with the move registered when the conditions for registering no   */
13754     /* longer hold                                                       */
13755     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13756         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13757         nCmailMovesRegistered --;
13758
13759         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13760           {
13761               free(cmailCommentList[lastLoadGameNumber - 1]);
13762               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13763           }
13764     }
13765
13766     if (cmailOldMove == -1) {
13767         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13768         return FALSE;
13769     }
13770
13771     if (currentMove > cmailOldMove + 1) {
13772         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13773         return FALSE;
13774     }
13775
13776     if (currentMove < cmailOldMove) {
13777         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13778         return FALSE;
13779     }
13780
13781     if (forwardMostMove > currentMove) {
13782         /* Silently truncate extra moves */
13783         TruncateGame();
13784     }
13785
13786     if (   (currentMove == cmailOldMove + 1)
13787         || (   (currentMove == cmailOldMove)
13788             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13789                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13790         if (gameInfo.result != GameUnfinished) {
13791             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13792         }
13793
13794         if (commentList[currentMove] != NULL) {
13795             cmailCommentList[lastLoadGameNumber - 1]
13796               = StrSave(commentList[currentMove]);
13797         }
13798         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13799
13800         if (appData.debugMode)
13801           fprintf(debugFP, "Saving %s for game %d\n",
13802                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13803
13804         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13805
13806         f = fopen(string, "w");
13807         if (appData.oldSaveStyle) {
13808             SaveGameOldStyle(f); /* also closes the file */
13809
13810             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13811             f = fopen(string, "w");
13812             SavePosition(f, 0, NULL); /* also closes the file */
13813         } else {
13814             fprintf(f, "{--------------\n");
13815             PrintPosition(f, currentMove);
13816             fprintf(f, "--------------}\n\n");
13817
13818             SaveGame(f, 0, NULL); /* also closes the file*/
13819         }
13820
13821         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13822         nCmailMovesRegistered ++;
13823     } else if (nCmailGames == 1) {
13824         DisplayError(_("You have not made a move yet"), 0);
13825         return FALSE;
13826     }
13827
13828     return TRUE;
13829 }
13830
13831 void
13832 MailMoveEvent ()
13833 {
13834 #if !WIN32
13835     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13836     FILE *commandOutput;
13837     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13838     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13839     int nBuffers;
13840     int i;
13841     int archived;
13842     char *arcDir;
13843
13844     if (! cmailMsgLoaded) {
13845         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13846         return;
13847     }
13848
13849     if (nCmailGames == nCmailResults) {
13850         DisplayError(_("No unfinished games"), 0);
13851         return;
13852     }
13853
13854 #if CMAIL_PROHIBIT_REMAIL
13855     if (cmailMailedMove) {
13856       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);
13857         DisplayError(msg, 0);
13858         return;
13859     }
13860 #endif
13861
13862     if (! (cmailMailedMove || RegisterMove())) return;
13863
13864     if (   cmailMailedMove
13865         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13866       snprintf(string, MSG_SIZ, partCommandString,
13867                appData.debugMode ? " -v" : "", appData.cmailGameName);
13868         commandOutput = popen(string, "r");
13869
13870         if (commandOutput == NULL) {
13871             DisplayError(_("Failed to invoke cmail"), 0);
13872         } else {
13873             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13874                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13875             }
13876             if (nBuffers > 1) {
13877                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13878                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13879                 nBytes = MSG_SIZ - 1;
13880             } else {
13881                 (void) memcpy(msg, buffer, nBytes);
13882             }
13883             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13884
13885             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13886                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13887
13888                 archived = TRUE;
13889                 for (i = 0; i < nCmailGames; i ++) {
13890                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13891                         archived = FALSE;
13892                     }
13893                 }
13894                 if (   archived
13895                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13896                         != NULL)) {
13897                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13898                            arcDir,
13899                            appData.cmailGameName,
13900                            gameInfo.date);
13901                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13902                     cmailMsgLoaded = FALSE;
13903                 }
13904             }
13905
13906             DisplayInformation(msg);
13907             pclose(commandOutput);
13908         }
13909     } else {
13910         if ((*cmailMsg) != '\0') {
13911             DisplayInformation(cmailMsg);
13912         }
13913     }
13914
13915     return;
13916 #endif /* !WIN32 */
13917 }
13918
13919 char *
13920 CmailMsg ()
13921 {
13922 #if WIN32
13923     return NULL;
13924 #else
13925     int  prependComma = 0;
13926     char number[5];
13927     char string[MSG_SIZ];       /* Space for game-list */
13928     int  i;
13929
13930     if (!cmailMsgLoaded) return "";
13931
13932     if (cmailMailedMove) {
13933       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13934     } else {
13935         /* Create a list of games left */
13936       snprintf(string, MSG_SIZ, "[");
13937         for (i = 0; i < nCmailGames; i ++) {
13938             if (! (   cmailMoveRegistered[i]
13939                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13940                 if (prependComma) {
13941                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13942                 } else {
13943                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13944                     prependComma = 1;
13945                 }
13946
13947                 strcat(string, number);
13948             }
13949         }
13950         strcat(string, "]");
13951
13952         if (nCmailMovesRegistered + nCmailResults == 0) {
13953             switch (nCmailGames) {
13954               case 1:
13955                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13956                 break;
13957
13958               case 2:
13959                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13960                 break;
13961
13962               default:
13963                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13964                          nCmailGames);
13965                 break;
13966             }
13967         } else {
13968             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13969               case 1:
13970                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13971                          string);
13972                 break;
13973
13974               case 0:
13975                 if (nCmailResults == nCmailGames) {
13976                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13977                 } else {
13978                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13979                 }
13980                 break;
13981
13982               default:
13983                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13984                          string);
13985             }
13986         }
13987     }
13988     return cmailMsg;
13989 #endif /* WIN32 */
13990 }
13991
13992 void
13993 ResetGameEvent ()
13994 {
13995     if (gameMode == Training)
13996       SetTrainingModeOff();
13997
13998     Reset(TRUE, TRUE);
13999     cmailMsgLoaded = FALSE;
14000     if (appData.icsActive) {
14001       SendToICS(ics_prefix);
14002       SendToICS("refresh\n");
14003     }
14004 }
14005
14006 void
14007 ExitEvent (int status)
14008 {
14009     exiting++;
14010     if (exiting > 2) {
14011       /* Give up on clean exit */
14012       exit(status);
14013     }
14014     if (exiting > 1) {
14015       /* Keep trying for clean exit */
14016       return;
14017     }
14018
14019     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14020     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14021
14022     if (telnetISR != NULL) {
14023       RemoveInputSource(telnetISR);
14024     }
14025     if (icsPR != NoProc) {
14026       DestroyChildProcess(icsPR, TRUE);
14027     }
14028
14029     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14030     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14031
14032     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14033     /* make sure this other one finishes before killing it!                  */
14034     if(endingGame) { int count = 0;
14035         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14036         while(endingGame && count++ < 10) DoSleep(1);
14037         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14038     }
14039
14040     /* Kill off chess programs */
14041     if (first.pr != NoProc) {
14042         ExitAnalyzeMode();
14043
14044         DoSleep( appData.delayBeforeQuit );
14045         SendToProgram("quit\n", &first);
14046         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14047     }
14048     if (second.pr != NoProc) {
14049         DoSleep( appData.delayBeforeQuit );
14050         SendToProgram("quit\n", &second);
14051         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14052     }
14053     if (first.isr != NULL) {
14054         RemoveInputSource(first.isr);
14055     }
14056     if (second.isr != NULL) {
14057         RemoveInputSource(second.isr);
14058     }
14059
14060     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14061     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14062
14063     ShutDownFrontEnd();
14064     exit(status);
14065 }
14066
14067 void
14068 PauseEngine (ChessProgramState *cps)
14069 {
14070     SendToProgram("pause\n", cps);
14071     cps->pause = 2;
14072 }
14073
14074 void
14075 UnPauseEngine (ChessProgramState *cps)
14076 {
14077     SendToProgram("resume\n", cps);
14078     cps->pause = 1;
14079 }
14080
14081 void
14082 PauseEvent ()
14083 {
14084     if (appData.debugMode)
14085         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14086     if (pausing) {
14087         pausing = FALSE;
14088         ModeHighlight();
14089         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14090             StartClocks();
14091             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14092                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14093                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14094             }
14095             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14096             HandleMachineMove(stashedInputMove, stalledEngine);
14097             stalledEngine = NULL;
14098             return;
14099         }
14100         if (gameMode == MachinePlaysWhite ||
14101             gameMode == TwoMachinesPlay   ||
14102             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14103             if(first.pause)  UnPauseEngine(&first);
14104             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14105             if(second.pause) UnPauseEngine(&second);
14106             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14107             StartClocks();
14108         } else {
14109             DisplayBothClocks();
14110         }
14111         if (gameMode == PlayFromGameFile) {
14112             if (appData.timeDelay >= 0)
14113                 AutoPlayGameLoop();
14114         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14115             Reset(FALSE, TRUE);
14116             SendToICS(ics_prefix);
14117             SendToICS("refresh\n");
14118         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14119             ForwardInner(forwardMostMove);
14120         }
14121         pauseExamInvalid = FALSE;
14122     } else {
14123         switch (gameMode) {
14124           default:
14125             return;
14126           case IcsExamining:
14127             pauseExamForwardMostMove = forwardMostMove;
14128             pauseExamInvalid = FALSE;
14129             /* fall through */
14130           case IcsObserving:
14131           case IcsPlayingWhite:
14132           case IcsPlayingBlack:
14133             pausing = TRUE;
14134             ModeHighlight();
14135             return;
14136           case PlayFromGameFile:
14137             (void) StopLoadGameTimer();
14138             pausing = TRUE;
14139             ModeHighlight();
14140             break;
14141           case BeginningOfGame:
14142             if (appData.icsActive) return;
14143             /* else fall through */
14144           case MachinePlaysWhite:
14145           case MachinePlaysBlack:
14146           case TwoMachinesPlay:
14147             if (forwardMostMove == 0)
14148               return;           /* don't pause if no one has moved */
14149             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14150                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14151                 if(onMove->pause) {           // thinking engine can be paused
14152                     PauseEngine(onMove);      // do it
14153                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14154                         PauseEngine(onMove->other);
14155                     else
14156                         SendToProgram("easy\n", onMove->other);
14157                     StopClocks();
14158                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14159             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14160                 if(first.pause) {
14161                     PauseEngine(&first);
14162                     StopClocks();
14163                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14164             } else { // human on move, pause pondering by either method
14165                 if(first.pause)
14166                     PauseEngine(&first);
14167                 else if(appData.ponderNextMove)
14168                     SendToProgram("easy\n", &first);
14169                 StopClocks();
14170             }
14171             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14172           case AnalyzeMode:
14173             pausing = TRUE;
14174             ModeHighlight();
14175             break;
14176         }
14177     }
14178 }
14179
14180 void
14181 EditCommentEvent ()
14182 {
14183     char title[MSG_SIZ];
14184
14185     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14186       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14187     } else {
14188       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14189                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14190                parseList[currentMove - 1]);
14191     }
14192
14193     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14194 }
14195
14196
14197 void
14198 EditTagsEvent ()
14199 {
14200     char *tags = PGNTags(&gameInfo);
14201     bookUp = FALSE;
14202     EditTagsPopUp(tags, NULL);
14203     free(tags);
14204 }
14205
14206 void
14207 ToggleSecond ()
14208 {
14209   if(second.analyzing) {
14210     SendToProgram("exit\n", &second);
14211     second.analyzing = FALSE;
14212   } else {
14213     if (second.pr == NoProc) StartChessProgram(&second);
14214     InitChessProgram(&second, FALSE);
14215     FeedMovesToProgram(&second, currentMove);
14216
14217     SendToProgram("analyze\n", &second);
14218     second.analyzing = TRUE;
14219   }
14220 }
14221
14222 /* Toggle ShowThinking */
14223 void
14224 ToggleShowThinking()
14225 {
14226   appData.showThinking = !appData.showThinking;
14227   ShowThinkingEvent();
14228 }
14229
14230 int
14231 AnalyzeModeEvent ()
14232 {
14233     char buf[MSG_SIZ];
14234
14235     if (!first.analysisSupport) {
14236       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14237       DisplayError(buf, 0);
14238       return 0;
14239     }
14240     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14241     if (appData.icsActive) {
14242         if (gameMode != IcsObserving) {
14243           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14244             DisplayError(buf, 0);
14245             /* secure check */
14246             if (appData.icsEngineAnalyze) {
14247                 if (appData.debugMode)
14248                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14249                 ExitAnalyzeMode();
14250                 ModeHighlight();
14251             }
14252             return 0;
14253         }
14254         /* if enable, user wants to disable icsEngineAnalyze */
14255         if (appData.icsEngineAnalyze) {
14256                 ExitAnalyzeMode();
14257                 ModeHighlight();
14258                 return 0;
14259         }
14260         appData.icsEngineAnalyze = TRUE;
14261         if (appData.debugMode)
14262             fprintf(debugFP, "ICS engine analyze starting... \n");
14263     }
14264
14265     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14266     if (appData.noChessProgram || gameMode == AnalyzeMode)
14267       return 0;
14268
14269     if (gameMode != AnalyzeFile) {
14270         if (!appData.icsEngineAnalyze) {
14271                EditGameEvent();
14272                if (gameMode != EditGame) return 0;
14273         }
14274         if (!appData.showThinking) ToggleShowThinking();
14275         ResurrectChessProgram();
14276         SendToProgram("analyze\n", &first);
14277         first.analyzing = TRUE;
14278         /*first.maybeThinking = TRUE;*/
14279         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14280         EngineOutputPopUp();
14281     }
14282     if (!appData.icsEngineAnalyze) {
14283         gameMode = AnalyzeMode;
14284         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14285     }
14286     pausing = FALSE;
14287     ModeHighlight();
14288     SetGameInfo();
14289
14290     StartAnalysisClock();
14291     GetTimeMark(&lastNodeCountTime);
14292     lastNodeCount = 0;
14293     return 1;
14294 }
14295
14296 void
14297 AnalyzeFileEvent ()
14298 {
14299     if (appData.noChessProgram || gameMode == AnalyzeFile)
14300       return;
14301
14302     if (!first.analysisSupport) {
14303       char buf[MSG_SIZ];
14304       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14305       DisplayError(buf, 0);
14306       return;
14307     }
14308
14309     if (gameMode != AnalyzeMode) {
14310         keepInfo = 1; // mere annotating should not alter PGN tags
14311         EditGameEvent();
14312         keepInfo = 0;
14313         if (gameMode != EditGame) return;
14314         if (!appData.showThinking) ToggleShowThinking();
14315         ResurrectChessProgram();
14316         SendToProgram("analyze\n", &first);
14317         first.analyzing = TRUE;
14318         /*first.maybeThinking = TRUE;*/
14319         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14320         EngineOutputPopUp();
14321     }
14322     gameMode = AnalyzeFile;
14323     pausing = FALSE;
14324     ModeHighlight();
14325
14326     StartAnalysisClock();
14327     GetTimeMark(&lastNodeCountTime);
14328     lastNodeCount = 0;
14329     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14330     AnalysisPeriodicEvent(1);
14331 }
14332
14333 void
14334 MachineWhiteEvent ()
14335 {
14336     char buf[MSG_SIZ];
14337     char *bookHit = NULL;
14338
14339     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14340       return;
14341
14342
14343     if (gameMode == PlayFromGameFile ||
14344         gameMode == TwoMachinesPlay  ||
14345         gameMode == Training         ||
14346         gameMode == AnalyzeMode      ||
14347         gameMode == EndOfGame)
14348         EditGameEvent();
14349
14350     if (gameMode == EditPosition)
14351         EditPositionDone(TRUE);
14352
14353     if (!WhiteOnMove(currentMove)) {
14354         DisplayError(_("It is not White's turn"), 0);
14355         return;
14356     }
14357
14358     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14359       ExitAnalyzeMode();
14360
14361     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14362         gameMode == AnalyzeFile)
14363         TruncateGame();
14364
14365     ResurrectChessProgram();    /* in case it isn't running */
14366     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14367         gameMode = MachinePlaysWhite;
14368         ResetClocks();
14369     } else
14370     gameMode = MachinePlaysWhite;
14371     pausing = FALSE;
14372     ModeHighlight();
14373     SetGameInfo();
14374     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14375     DisplayTitle(buf);
14376     if (first.sendName) {
14377       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14378       SendToProgram(buf, &first);
14379     }
14380     if (first.sendTime) {
14381       if (first.useColors) {
14382         SendToProgram("black\n", &first); /*gnu kludge*/
14383       }
14384       SendTimeRemaining(&first, TRUE);
14385     }
14386     if (first.useColors) {
14387       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14388     }
14389     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14390     SetMachineThinkingEnables();
14391     first.maybeThinking = TRUE;
14392     StartClocks();
14393     firstMove = FALSE;
14394
14395     if (appData.autoFlipView && !flipView) {
14396       flipView = !flipView;
14397       DrawPosition(FALSE, NULL);
14398       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14399     }
14400
14401     if(bookHit) { // [HGM] book: simulate book reply
14402         static char bookMove[MSG_SIZ]; // a bit generous?
14403
14404         programStats.nodes = programStats.depth = programStats.time =
14405         programStats.score = programStats.got_only_move = 0;
14406         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14407
14408         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14409         strcat(bookMove, bookHit);
14410         HandleMachineMove(bookMove, &first);
14411     }
14412 }
14413
14414 void
14415 MachineBlackEvent ()
14416 {
14417   char buf[MSG_SIZ];
14418   char *bookHit = NULL;
14419
14420     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14421         return;
14422
14423
14424     if (gameMode == PlayFromGameFile ||
14425         gameMode == TwoMachinesPlay  ||
14426         gameMode == Training         ||
14427         gameMode == AnalyzeMode      ||
14428         gameMode == EndOfGame)
14429         EditGameEvent();
14430
14431     if (gameMode == EditPosition)
14432         EditPositionDone(TRUE);
14433
14434     if (WhiteOnMove(currentMove)) {
14435         DisplayError(_("It is not Black's turn"), 0);
14436         return;
14437     }
14438
14439     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14440       ExitAnalyzeMode();
14441
14442     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14443         gameMode == AnalyzeFile)
14444         TruncateGame();
14445
14446     ResurrectChessProgram();    /* in case it isn't running */
14447     gameMode = MachinePlaysBlack;
14448     pausing = FALSE;
14449     ModeHighlight();
14450     SetGameInfo();
14451     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14452     DisplayTitle(buf);
14453     if (first.sendName) {
14454       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14455       SendToProgram(buf, &first);
14456     }
14457     if (first.sendTime) {
14458       if (first.useColors) {
14459         SendToProgram("white\n", &first); /*gnu kludge*/
14460       }
14461       SendTimeRemaining(&first, FALSE);
14462     }
14463     if (first.useColors) {
14464       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14465     }
14466     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14467     SetMachineThinkingEnables();
14468     first.maybeThinking = TRUE;
14469     StartClocks();
14470
14471     if (appData.autoFlipView && flipView) {
14472       flipView = !flipView;
14473       DrawPosition(FALSE, NULL);
14474       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14475     }
14476     if(bookHit) { // [HGM] book: simulate book reply
14477         static char bookMove[MSG_SIZ]; // a bit generous?
14478
14479         programStats.nodes = programStats.depth = programStats.time =
14480         programStats.score = programStats.got_only_move = 0;
14481         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14482
14483         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14484         strcat(bookMove, bookHit);
14485         HandleMachineMove(bookMove, &first);
14486     }
14487 }
14488
14489
14490 void
14491 DisplayTwoMachinesTitle ()
14492 {
14493     char buf[MSG_SIZ];
14494     if (appData.matchGames > 0) {
14495         if(appData.tourneyFile[0]) {
14496           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14497                    gameInfo.white, _("vs."), gameInfo.black,
14498                    nextGame+1, appData.matchGames+1,
14499                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14500         } else
14501         if (first.twoMachinesColor[0] == 'w') {
14502           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14503                    gameInfo.white, _("vs."),  gameInfo.black,
14504                    first.matchWins, second.matchWins,
14505                    matchGame - 1 - (first.matchWins + second.matchWins));
14506         } else {
14507           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14508                    gameInfo.white, _("vs."), gameInfo.black,
14509                    second.matchWins, first.matchWins,
14510                    matchGame - 1 - (first.matchWins + second.matchWins));
14511         }
14512     } else {
14513       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14514     }
14515     DisplayTitle(buf);
14516 }
14517
14518 void
14519 SettingsMenuIfReady ()
14520 {
14521   if (second.lastPing != second.lastPong) {
14522     DisplayMessage("", _("Waiting for second chess program"));
14523     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14524     return;
14525   }
14526   ThawUI();
14527   DisplayMessage("", "");
14528   SettingsPopUp(&second);
14529 }
14530
14531 int
14532 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14533 {
14534     char buf[MSG_SIZ];
14535     if (cps->pr == NoProc) {
14536         StartChessProgram(cps);
14537         if (cps->protocolVersion == 1) {
14538           retry();
14539           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14540         } else {
14541           /* kludge: allow timeout for initial "feature" command */
14542           if(retry != TwoMachinesEventIfReady) FreezeUI();
14543           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14544           DisplayMessage("", buf);
14545           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14546         }
14547         return 1;
14548     }
14549     return 0;
14550 }
14551
14552 void
14553 TwoMachinesEvent P((void))
14554 {
14555     int i;
14556     char buf[MSG_SIZ];
14557     ChessProgramState *onmove;
14558     char *bookHit = NULL;
14559     static int stalling = 0;
14560     TimeMark now;
14561     long wait;
14562
14563     if (appData.noChessProgram) return;
14564
14565     switch (gameMode) {
14566       case TwoMachinesPlay:
14567         return;
14568       case MachinePlaysWhite:
14569       case MachinePlaysBlack:
14570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14571             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14572             return;
14573         }
14574         /* fall through */
14575       case BeginningOfGame:
14576       case PlayFromGameFile:
14577       case EndOfGame:
14578         EditGameEvent();
14579         if (gameMode != EditGame) return;
14580         break;
14581       case EditPosition:
14582         EditPositionDone(TRUE);
14583         break;
14584       case AnalyzeMode:
14585       case AnalyzeFile:
14586         ExitAnalyzeMode();
14587         break;
14588       case EditGame:
14589       default:
14590         break;
14591     }
14592
14593 //    forwardMostMove = currentMove;
14594     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14595     startingEngine = TRUE;
14596
14597     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14598
14599     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14600     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14601       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14602       return;
14603     }
14604     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14605
14606     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14607                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14608         startingEngine = FALSE;
14609         DisplayError("second engine does not play this", 0);
14610         return;
14611     }
14612
14613     if(!stalling) {
14614       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14615       SendToProgram("force\n", &second);
14616       stalling = 1;
14617       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14618       return;
14619     }
14620     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14621     if(appData.matchPause>10000 || appData.matchPause<10)
14622                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14623     wait = SubtractTimeMarks(&now, &pauseStart);
14624     if(wait < appData.matchPause) {
14625         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14626         return;
14627     }
14628     // we are now committed to starting the game
14629     stalling = 0;
14630     DisplayMessage("", "");
14631     if (startedFromSetupPosition) {
14632         SendBoard(&second, backwardMostMove);
14633     if (appData.debugMode) {
14634         fprintf(debugFP, "Two Machines\n");
14635     }
14636     }
14637     for (i = backwardMostMove; i < forwardMostMove; i++) {
14638         SendMoveToProgram(i, &second);
14639     }
14640
14641     gameMode = TwoMachinesPlay;
14642     pausing = startingEngine = FALSE;
14643     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14644     SetGameInfo();
14645     DisplayTwoMachinesTitle();
14646     firstMove = TRUE;
14647     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14648         onmove = &first;
14649     } else {
14650         onmove = &second;
14651     }
14652     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14653     SendToProgram(first.computerString, &first);
14654     if (first.sendName) {
14655       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14656       SendToProgram(buf, &first);
14657     }
14658     SendToProgram(second.computerString, &second);
14659     if (second.sendName) {
14660       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14661       SendToProgram(buf, &second);
14662     }
14663
14664     ResetClocks();
14665     if (!first.sendTime || !second.sendTime) {
14666         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14667         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14668     }
14669     if (onmove->sendTime) {
14670       if (onmove->useColors) {
14671         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14672       }
14673       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14674     }
14675     if (onmove->useColors) {
14676       SendToProgram(onmove->twoMachinesColor, onmove);
14677     }
14678     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14679 //    SendToProgram("go\n", onmove);
14680     onmove->maybeThinking = TRUE;
14681     SetMachineThinkingEnables();
14682
14683     StartClocks();
14684
14685     if(bookHit) { // [HGM] book: simulate book reply
14686         static char bookMove[MSG_SIZ]; // a bit generous?
14687
14688         programStats.nodes = programStats.depth = programStats.time =
14689         programStats.score = programStats.got_only_move = 0;
14690         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14691
14692         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14693         strcat(bookMove, bookHit);
14694         savedMessage = bookMove; // args for deferred call
14695         savedState = onmove;
14696         ScheduleDelayedEvent(DeferredBookMove, 1);
14697     }
14698 }
14699
14700 void
14701 TrainingEvent ()
14702 {
14703     if (gameMode == Training) {
14704       SetTrainingModeOff();
14705       gameMode = PlayFromGameFile;
14706       DisplayMessage("", _("Training mode off"));
14707     } else {
14708       gameMode = Training;
14709       animateTraining = appData.animate;
14710
14711       /* make sure we are not already at the end of the game */
14712       if (currentMove < forwardMostMove) {
14713         SetTrainingModeOn();
14714         DisplayMessage("", _("Training mode on"));
14715       } else {
14716         gameMode = PlayFromGameFile;
14717         DisplayError(_("Already at end of game"), 0);
14718       }
14719     }
14720     ModeHighlight();
14721 }
14722
14723 void
14724 IcsClientEvent ()
14725 {
14726     if (!appData.icsActive) return;
14727     switch (gameMode) {
14728       case IcsPlayingWhite:
14729       case IcsPlayingBlack:
14730       case IcsObserving:
14731       case IcsIdle:
14732       case BeginningOfGame:
14733       case IcsExamining:
14734         return;
14735
14736       case EditGame:
14737         break;
14738
14739       case EditPosition:
14740         EditPositionDone(TRUE);
14741         break;
14742
14743       case AnalyzeMode:
14744       case AnalyzeFile:
14745         ExitAnalyzeMode();
14746         break;
14747
14748       default:
14749         EditGameEvent();
14750         break;
14751     }
14752
14753     gameMode = IcsIdle;
14754     ModeHighlight();
14755     return;
14756 }
14757
14758 void
14759 EditGameEvent ()
14760 {
14761     int i;
14762
14763     switch (gameMode) {
14764       case Training:
14765         SetTrainingModeOff();
14766         break;
14767       case MachinePlaysWhite:
14768       case MachinePlaysBlack:
14769       case BeginningOfGame:
14770         SendToProgram("force\n", &first);
14771         SetUserThinkingEnables();
14772         break;
14773       case PlayFromGameFile:
14774         (void) StopLoadGameTimer();
14775         if (gameFileFP != NULL) {
14776             gameFileFP = NULL;
14777         }
14778         break;
14779       case EditPosition:
14780         EditPositionDone(TRUE);
14781         break;
14782       case AnalyzeMode:
14783       case AnalyzeFile:
14784         ExitAnalyzeMode();
14785         SendToProgram("force\n", &first);
14786         break;
14787       case TwoMachinesPlay:
14788         GameEnds(EndOfFile, NULL, GE_PLAYER);
14789         ResurrectChessProgram();
14790         SetUserThinkingEnables();
14791         break;
14792       case EndOfGame:
14793         ResurrectChessProgram();
14794         break;
14795       case IcsPlayingBlack:
14796       case IcsPlayingWhite:
14797         DisplayError(_("Warning: You are still playing a game"), 0);
14798         break;
14799       case IcsObserving:
14800         DisplayError(_("Warning: You are still observing a game"), 0);
14801         break;
14802       case IcsExamining:
14803         DisplayError(_("Warning: You are still examining a game"), 0);
14804         break;
14805       case IcsIdle:
14806         break;
14807       case EditGame:
14808       default:
14809         return;
14810     }
14811
14812     pausing = FALSE;
14813     StopClocks();
14814     first.offeredDraw = second.offeredDraw = 0;
14815
14816     if (gameMode == PlayFromGameFile) {
14817         whiteTimeRemaining = timeRemaining[0][currentMove];
14818         blackTimeRemaining = timeRemaining[1][currentMove];
14819         DisplayTitle("");
14820     }
14821
14822     if (gameMode == MachinePlaysWhite ||
14823         gameMode == MachinePlaysBlack ||
14824         gameMode == TwoMachinesPlay ||
14825         gameMode == EndOfGame) {
14826         i = forwardMostMove;
14827         while (i > currentMove) {
14828             SendToProgram("undo\n", &first);
14829             i--;
14830         }
14831         if(!adjustedClock) {
14832         whiteTimeRemaining = timeRemaining[0][currentMove];
14833         blackTimeRemaining = timeRemaining[1][currentMove];
14834         DisplayBothClocks();
14835         }
14836         if (whiteFlag || blackFlag) {
14837             whiteFlag = blackFlag = 0;
14838         }
14839         DisplayTitle("");
14840     }
14841
14842     gameMode = EditGame;
14843     ModeHighlight();
14844     SetGameInfo();
14845 }
14846
14847
14848 void
14849 EditPositionEvent ()
14850 {
14851     if (gameMode == EditPosition) {
14852         EditGameEvent();
14853         return;
14854     }
14855
14856     EditGameEvent();
14857     if (gameMode != EditGame) return;
14858
14859     gameMode = EditPosition;
14860     ModeHighlight();
14861     SetGameInfo();
14862     if (currentMove > 0)
14863       CopyBoard(boards[0], boards[currentMove]);
14864
14865     blackPlaysFirst = !WhiteOnMove(currentMove);
14866     ResetClocks();
14867     currentMove = forwardMostMove = backwardMostMove = 0;
14868     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14869     DisplayMove(-1);
14870     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14871 }
14872
14873 void
14874 ExitAnalyzeMode ()
14875 {
14876     /* [DM] icsEngineAnalyze - possible call from other functions */
14877     if (appData.icsEngineAnalyze) {
14878         appData.icsEngineAnalyze = FALSE;
14879
14880         DisplayMessage("",_("Close ICS engine analyze..."));
14881     }
14882     if (first.analysisSupport && first.analyzing) {
14883       SendToBoth("exit\n");
14884       first.analyzing = second.analyzing = FALSE;
14885     }
14886     thinkOutput[0] = NULLCHAR;
14887 }
14888
14889 void
14890 EditPositionDone (Boolean fakeRights)
14891 {
14892     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14893
14894     startedFromSetupPosition = TRUE;
14895     InitChessProgram(&first, FALSE);
14896     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14897       boards[0][EP_STATUS] = EP_NONE;
14898       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14899       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14900         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14901         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14902       } else boards[0][CASTLING][2] = NoRights;
14903       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14904         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14905         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14906       } else boards[0][CASTLING][5] = NoRights;
14907       if(gameInfo.variant == VariantSChess) {
14908         int i;
14909         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14910           boards[0][VIRGIN][i] = 0;
14911           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14912           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14913         }
14914       }
14915     }
14916     SendToProgram("force\n", &first);
14917     if (blackPlaysFirst) {
14918         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14919         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14920         currentMove = forwardMostMove = backwardMostMove = 1;
14921         CopyBoard(boards[1], boards[0]);
14922     } else {
14923         currentMove = forwardMostMove = backwardMostMove = 0;
14924     }
14925     SendBoard(&first, forwardMostMove);
14926     if (appData.debugMode) {
14927         fprintf(debugFP, "EditPosDone\n");
14928     }
14929     DisplayTitle("");
14930     DisplayMessage("", "");
14931     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14932     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14933     gameMode = EditGame;
14934     ModeHighlight();
14935     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14936     ClearHighlights(); /* [AS] */
14937 }
14938
14939 /* Pause for `ms' milliseconds */
14940 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14941 void
14942 TimeDelay (long ms)
14943 {
14944     TimeMark m1, m2;
14945
14946     GetTimeMark(&m1);
14947     do {
14948         GetTimeMark(&m2);
14949     } while (SubtractTimeMarks(&m2, &m1) < ms);
14950 }
14951
14952 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14953 void
14954 SendMultiLineToICS (char *buf)
14955 {
14956     char temp[MSG_SIZ+1], *p;
14957     int len;
14958
14959     len = strlen(buf);
14960     if (len > MSG_SIZ)
14961       len = MSG_SIZ;
14962
14963     strncpy(temp, buf, len);
14964     temp[len] = 0;
14965
14966     p = temp;
14967     while (*p) {
14968         if (*p == '\n' || *p == '\r')
14969           *p = ' ';
14970         ++p;
14971     }
14972
14973     strcat(temp, "\n");
14974     SendToICS(temp);
14975     SendToPlayer(temp, strlen(temp));
14976 }
14977
14978 void
14979 SetWhiteToPlayEvent ()
14980 {
14981     if (gameMode == EditPosition) {
14982         blackPlaysFirst = FALSE;
14983         DisplayBothClocks();    /* works because currentMove is 0 */
14984     } else if (gameMode == IcsExamining) {
14985         SendToICS(ics_prefix);
14986         SendToICS("tomove white\n");
14987     }
14988 }
14989
14990 void
14991 SetBlackToPlayEvent ()
14992 {
14993     if (gameMode == EditPosition) {
14994         blackPlaysFirst = TRUE;
14995         currentMove = 1;        /* kludge */
14996         DisplayBothClocks();
14997         currentMove = 0;
14998     } else if (gameMode == IcsExamining) {
14999         SendToICS(ics_prefix);
15000         SendToICS("tomove black\n");
15001     }
15002 }
15003
15004 void
15005 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15006 {
15007     char buf[MSG_SIZ];
15008     ChessSquare piece = boards[0][y][x];
15009     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15010     static int lastVariant;
15011
15012     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15013
15014     switch (selection) {
15015       case ClearBoard:
15016         CopyBoard(currentBoard, boards[0]);
15017         CopyBoard(menuBoard, initialPosition);
15018         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15019             SendToICS(ics_prefix);
15020             SendToICS("bsetup clear\n");
15021         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15022             SendToICS(ics_prefix);
15023             SendToICS("clearboard\n");
15024         } else {
15025             int nonEmpty = 0;
15026             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15027                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15028                 for (y = 0; y < BOARD_HEIGHT; y++) {
15029                     if (gameMode == IcsExamining) {
15030                         if (boards[currentMove][y][x] != EmptySquare) {
15031                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15032                                     AAA + x, ONE + y);
15033                             SendToICS(buf);
15034                         }
15035                     } else {
15036                         if(boards[0][y][x] != p) nonEmpty++;
15037                         boards[0][y][x] = p;
15038                     }
15039                 }
15040             }
15041             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15042                 int r;
15043                 for(r = 0; r < BOARD_HEIGHT; r++) {
15044                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15045                     ChessSquare p = menuBoard[r][x];
15046                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15047                   }
15048                 }
15049                 DisplayMessage("Clicking clock again restores position", "");
15050                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15051                 if(!nonEmpty) { // asked to clear an empty board
15052                     CopyBoard(boards[0], menuBoard);
15053                 } else
15054                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15055                     CopyBoard(boards[0], initialPosition);
15056                 } else
15057                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15058                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15059                     CopyBoard(boards[0], erasedBoard);
15060                 } else
15061                     CopyBoard(erasedBoard, currentBoard);
15062
15063             }
15064         }
15065         if (gameMode == EditPosition) {
15066             DrawPosition(FALSE, boards[0]);
15067         }
15068         break;
15069
15070       case WhitePlay:
15071         SetWhiteToPlayEvent();
15072         break;
15073
15074       case BlackPlay:
15075         SetBlackToPlayEvent();
15076         break;
15077
15078       case EmptySquare:
15079         if (gameMode == IcsExamining) {
15080             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15081             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15082             SendToICS(buf);
15083         } else {
15084             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15085                 if(x == BOARD_LEFT-2) {
15086                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15087                     boards[0][y][1] = 0;
15088                 } else
15089                 if(x == BOARD_RGHT+1) {
15090                     if(y >= gameInfo.holdingsSize) break;
15091                     boards[0][y][BOARD_WIDTH-2] = 0;
15092                 } else break;
15093             }
15094             boards[0][y][x] = EmptySquare;
15095             DrawPosition(FALSE, boards[0]);
15096         }
15097         break;
15098
15099       case PromotePiece:
15100         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15101            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15102             selection = (ChessSquare) (PROMOTED piece);
15103         } else if(piece == EmptySquare) selection = WhiteSilver;
15104         else selection = (ChessSquare)((int)piece - 1);
15105         goto defaultlabel;
15106
15107       case DemotePiece:
15108         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15109            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15110             selection = (ChessSquare) (DEMOTED piece);
15111         } else if(piece == EmptySquare) selection = BlackSilver;
15112         else selection = (ChessSquare)((int)piece + 1);
15113         goto defaultlabel;
15114
15115       case WhiteQueen:
15116       case BlackQueen:
15117         if(gameInfo.variant == VariantShatranj ||
15118            gameInfo.variant == VariantXiangqi  ||
15119            gameInfo.variant == VariantCourier  ||
15120            gameInfo.variant == VariantASEAN    ||
15121            gameInfo.variant == VariantMakruk     )
15122             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15123         goto defaultlabel;
15124
15125       case WhiteKing:
15126       case BlackKing:
15127         if(gameInfo.variant == VariantXiangqi)
15128             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15129         if(gameInfo.variant == VariantKnightmate)
15130             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15131       default:
15132         defaultlabel:
15133         if (gameMode == IcsExamining) {
15134             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15135             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15136                      PieceToChar(selection), AAA + x, ONE + y);
15137             SendToICS(buf);
15138         } else {
15139             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15140                 int n;
15141                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15142                     n = PieceToNumber(selection - BlackPawn);
15143                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15144                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15145                     boards[0][BOARD_HEIGHT-1-n][1]++;
15146                 } else
15147                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15148                     n = PieceToNumber(selection);
15149                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15150                     boards[0][n][BOARD_WIDTH-1] = selection;
15151                     boards[0][n][BOARD_WIDTH-2]++;
15152                 }
15153             } else
15154             boards[0][y][x] = selection;
15155             DrawPosition(TRUE, boards[0]);
15156             ClearHighlights();
15157             fromX = fromY = -1;
15158         }
15159         break;
15160     }
15161 }
15162
15163
15164 void
15165 DropMenuEvent (ChessSquare selection, int x, int y)
15166 {
15167     ChessMove moveType;
15168
15169     switch (gameMode) {
15170       case IcsPlayingWhite:
15171       case MachinePlaysBlack:
15172         if (!WhiteOnMove(currentMove)) {
15173             DisplayMoveError(_("It is Black's turn"));
15174             return;
15175         }
15176         moveType = WhiteDrop;
15177         break;
15178       case IcsPlayingBlack:
15179       case MachinePlaysWhite:
15180         if (WhiteOnMove(currentMove)) {
15181             DisplayMoveError(_("It is White's turn"));
15182             return;
15183         }
15184         moveType = BlackDrop;
15185         break;
15186       case EditGame:
15187         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15188         break;
15189       default:
15190         return;
15191     }
15192
15193     if (moveType == BlackDrop && selection < BlackPawn) {
15194       selection = (ChessSquare) ((int) selection
15195                                  + (int) BlackPawn - (int) WhitePawn);
15196     }
15197     if (boards[currentMove][y][x] != EmptySquare) {
15198         DisplayMoveError(_("That square is occupied"));
15199         return;
15200     }
15201
15202     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15203 }
15204
15205 void
15206 AcceptEvent ()
15207 {
15208     /* Accept a pending offer of any kind from opponent */
15209
15210     if (appData.icsActive) {
15211         SendToICS(ics_prefix);
15212         SendToICS("accept\n");
15213     } else if (cmailMsgLoaded) {
15214         if (currentMove == cmailOldMove &&
15215             commentList[cmailOldMove] != NULL &&
15216             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15217                    "Black offers a draw" : "White offers a draw")) {
15218             TruncateGame();
15219             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15220             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15221         } else {
15222             DisplayError(_("There is no pending offer on this move"), 0);
15223             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15224         }
15225     } else {
15226         /* Not used for offers from chess program */
15227     }
15228 }
15229
15230 void
15231 DeclineEvent ()
15232 {
15233     /* Decline a pending offer of any kind from opponent */
15234
15235     if (appData.icsActive) {
15236         SendToICS(ics_prefix);
15237         SendToICS("decline\n");
15238     } else if (cmailMsgLoaded) {
15239         if (currentMove == cmailOldMove &&
15240             commentList[cmailOldMove] != NULL &&
15241             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15242                    "Black offers a draw" : "White offers a draw")) {
15243 #ifdef NOTDEF
15244             AppendComment(cmailOldMove, "Draw declined", TRUE);
15245             DisplayComment(cmailOldMove - 1, "Draw declined");
15246 #endif /*NOTDEF*/
15247         } else {
15248             DisplayError(_("There is no pending offer on this move"), 0);
15249         }
15250     } else {
15251         /* Not used for offers from chess program */
15252     }
15253 }
15254
15255 void
15256 RematchEvent ()
15257 {
15258     /* Issue ICS rematch command */
15259     if (appData.icsActive) {
15260         SendToICS(ics_prefix);
15261         SendToICS("rematch\n");
15262     }
15263 }
15264
15265 void
15266 CallFlagEvent ()
15267 {
15268     /* Call your opponent's flag (claim a win on time) */
15269     if (appData.icsActive) {
15270         SendToICS(ics_prefix);
15271         SendToICS("flag\n");
15272     } else {
15273         switch (gameMode) {
15274           default:
15275             return;
15276           case MachinePlaysWhite:
15277             if (whiteFlag) {
15278                 if (blackFlag)
15279                   GameEnds(GameIsDrawn, "Both players ran out of time",
15280                            GE_PLAYER);
15281                 else
15282                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15283             } else {
15284                 DisplayError(_("Your opponent is not out of time"), 0);
15285             }
15286             break;
15287           case MachinePlaysBlack:
15288             if (blackFlag) {
15289                 if (whiteFlag)
15290                   GameEnds(GameIsDrawn, "Both players ran out of time",
15291                            GE_PLAYER);
15292                 else
15293                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15294             } else {
15295                 DisplayError(_("Your opponent is not out of time"), 0);
15296             }
15297             break;
15298         }
15299     }
15300 }
15301
15302 void
15303 ClockClick (int which)
15304 {       // [HGM] code moved to back-end from winboard.c
15305         if(which) { // black clock
15306           if (gameMode == EditPosition || gameMode == IcsExamining) {
15307             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15308             SetBlackToPlayEvent();
15309           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15310                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15311           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15312           } else if (shiftKey) {
15313             AdjustClock(which, -1);
15314           } else if (gameMode == IcsPlayingWhite ||
15315                      gameMode == MachinePlaysBlack) {
15316             CallFlagEvent();
15317           }
15318         } else { // white clock
15319           if (gameMode == EditPosition || gameMode == IcsExamining) {
15320             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15321             SetWhiteToPlayEvent();
15322           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15323                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15324           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15325           } else if (shiftKey) {
15326             AdjustClock(which, -1);
15327           } else if (gameMode == IcsPlayingBlack ||
15328                    gameMode == MachinePlaysWhite) {
15329             CallFlagEvent();
15330           }
15331         }
15332 }
15333
15334 void
15335 DrawEvent ()
15336 {
15337     /* Offer draw or accept pending draw offer from opponent */
15338
15339     if (appData.icsActive) {
15340         /* Note: tournament rules require draw offers to be
15341            made after you make your move but before you punch
15342            your clock.  Currently ICS doesn't let you do that;
15343            instead, you immediately punch your clock after making
15344            a move, but you can offer a draw at any time. */
15345
15346         SendToICS(ics_prefix);
15347         SendToICS("draw\n");
15348         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15349     } else if (cmailMsgLoaded) {
15350         if (currentMove == cmailOldMove &&
15351             commentList[cmailOldMove] != NULL &&
15352             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15353                    "Black offers a draw" : "White offers a draw")) {
15354             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15356         } else if (currentMove == cmailOldMove + 1) {
15357             char *offer = WhiteOnMove(cmailOldMove) ?
15358               "White offers a draw" : "Black offers a draw";
15359             AppendComment(currentMove, offer, TRUE);
15360             DisplayComment(currentMove - 1, offer);
15361             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15362         } else {
15363             DisplayError(_("You must make your move before offering a draw"), 0);
15364             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15365         }
15366     } else if (first.offeredDraw) {
15367         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15368     } else {
15369         if (first.sendDrawOffers) {
15370             SendToProgram("draw\n", &first);
15371             userOfferedDraw = TRUE;
15372         }
15373     }
15374 }
15375
15376 void
15377 AdjournEvent ()
15378 {
15379     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15380
15381     if (appData.icsActive) {
15382         SendToICS(ics_prefix);
15383         SendToICS("adjourn\n");
15384     } else {
15385         /* Currently GNU Chess doesn't offer or accept Adjourns */
15386     }
15387 }
15388
15389
15390 void
15391 AbortEvent ()
15392 {
15393     /* Offer Abort or accept pending Abort offer from opponent */
15394
15395     if (appData.icsActive) {
15396         SendToICS(ics_prefix);
15397         SendToICS("abort\n");
15398     } else {
15399         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15400     }
15401 }
15402
15403 void
15404 ResignEvent ()
15405 {
15406     /* Resign.  You can do this even if it's not your turn. */
15407
15408     if (appData.icsActive) {
15409         SendToICS(ics_prefix);
15410         SendToICS("resign\n");
15411     } else {
15412         switch (gameMode) {
15413           case MachinePlaysWhite:
15414             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15415             break;
15416           case MachinePlaysBlack:
15417             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15418             break;
15419           case EditGame:
15420             if (cmailMsgLoaded) {
15421                 TruncateGame();
15422                 if (WhiteOnMove(cmailOldMove)) {
15423                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15424                 } else {
15425                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15426                 }
15427                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15428             }
15429             break;
15430           default:
15431             break;
15432         }
15433     }
15434 }
15435
15436
15437 void
15438 StopObservingEvent ()
15439 {
15440     /* Stop observing current games */
15441     SendToICS(ics_prefix);
15442     SendToICS("unobserve\n");
15443 }
15444
15445 void
15446 StopExaminingEvent ()
15447 {
15448     /* Stop observing current game */
15449     SendToICS(ics_prefix);
15450     SendToICS("unexamine\n");
15451 }
15452
15453 void
15454 ForwardInner (int target)
15455 {
15456     int limit; int oldSeekGraphUp = seekGraphUp;
15457
15458     if (appData.debugMode)
15459         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15460                 target, currentMove, forwardMostMove);
15461
15462     if (gameMode == EditPosition)
15463       return;
15464
15465     seekGraphUp = FALSE;
15466     MarkTargetSquares(1);
15467
15468     if (gameMode == PlayFromGameFile && !pausing)
15469       PauseEvent();
15470
15471     if (gameMode == IcsExamining && pausing)
15472       limit = pauseExamForwardMostMove;
15473     else
15474       limit = forwardMostMove;
15475
15476     if (target > limit) target = limit;
15477
15478     if (target > 0 && moveList[target - 1][0]) {
15479         int fromX, fromY, toX, toY;
15480         toX = moveList[target - 1][2] - AAA;
15481         toY = moveList[target - 1][3] - ONE;
15482         if (moveList[target - 1][1] == '@') {
15483             if (appData.highlightLastMove) {
15484                 SetHighlights(-1, -1, toX, toY);
15485             }
15486         } else {
15487             fromX = moveList[target - 1][0] - AAA;
15488             fromY = moveList[target - 1][1] - ONE;
15489             if (target == currentMove + 1) {
15490                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15491             }
15492             if (appData.highlightLastMove) {
15493                 SetHighlights(fromX, fromY, toX, toY);
15494             }
15495         }
15496     }
15497     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15498         gameMode == Training || gameMode == PlayFromGameFile ||
15499         gameMode == AnalyzeFile) {
15500         while (currentMove < target) {
15501             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15502             SendMoveToProgram(currentMove++, &first);
15503         }
15504     } else {
15505         currentMove = target;
15506     }
15507
15508     if (gameMode == EditGame || gameMode == EndOfGame) {
15509         whiteTimeRemaining = timeRemaining[0][currentMove];
15510         blackTimeRemaining = timeRemaining[1][currentMove];
15511     }
15512     DisplayBothClocks();
15513     DisplayMove(currentMove - 1);
15514     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15515     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15516     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15517         DisplayComment(currentMove - 1, commentList[currentMove]);
15518     }
15519     ClearMap(); // [HGM] exclude: invalidate map
15520 }
15521
15522
15523 void
15524 ForwardEvent ()
15525 {
15526     if (gameMode == IcsExamining && !pausing) {
15527         SendToICS(ics_prefix);
15528         SendToICS("forward\n");
15529     } else {
15530         ForwardInner(currentMove + 1);
15531     }
15532 }
15533
15534 void
15535 ToEndEvent ()
15536 {
15537     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15538         /* to optimze, we temporarily turn off analysis mode while we feed
15539          * the remaining moves to the engine. Otherwise we get analysis output
15540          * after each move.
15541          */
15542         if (first.analysisSupport) {
15543           SendToProgram("exit\nforce\n", &first);
15544           first.analyzing = FALSE;
15545         }
15546     }
15547
15548     if (gameMode == IcsExamining && !pausing) {
15549         SendToICS(ics_prefix);
15550         SendToICS("forward 999999\n");
15551     } else {
15552         ForwardInner(forwardMostMove);
15553     }
15554
15555     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15556         /* we have fed all the moves, so reactivate analysis mode */
15557         SendToProgram("analyze\n", &first);
15558         first.analyzing = TRUE;
15559         /*first.maybeThinking = TRUE;*/
15560         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15561     }
15562 }
15563
15564 void
15565 BackwardInner (int target)
15566 {
15567     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15568
15569     if (appData.debugMode)
15570         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15571                 target, currentMove, forwardMostMove);
15572
15573     if (gameMode == EditPosition) return;
15574     seekGraphUp = FALSE;
15575     MarkTargetSquares(1);
15576     if (currentMove <= backwardMostMove) {
15577         ClearHighlights();
15578         DrawPosition(full_redraw, boards[currentMove]);
15579         return;
15580     }
15581     if (gameMode == PlayFromGameFile && !pausing)
15582       PauseEvent();
15583
15584     if (moveList[target][0]) {
15585         int fromX, fromY, toX, toY;
15586         toX = moveList[target][2] - AAA;
15587         toY = moveList[target][3] - ONE;
15588         if (moveList[target][1] == '@') {
15589             if (appData.highlightLastMove) {
15590                 SetHighlights(-1, -1, toX, toY);
15591             }
15592         } else {
15593             fromX = moveList[target][0] - AAA;
15594             fromY = moveList[target][1] - ONE;
15595             if (target == currentMove - 1) {
15596                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15597             }
15598             if (appData.highlightLastMove) {
15599                 SetHighlights(fromX, fromY, toX, toY);
15600             }
15601         }
15602     }
15603     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15604         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15605         while (currentMove > target) {
15606             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15607                 // null move cannot be undone. Reload program with move history before it.
15608                 int i;
15609                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15610                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15611                 }
15612                 SendBoard(&first, i);
15613               if(second.analyzing) SendBoard(&second, i);
15614                 for(currentMove=i; currentMove<target; currentMove++) {
15615                     SendMoveToProgram(currentMove, &first);
15616                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15617                 }
15618                 break;
15619             }
15620             SendToBoth("undo\n");
15621             currentMove--;
15622         }
15623     } else {
15624         currentMove = target;
15625     }
15626
15627     if (gameMode == EditGame || gameMode == EndOfGame) {
15628         whiteTimeRemaining = timeRemaining[0][currentMove];
15629         blackTimeRemaining = timeRemaining[1][currentMove];
15630     }
15631     DisplayBothClocks();
15632     DisplayMove(currentMove - 1);
15633     DrawPosition(full_redraw, boards[currentMove]);
15634     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15635     // [HGM] PV info: routine tests if comment empty
15636     DisplayComment(currentMove - 1, commentList[currentMove]);
15637     ClearMap(); // [HGM] exclude: invalidate map
15638 }
15639
15640 void
15641 BackwardEvent ()
15642 {
15643     if (gameMode == IcsExamining && !pausing) {
15644         SendToICS(ics_prefix);
15645         SendToICS("backward\n");
15646     } else {
15647         BackwardInner(currentMove - 1);
15648     }
15649 }
15650
15651 void
15652 ToStartEvent ()
15653 {
15654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15655         /* to optimize, we temporarily turn off analysis mode while we undo
15656          * all the moves. Otherwise we get analysis output after each undo.
15657          */
15658         if (first.analysisSupport) {
15659           SendToProgram("exit\nforce\n", &first);
15660           first.analyzing = FALSE;
15661         }
15662     }
15663
15664     if (gameMode == IcsExamining && !pausing) {
15665         SendToICS(ics_prefix);
15666         SendToICS("backward 999999\n");
15667     } else {
15668         BackwardInner(backwardMostMove);
15669     }
15670
15671     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15672         /* we have fed all the moves, so reactivate analysis mode */
15673         SendToProgram("analyze\n", &first);
15674         first.analyzing = TRUE;
15675         /*first.maybeThinking = TRUE;*/
15676         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15677     }
15678 }
15679
15680 void
15681 ToNrEvent (int to)
15682 {
15683   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15684   if (to >= forwardMostMove) to = forwardMostMove;
15685   if (to <= backwardMostMove) to = backwardMostMove;
15686   if (to < currentMove) {
15687     BackwardInner(to);
15688   } else {
15689     ForwardInner(to);
15690   }
15691 }
15692
15693 void
15694 RevertEvent (Boolean annotate)
15695 {
15696     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15697         return;
15698     }
15699     if (gameMode != IcsExamining) {
15700         DisplayError(_("You are not examining a game"), 0);
15701         return;
15702     }
15703     if (pausing) {
15704         DisplayError(_("You can't revert while pausing"), 0);
15705         return;
15706     }
15707     SendToICS(ics_prefix);
15708     SendToICS("revert\n");
15709 }
15710
15711 void
15712 RetractMoveEvent ()
15713 {
15714     switch (gameMode) {
15715       case MachinePlaysWhite:
15716       case MachinePlaysBlack:
15717         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15718             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15719             return;
15720         }
15721         if (forwardMostMove < 2) return;
15722         currentMove = forwardMostMove = forwardMostMove - 2;
15723         whiteTimeRemaining = timeRemaining[0][currentMove];
15724         blackTimeRemaining = timeRemaining[1][currentMove];
15725         DisplayBothClocks();
15726         DisplayMove(currentMove - 1);
15727         ClearHighlights();/*!! could figure this out*/
15728         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15729         SendToProgram("remove\n", &first);
15730         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15731         break;
15732
15733       case BeginningOfGame:
15734       default:
15735         break;
15736
15737       case IcsPlayingWhite:
15738       case IcsPlayingBlack:
15739         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15740             SendToICS(ics_prefix);
15741             SendToICS("takeback 2\n");
15742         } else {
15743             SendToICS(ics_prefix);
15744             SendToICS("takeback 1\n");
15745         }
15746         break;
15747     }
15748 }
15749
15750 void
15751 MoveNowEvent ()
15752 {
15753     ChessProgramState *cps;
15754
15755     switch (gameMode) {
15756       case MachinePlaysWhite:
15757         if (!WhiteOnMove(forwardMostMove)) {
15758             DisplayError(_("It is your turn"), 0);
15759             return;
15760         }
15761         cps = &first;
15762         break;
15763       case MachinePlaysBlack:
15764         if (WhiteOnMove(forwardMostMove)) {
15765             DisplayError(_("It is your turn"), 0);
15766             return;
15767         }
15768         cps = &first;
15769         break;
15770       case TwoMachinesPlay:
15771         if (WhiteOnMove(forwardMostMove) ==
15772             (first.twoMachinesColor[0] == 'w')) {
15773             cps = &first;
15774         } else {
15775             cps = &second;
15776         }
15777         break;
15778       case BeginningOfGame:
15779       default:
15780         return;
15781     }
15782     SendToProgram("?\n", cps);
15783 }
15784
15785 void
15786 TruncateGameEvent ()
15787 {
15788     EditGameEvent();
15789     if (gameMode != EditGame) return;
15790     TruncateGame();
15791 }
15792
15793 void
15794 TruncateGame ()
15795 {
15796     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15797     if (forwardMostMove > currentMove) {
15798         if (gameInfo.resultDetails != NULL) {
15799             free(gameInfo.resultDetails);
15800             gameInfo.resultDetails = NULL;
15801             gameInfo.result = GameUnfinished;
15802         }
15803         forwardMostMove = currentMove;
15804         HistorySet(parseList, backwardMostMove, forwardMostMove,
15805                    currentMove-1);
15806     }
15807 }
15808
15809 void
15810 HintEvent ()
15811 {
15812     if (appData.noChessProgram) return;
15813     switch (gameMode) {
15814       case MachinePlaysWhite:
15815         if (WhiteOnMove(forwardMostMove)) {
15816             DisplayError(_("Wait until your turn."), 0);
15817             return;
15818         }
15819         break;
15820       case BeginningOfGame:
15821       case MachinePlaysBlack:
15822         if (!WhiteOnMove(forwardMostMove)) {
15823             DisplayError(_("Wait until your turn."), 0);
15824             return;
15825         }
15826         break;
15827       default:
15828         DisplayError(_("No hint available"), 0);
15829         return;
15830     }
15831     SendToProgram("hint\n", &first);
15832     hintRequested = TRUE;
15833 }
15834
15835 int
15836 SaveSelected (FILE *g, int dummy, char *dummy2)
15837 {
15838     ListGame * lg = (ListGame *) gameList.head;
15839     int nItem, cnt=0;
15840     FILE *f;
15841
15842     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15843         DisplayError(_("Game list not loaded or empty"), 0);
15844         return 0;
15845     }
15846
15847     creatingBook = TRUE; // suppresses stuff during load game
15848
15849     /* Get list size */
15850     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15851         if(lg->position >= 0) { // selected?
15852             LoadGame(f, nItem, "", TRUE);
15853             SaveGamePGN2(g); // leaves g open
15854             cnt++;
15855         }
15856         lg = (ListGame *) lg->node.succ;
15857     }
15858
15859     fclose(g);
15860     creatingBook = FALSE;
15861
15862     return cnt;
15863 }
15864
15865 void
15866 CreateBookEvent ()
15867 {
15868     ListGame * lg = (ListGame *) gameList.head;
15869     FILE *f, *g;
15870     int nItem;
15871     static int secondTime = FALSE;
15872
15873     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15874         DisplayError(_("Game list not loaded or empty"), 0);
15875         return;
15876     }
15877
15878     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15879         fclose(g);
15880         secondTime++;
15881         DisplayNote(_("Book file exists! Try again for overwrite."));
15882         return;
15883     }
15884
15885     creatingBook = TRUE;
15886     secondTime = FALSE;
15887
15888     /* Get list size */
15889     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15890         if(lg->position >= 0) {
15891             LoadGame(f, nItem, "", TRUE);
15892             AddGameToBook(TRUE);
15893         }
15894         lg = (ListGame *) lg->node.succ;
15895     }
15896
15897     creatingBook = FALSE;
15898     FlushBook();
15899 }
15900
15901 void
15902 BookEvent ()
15903 {
15904     if (appData.noChessProgram) return;
15905     switch (gameMode) {
15906       case MachinePlaysWhite:
15907         if (WhiteOnMove(forwardMostMove)) {
15908             DisplayError(_("Wait until your turn."), 0);
15909             return;
15910         }
15911         break;
15912       case BeginningOfGame:
15913       case MachinePlaysBlack:
15914         if (!WhiteOnMove(forwardMostMove)) {
15915             DisplayError(_("Wait until your turn."), 0);
15916             return;
15917         }
15918         break;
15919       case EditPosition:
15920         EditPositionDone(TRUE);
15921         break;
15922       case TwoMachinesPlay:
15923         return;
15924       default:
15925         break;
15926     }
15927     SendToProgram("bk\n", &first);
15928     bookOutput[0] = NULLCHAR;
15929     bookRequested = TRUE;
15930 }
15931
15932 void
15933 AboutGameEvent ()
15934 {
15935     char *tags = PGNTags(&gameInfo);
15936     TagsPopUp(tags, CmailMsg());
15937     free(tags);
15938 }
15939
15940 /* end button procedures */
15941
15942 void
15943 PrintPosition (FILE *fp, int move)
15944 {
15945     int i, j;
15946
15947     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15948         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15949             char c = PieceToChar(boards[move][i][j]);
15950             fputc(c == 'x' ? '.' : c, fp);
15951             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15952         }
15953     }
15954     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15955       fprintf(fp, "white to play\n");
15956     else
15957       fprintf(fp, "black to play\n");
15958 }
15959
15960 void
15961 PrintOpponents (FILE *fp)
15962 {
15963     if (gameInfo.white != NULL) {
15964         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15965     } else {
15966         fprintf(fp, "\n");
15967     }
15968 }
15969
15970 /* Find last component of program's own name, using some heuristics */
15971 void
15972 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15973 {
15974     char *p, *q, c;
15975     int local = (strcmp(host, "localhost") == 0);
15976     while (!local && (p = strchr(prog, ';')) != NULL) {
15977         p++;
15978         while (*p == ' ') p++;
15979         prog = p;
15980     }
15981     if (*prog == '"' || *prog == '\'') {
15982         q = strchr(prog + 1, *prog);
15983     } else {
15984         q = strchr(prog, ' ');
15985     }
15986     if (q == NULL) q = prog + strlen(prog);
15987     p = q;
15988     while (p >= prog && *p != '/' && *p != '\\') p--;
15989     p++;
15990     if(p == prog && *p == '"') p++;
15991     c = *q; *q = 0;
15992     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15993     memcpy(buf, p, q - p);
15994     buf[q - p] = NULLCHAR;
15995     if (!local) {
15996         strcat(buf, "@");
15997         strcat(buf, host);
15998     }
15999 }
16000
16001 char *
16002 TimeControlTagValue ()
16003 {
16004     char buf[MSG_SIZ];
16005     if (!appData.clockMode) {
16006       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16007     } else if (movesPerSession > 0) {
16008       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16009     } else if (timeIncrement == 0) {
16010       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16011     } else {
16012       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16013     }
16014     return StrSave(buf);
16015 }
16016
16017 void
16018 SetGameInfo ()
16019 {
16020     /* This routine is used only for certain modes */
16021     VariantClass v = gameInfo.variant;
16022     ChessMove r = GameUnfinished;
16023     char *p = NULL;
16024
16025     if(keepInfo) return;
16026
16027     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16028         r = gameInfo.result;
16029         p = gameInfo.resultDetails;
16030         gameInfo.resultDetails = NULL;
16031     }
16032     ClearGameInfo(&gameInfo);
16033     gameInfo.variant = v;
16034
16035     switch (gameMode) {
16036       case MachinePlaysWhite:
16037         gameInfo.event = StrSave( appData.pgnEventHeader );
16038         gameInfo.site = StrSave(HostName());
16039         gameInfo.date = PGNDate();
16040         gameInfo.round = StrSave("-");
16041         gameInfo.white = StrSave(first.tidy);
16042         gameInfo.black = StrSave(UserName());
16043         gameInfo.timeControl = TimeControlTagValue();
16044         break;
16045
16046       case MachinePlaysBlack:
16047         gameInfo.event = StrSave( appData.pgnEventHeader );
16048         gameInfo.site = StrSave(HostName());
16049         gameInfo.date = PGNDate();
16050         gameInfo.round = StrSave("-");
16051         gameInfo.white = StrSave(UserName());
16052         gameInfo.black = StrSave(first.tidy);
16053         gameInfo.timeControl = TimeControlTagValue();
16054         break;
16055
16056       case TwoMachinesPlay:
16057         gameInfo.event = StrSave( appData.pgnEventHeader );
16058         gameInfo.site = StrSave(HostName());
16059         gameInfo.date = PGNDate();
16060         if (roundNr > 0) {
16061             char buf[MSG_SIZ];
16062             snprintf(buf, MSG_SIZ, "%d", roundNr);
16063             gameInfo.round = StrSave(buf);
16064         } else {
16065             gameInfo.round = StrSave("-");
16066         }
16067         if (first.twoMachinesColor[0] == 'w') {
16068             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16069             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16070         } else {
16071             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16072             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16073         }
16074         gameInfo.timeControl = TimeControlTagValue();
16075         break;
16076
16077       case EditGame:
16078         gameInfo.event = StrSave("Edited game");
16079         gameInfo.site = StrSave(HostName());
16080         gameInfo.date = PGNDate();
16081         gameInfo.round = StrSave("-");
16082         gameInfo.white = StrSave("-");
16083         gameInfo.black = StrSave("-");
16084         gameInfo.result = r;
16085         gameInfo.resultDetails = p;
16086         break;
16087
16088       case EditPosition:
16089         gameInfo.event = StrSave("Edited position");
16090         gameInfo.site = StrSave(HostName());
16091         gameInfo.date = PGNDate();
16092         gameInfo.round = StrSave("-");
16093         gameInfo.white = StrSave("-");
16094         gameInfo.black = StrSave("-");
16095         break;
16096
16097       case IcsPlayingWhite:
16098       case IcsPlayingBlack:
16099       case IcsObserving:
16100       case IcsExamining:
16101         break;
16102
16103       case PlayFromGameFile:
16104         gameInfo.event = StrSave("Game from non-PGN file");
16105         gameInfo.site = StrSave(HostName());
16106         gameInfo.date = PGNDate();
16107         gameInfo.round = StrSave("-");
16108         gameInfo.white = StrSave("?");
16109         gameInfo.black = StrSave("?");
16110         break;
16111
16112       default:
16113         break;
16114     }
16115 }
16116
16117 void
16118 ReplaceComment (int index, char *text)
16119 {
16120     int len;
16121     char *p;
16122     float score;
16123
16124     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16125        pvInfoList[index-1].depth == len &&
16126        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16127        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16128     while (*text == '\n') text++;
16129     len = strlen(text);
16130     while (len > 0 && text[len - 1] == '\n') len--;
16131
16132     if (commentList[index] != NULL)
16133       free(commentList[index]);
16134
16135     if (len == 0) {
16136         commentList[index] = NULL;
16137         return;
16138     }
16139   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16140       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16141       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16142     commentList[index] = (char *) malloc(len + 2);
16143     strncpy(commentList[index], text, len);
16144     commentList[index][len] = '\n';
16145     commentList[index][len + 1] = NULLCHAR;
16146   } else {
16147     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16148     char *p;
16149     commentList[index] = (char *) malloc(len + 7);
16150     safeStrCpy(commentList[index], "{\n", 3);
16151     safeStrCpy(commentList[index]+2, text, len+1);
16152     commentList[index][len+2] = NULLCHAR;
16153     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16154     strcat(commentList[index], "\n}\n");
16155   }
16156 }
16157
16158 void
16159 CrushCRs (char *text)
16160 {
16161   char *p = text;
16162   char *q = text;
16163   char ch;
16164
16165   do {
16166     ch = *p++;
16167     if (ch == '\r') continue;
16168     *q++ = ch;
16169   } while (ch != '\0');
16170 }
16171
16172 void
16173 AppendComment (int index, char *text, Boolean addBraces)
16174 /* addBraces  tells if we should add {} */
16175 {
16176     int oldlen, len;
16177     char *old;
16178
16179 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16180     if(addBraces == 3) addBraces = 0; else // force appending literally
16181     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16182
16183     CrushCRs(text);
16184     while (*text == '\n') text++;
16185     len = strlen(text);
16186     while (len > 0 && text[len - 1] == '\n') len--;
16187     text[len] = NULLCHAR;
16188
16189     if (len == 0) return;
16190
16191     if (commentList[index] != NULL) {
16192       Boolean addClosingBrace = addBraces;
16193         old = commentList[index];
16194         oldlen = strlen(old);
16195         while(commentList[index][oldlen-1] ==  '\n')
16196           commentList[index][--oldlen] = NULLCHAR;
16197         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16198         safeStrCpy(commentList[index], old, oldlen + len + 6);
16199         free(old);
16200         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16201         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16202           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16203           while (*text == '\n') { text++; len--; }
16204           commentList[index][--oldlen] = NULLCHAR;
16205       }
16206         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16207         else          strcat(commentList[index], "\n");
16208         strcat(commentList[index], text);
16209         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16210         else          strcat(commentList[index], "\n");
16211     } else {
16212         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16213         if(addBraces)
16214           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16215         else commentList[index][0] = NULLCHAR;
16216         strcat(commentList[index], text);
16217         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16218         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16219     }
16220 }
16221
16222 static char *
16223 FindStr (char * text, char * sub_text)
16224 {
16225     char * result = strstr( text, sub_text );
16226
16227     if( result != NULL ) {
16228         result += strlen( sub_text );
16229     }
16230
16231     return result;
16232 }
16233
16234 /* [AS] Try to extract PV info from PGN comment */
16235 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16236 char *
16237 GetInfoFromComment (int index, char * text)
16238 {
16239     char * sep = text, *p;
16240
16241     if( text != NULL && index > 0 ) {
16242         int score = 0;
16243         int depth = 0;
16244         int time = -1, sec = 0, deci;
16245         char * s_eval = FindStr( text, "[%eval " );
16246         char * s_emt = FindStr( text, "[%emt " );
16247 #if 0
16248         if( s_eval != NULL || s_emt != NULL ) {
16249 #else
16250         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16251 #endif
16252             /* New style */
16253             char delim;
16254
16255             if( s_eval != NULL ) {
16256                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16257                     return text;
16258                 }
16259
16260                 if( delim != ']' ) {
16261                     return text;
16262                 }
16263             }
16264
16265             if( s_emt != NULL ) {
16266             }
16267                 return text;
16268         }
16269         else {
16270             /* We expect something like: [+|-]nnn.nn/dd */
16271             int score_lo = 0;
16272
16273             if(*text != '{') return text; // [HGM] braces: must be normal comment
16274
16275             sep = strchr( text, '/' );
16276             if( sep == NULL || sep < (text+4) ) {
16277                 return text;
16278             }
16279
16280             p = text;
16281             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16282             if(p[1] == '(') { // comment starts with PV
16283                p = strchr(p, ')'); // locate end of PV
16284                if(p == NULL || sep < p+5) return text;
16285                // at this point we have something like "{(.*) +0.23/6 ..."
16286                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16287                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16288                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16289             }
16290             time = -1; sec = -1; deci = -1;
16291             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16292                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16293                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16294                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16295                 return text;
16296             }
16297
16298             if( score_lo < 0 || score_lo >= 100 ) {
16299                 return text;
16300             }
16301
16302             if(sec >= 0) time = 600*time + 10*sec; else
16303             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16304
16305             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16306
16307             /* [HGM] PV time: now locate end of PV info */
16308             while( *++sep >= '0' && *sep <= '9'); // strip depth
16309             if(time >= 0)
16310             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16311             if(sec >= 0)
16312             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16313             if(deci >= 0)
16314             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16315             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16316         }
16317
16318         if( depth <= 0 ) {
16319             return text;
16320         }
16321
16322         if( time < 0 ) {
16323             time = -1;
16324         }
16325
16326         pvInfoList[index-1].depth = depth;
16327         pvInfoList[index-1].score = score;
16328         pvInfoList[index-1].time  = 10*time; // centi-sec
16329         if(*sep == '}') *sep = 0; else *--sep = '{';
16330         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16331     }
16332     return sep;
16333 }
16334
16335 void
16336 SendToProgram (char *message, ChessProgramState *cps)
16337 {
16338     int count, outCount, error;
16339     char buf[MSG_SIZ];
16340
16341     if (cps->pr == NoProc) return;
16342     Attention(cps);
16343
16344     if (appData.debugMode) {
16345         TimeMark now;
16346         GetTimeMark(&now);
16347         fprintf(debugFP, "%ld >%-6s: %s",
16348                 SubtractTimeMarks(&now, &programStartTime),
16349                 cps->which, message);
16350         if(serverFP)
16351             fprintf(serverFP, "%ld >%-6s: %s",
16352                 SubtractTimeMarks(&now, &programStartTime),
16353                 cps->which, message), fflush(serverFP);
16354     }
16355
16356     count = strlen(message);
16357     outCount = OutputToProcess(cps->pr, message, count, &error);
16358     if (outCount < count && !exiting
16359                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16360       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16361       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16362         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16363             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16364                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16365                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16366                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16367             } else {
16368                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16369                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16370                 gameInfo.result = res;
16371             }
16372             gameInfo.resultDetails = StrSave(buf);
16373         }
16374         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16375         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16376     }
16377 }
16378
16379 void
16380 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16381 {
16382     char *end_str;
16383     char buf[MSG_SIZ];
16384     ChessProgramState *cps = (ChessProgramState *)closure;
16385
16386     if (isr != cps->isr) return; /* Killed intentionally */
16387     if (count <= 0) {
16388         if (count == 0) {
16389             RemoveInputSource(cps->isr);
16390             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16391                     _(cps->which), cps->program);
16392             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16393             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16394                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16395                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16396                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16397                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16398                 } else {
16399                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16400                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16401                     gameInfo.result = res;
16402                 }
16403                 gameInfo.resultDetails = StrSave(buf);
16404             }
16405             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16406             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16407         } else {
16408             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16409                     _(cps->which), cps->program);
16410             RemoveInputSource(cps->isr);
16411
16412             /* [AS] Program is misbehaving badly... kill it */
16413             if( count == -2 ) {
16414                 DestroyChildProcess( cps->pr, 9 );
16415                 cps->pr = NoProc;
16416             }
16417
16418             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16419         }
16420         return;
16421     }
16422
16423     if ((end_str = strchr(message, '\r')) != NULL)
16424       *end_str = NULLCHAR;
16425     if ((end_str = strchr(message, '\n')) != NULL)
16426       *end_str = NULLCHAR;
16427
16428     if (appData.debugMode) {
16429         TimeMark now; int print = 1;
16430         char *quote = ""; char c; int i;
16431
16432         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16433                 char start = message[0];
16434                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16435                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16436                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16437                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16438                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16439                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16440                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16441                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16442                    sscanf(message, "hint: %c", &c)!=1 &&
16443                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16444                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16445                     print = (appData.engineComments >= 2);
16446                 }
16447                 message[0] = start; // restore original message
16448         }
16449         if(print) {
16450                 GetTimeMark(&now);
16451                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16452                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16453                         quote,
16454                         message);
16455                 if(serverFP)
16456                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16457                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16458                         quote,
16459                         message), fflush(serverFP);
16460         }
16461     }
16462
16463     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16464     if (appData.icsEngineAnalyze) {
16465         if (strstr(message, "whisper") != NULL ||
16466              strstr(message, "kibitz") != NULL ||
16467             strstr(message, "tellics") != NULL) return;
16468     }
16469
16470     HandleMachineMove(message, cps);
16471 }
16472
16473
16474 void
16475 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16476 {
16477     char buf[MSG_SIZ];
16478     int seconds;
16479
16480     if( timeControl_2 > 0 ) {
16481         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16482             tc = timeControl_2;
16483         }
16484     }
16485     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16486     inc /= cps->timeOdds;
16487     st  /= cps->timeOdds;
16488
16489     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16490
16491     if (st > 0) {
16492       /* Set exact time per move, normally using st command */
16493       if (cps->stKludge) {
16494         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16495         seconds = st % 60;
16496         if (seconds == 0) {
16497           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16498         } else {
16499           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16500         }
16501       } else {
16502         snprintf(buf, MSG_SIZ, "st %d\n", st);
16503       }
16504     } else {
16505       /* Set conventional or incremental time control, using level command */
16506       if (seconds == 0) {
16507         /* Note old gnuchess bug -- minutes:seconds used to not work.
16508            Fixed in later versions, but still avoid :seconds
16509            when seconds is 0. */
16510         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16511       } else {
16512         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16513                  seconds, inc/1000.);
16514       }
16515     }
16516     SendToProgram(buf, cps);
16517
16518     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16519     /* Orthogonally, limit search to given depth */
16520     if (sd > 0) {
16521       if (cps->sdKludge) {
16522         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16523       } else {
16524         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16525       }
16526       SendToProgram(buf, cps);
16527     }
16528
16529     if(cps->nps >= 0) { /* [HGM] nps */
16530         if(cps->supportsNPS == FALSE)
16531           cps->nps = -1; // don't use if engine explicitly says not supported!
16532         else {
16533           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16534           SendToProgram(buf, cps);
16535         }
16536     }
16537 }
16538
16539 ChessProgramState *
16540 WhitePlayer ()
16541 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16542 {
16543     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16544        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16545         return &second;
16546     return &first;
16547 }
16548
16549 void
16550 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16551 {
16552     char message[MSG_SIZ];
16553     long time, otime;
16554
16555     /* Note: this routine must be called when the clocks are stopped
16556        or when they have *just* been set or switched; otherwise
16557        it will be off by the time since the current tick started.
16558     */
16559     if (machineWhite) {
16560         time = whiteTimeRemaining / 10;
16561         otime = blackTimeRemaining / 10;
16562     } else {
16563         time = blackTimeRemaining / 10;
16564         otime = whiteTimeRemaining / 10;
16565     }
16566     /* [HGM] translate opponent's time by time-odds factor */
16567     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16568
16569     if (time <= 0) time = 1;
16570     if (otime <= 0) otime = 1;
16571
16572     snprintf(message, MSG_SIZ, "time %ld\n", time);
16573     SendToProgram(message, cps);
16574
16575     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16576     SendToProgram(message, cps);
16577 }
16578
16579 char *
16580 EngineDefinedVariant (ChessProgramState *cps, int n)
16581 {   // return name of n-th unknown variant that engine supports
16582     static char buf[MSG_SIZ];
16583     char *p, *s = cps->variants;
16584     if(!s) return NULL;
16585     do { // parse string from variants feature
16586       VariantClass v;
16587         p = strchr(s, ',');
16588         if(p) *p = NULLCHAR;
16589       v = StringToVariant(s);
16590       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16591         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16592             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16593         }
16594         if(p) *p++ = ',';
16595         if(n < 0) return buf;
16596     } while(s = p);
16597     return NULL;
16598 }
16599
16600 int
16601 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16602 {
16603   char buf[MSG_SIZ];
16604   int len = strlen(name);
16605   int val;
16606
16607   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16608     (*p) += len + 1;
16609     sscanf(*p, "%d", &val);
16610     *loc = (val != 0);
16611     while (**p && **p != ' ')
16612       (*p)++;
16613     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16614     SendToProgram(buf, cps);
16615     return TRUE;
16616   }
16617   return FALSE;
16618 }
16619
16620 int
16621 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16622 {
16623   char buf[MSG_SIZ];
16624   int len = strlen(name);
16625   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16626     (*p) += len + 1;
16627     sscanf(*p, "%d", loc);
16628     while (**p && **p != ' ') (*p)++;
16629     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16630     SendToProgram(buf, cps);
16631     return TRUE;
16632   }
16633   return FALSE;
16634 }
16635
16636 int
16637 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16638 {
16639   char buf[MSG_SIZ];
16640   int len = strlen(name);
16641   if (strncmp((*p), name, len) == 0
16642       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16643     (*p) += len + 2;
16644     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16645     sscanf(*p, "%[^\"]", *loc);
16646     while (**p && **p != '\"') (*p)++;
16647     if (**p == '\"') (*p)++;
16648     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16649     SendToProgram(buf, cps);
16650     return TRUE;
16651   }
16652   return FALSE;
16653 }
16654
16655 int
16656 ParseOption (Option *opt, ChessProgramState *cps)
16657 // [HGM] options: process the string that defines an engine option, and determine
16658 // name, type, default value, and allowed value range
16659 {
16660         char *p, *q, buf[MSG_SIZ];
16661         int n, min = (-1)<<31, max = 1<<31, def;
16662
16663         if(p = strstr(opt->name, " -spin ")) {
16664             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16665             if(max < min) max = min; // enforce consistency
16666             if(def < min) def = min;
16667             if(def > max) def = max;
16668             opt->value = def;
16669             opt->min = min;
16670             opt->max = max;
16671             opt->type = Spin;
16672         } else if((p = strstr(opt->name, " -slider "))) {
16673             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16674             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16675             if(max < min) max = min; // enforce consistency
16676             if(def < min) def = min;
16677             if(def > max) def = max;
16678             opt->value = def;
16679             opt->min = min;
16680             opt->max = max;
16681             opt->type = Spin; // Slider;
16682         } else if((p = strstr(opt->name, " -string "))) {
16683             opt->textValue = p+9;
16684             opt->type = TextBox;
16685         } else if((p = strstr(opt->name, " -file "))) {
16686             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16687             opt->textValue = p+7;
16688             opt->type = FileName; // FileName;
16689         } else if((p = strstr(opt->name, " -path "))) {
16690             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16691             opt->textValue = p+7;
16692             opt->type = PathName; // PathName;
16693         } else if(p = strstr(opt->name, " -check ")) {
16694             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16695             opt->value = (def != 0);
16696             opt->type = CheckBox;
16697         } else if(p = strstr(opt->name, " -combo ")) {
16698             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16699             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16700             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16701             opt->value = n = 0;
16702             while(q = StrStr(q, " /// ")) {
16703                 n++; *q = 0;    // count choices, and null-terminate each of them
16704                 q += 5;
16705                 if(*q == '*') { // remember default, which is marked with * prefix
16706                     q++;
16707                     opt->value = n;
16708                 }
16709                 cps->comboList[cps->comboCnt++] = q;
16710             }
16711             cps->comboList[cps->comboCnt++] = NULL;
16712             opt->max = n + 1;
16713             opt->type = ComboBox;
16714         } else if(p = strstr(opt->name, " -button")) {
16715             opt->type = Button;
16716         } else if(p = strstr(opt->name, " -save")) {
16717             opt->type = SaveButton;
16718         } else return FALSE;
16719         *p = 0; // terminate option name
16720         // now look if the command-line options define a setting for this engine option.
16721         if(cps->optionSettings && cps->optionSettings[0])
16722             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16723         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16724           snprintf(buf, MSG_SIZ, "option %s", p);
16725                 if(p = strstr(buf, ",")) *p = 0;
16726                 if(q = strchr(buf, '=')) switch(opt->type) {
16727                     case ComboBox:
16728                         for(n=0; n<opt->max; n++)
16729                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16730                         break;
16731                     case TextBox:
16732                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16733                         break;
16734                     case Spin:
16735                     case CheckBox:
16736                         opt->value = atoi(q+1);
16737                     default:
16738                         break;
16739                 }
16740                 strcat(buf, "\n");
16741                 SendToProgram(buf, cps);
16742         }
16743         return TRUE;
16744 }
16745
16746 void
16747 FeatureDone (ChessProgramState *cps, int val)
16748 {
16749   DelayedEventCallback cb = GetDelayedEvent();
16750   if ((cb == InitBackEnd3 && cps == &first) ||
16751       (cb == SettingsMenuIfReady && cps == &second) ||
16752       (cb == LoadEngine) ||
16753       (cb == TwoMachinesEventIfReady)) {
16754     CancelDelayedEvent();
16755     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16756   }
16757   cps->initDone = val;
16758   if(val) cps->reload = FALSE;
16759 }
16760
16761 /* Parse feature command from engine */
16762 void
16763 ParseFeatures (char *args, ChessProgramState *cps)
16764 {
16765   char *p = args;
16766   char *q = NULL;
16767   int val;
16768   char buf[MSG_SIZ];
16769
16770   for (;;) {
16771     while (*p == ' ') p++;
16772     if (*p == NULLCHAR) return;
16773
16774     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16775     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16776     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16777     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16778     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16779     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16780     if (BoolFeature(&p, "reuse", &val, cps)) {
16781       /* Engine can disable reuse, but can't enable it if user said no */
16782       if (!val) cps->reuse = FALSE;
16783       continue;
16784     }
16785     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16786     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16787       if (gameMode == TwoMachinesPlay) {
16788         DisplayTwoMachinesTitle();
16789       } else {
16790         DisplayTitle("");
16791       }
16792       continue;
16793     }
16794     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16795     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16796     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16797     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16798     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16799     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16800     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16801     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16802     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16803     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16804     if (IntFeature(&p, "done", &val, cps)) {
16805       FeatureDone(cps, val);
16806       continue;
16807     }
16808     /* Added by Tord: */
16809     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16810     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16811     /* End of additions by Tord */
16812
16813     /* [HGM] added features: */
16814     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16815     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16816     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16817     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16818     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16819     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16820     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16821     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16822         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16823         FREE(cps->option[cps->nrOptions].name);
16824         cps->option[cps->nrOptions].name = q; q = NULL;
16825         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16826           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16827             SendToProgram(buf, cps);
16828             continue;
16829         }
16830         if(cps->nrOptions >= MAX_OPTIONS) {
16831             cps->nrOptions--;
16832             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16833             DisplayError(buf, 0);
16834         }
16835         continue;
16836     }
16837     /* End of additions by HGM */
16838
16839     /* unknown feature: complain and skip */
16840     q = p;
16841     while (*q && *q != '=') q++;
16842     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16843     SendToProgram(buf, cps);
16844     p = q;
16845     if (*p == '=') {
16846       p++;
16847       if (*p == '\"') {
16848         p++;
16849         while (*p && *p != '\"') p++;
16850         if (*p == '\"') p++;
16851       } else {
16852         while (*p && *p != ' ') p++;
16853       }
16854     }
16855   }
16856
16857 }
16858
16859 void
16860 PeriodicUpdatesEvent (int newState)
16861 {
16862     if (newState == appData.periodicUpdates)
16863       return;
16864
16865     appData.periodicUpdates=newState;
16866
16867     /* Display type changes, so update it now */
16868 //    DisplayAnalysis();
16869
16870     /* Get the ball rolling again... */
16871     if (newState) {
16872         AnalysisPeriodicEvent(1);
16873         StartAnalysisClock();
16874     }
16875 }
16876
16877 void
16878 PonderNextMoveEvent (int newState)
16879 {
16880     if (newState == appData.ponderNextMove) return;
16881     if (gameMode == EditPosition) EditPositionDone(TRUE);
16882     if (newState) {
16883         SendToProgram("hard\n", &first);
16884         if (gameMode == TwoMachinesPlay) {
16885             SendToProgram("hard\n", &second);
16886         }
16887     } else {
16888         SendToProgram("easy\n", &first);
16889         thinkOutput[0] = NULLCHAR;
16890         if (gameMode == TwoMachinesPlay) {
16891             SendToProgram("easy\n", &second);
16892         }
16893     }
16894     appData.ponderNextMove = newState;
16895 }
16896
16897 void
16898 NewSettingEvent (int option, int *feature, char *command, int value)
16899 {
16900     char buf[MSG_SIZ];
16901
16902     if (gameMode == EditPosition) EditPositionDone(TRUE);
16903     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16904     if(feature == NULL || *feature) SendToProgram(buf, &first);
16905     if (gameMode == TwoMachinesPlay) {
16906         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16907     }
16908 }
16909
16910 void
16911 ShowThinkingEvent ()
16912 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16913 {
16914     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16915     int newState = appData.showThinking
16916         // [HGM] thinking: other features now need thinking output as well
16917         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16918
16919     if (oldState == newState) return;
16920     oldState = newState;
16921     if (gameMode == EditPosition) EditPositionDone(TRUE);
16922     if (oldState) {
16923         SendToProgram("post\n", &first);
16924         if (gameMode == TwoMachinesPlay) {
16925             SendToProgram("post\n", &second);
16926         }
16927     } else {
16928         SendToProgram("nopost\n", &first);
16929         thinkOutput[0] = NULLCHAR;
16930         if (gameMode == TwoMachinesPlay) {
16931             SendToProgram("nopost\n", &second);
16932         }
16933     }
16934 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16935 }
16936
16937 void
16938 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16939 {
16940   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16941   if (pr == NoProc) return;
16942   AskQuestion(title, question, replyPrefix, pr);
16943 }
16944
16945 void
16946 TypeInEvent (char firstChar)
16947 {
16948     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16949         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16950         gameMode == AnalyzeMode || gameMode == EditGame ||
16951         gameMode == EditPosition || gameMode == IcsExamining ||
16952         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16953         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16954                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16955                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16956         gameMode == Training) PopUpMoveDialog(firstChar);
16957 }
16958
16959 void
16960 TypeInDoneEvent (char *move)
16961 {
16962         Board board;
16963         int n, fromX, fromY, toX, toY;
16964         char promoChar;
16965         ChessMove moveType;
16966
16967         // [HGM] FENedit
16968         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16969                 EditPositionPasteFEN(move);
16970                 return;
16971         }
16972         // [HGM] movenum: allow move number to be typed in any mode
16973         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16974           ToNrEvent(2*n-1);
16975           return;
16976         }
16977         // undocumented kludge: allow command-line option to be typed in!
16978         // (potentially fatal, and does not implement the effect of the option.)
16979         // should only be used for options that are values on which future decisions will be made,
16980         // and definitely not on options that would be used during initialization.
16981         if(strstr(move, "!!! -") == move) {
16982             ParseArgsFromString(move+4);
16983             return;
16984         }
16985
16986       if (gameMode != EditGame && currentMove != forwardMostMove &&
16987         gameMode != Training) {
16988         DisplayMoveError(_("Displayed move is not current"));
16989       } else {
16990         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16991           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16992         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16993         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16994           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16995           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16996         } else {
16997           DisplayMoveError(_("Could not parse move"));
16998         }
16999       }
17000 }
17001
17002 void
17003 DisplayMove (int moveNumber)
17004 {
17005     char message[MSG_SIZ];
17006     char res[MSG_SIZ];
17007     char cpThinkOutput[MSG_SIZ];
17008
17009     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17010
17011     if (moveNumber == forwardMostMove - 1 ||
17012         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17013
17014         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17015
17016         if (strchr(cpThinkOutput, '\n')) {
17017             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17018         }
17019     } else {
17020         *cpThinkOutput = NULLCHAR;
17021     }
17022
17023     /* [AS] Hide thinking from human user */
17024     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17025         *cpThinkOutput = NULLCHAR;
17026         if( thinkOutput[0] != NULLCHAR ) {
17027             int i;
17028
17029             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17030                 cpThinkOutput[i] = '.';
17031             }
17032             cpThinkOutput[i] = NULLCHAR;
17033             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17034         }
17035     }
17036
17037     if (moveNumber == forwardMostMove - 1 &&
17038         gameInfo.resultDetails != NULL) {
17039         if (gameInfo.resultDetails[0] == NULLCHAR) {
17040           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17041         } else {
17042           snprintf(res, MSG_SIZ, " {%s} %s",
17043                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17044         }
17045     } else {
17046         res[0] = NULLCHAR;
17047     }
17048
17049     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17050         DisplayMessage(res, cpThinkOutput);
17051     } else {
17052       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17053                 WhiteOnMove(moveNumber) ? " " : ".. ",
17054                 parseList[moveNumber], res);
17055         DisplayMessage(message, cpThinkOutput);
17056     }
17057 }
17058
17059 void
17060 DisplayComment (int moveNumber, char *text)
17061 {
17062     char title[MSG_SIZ];
17063
17064     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17065       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17066     } else {
17067       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17068               WhiteOnMove(moveNumber) ? " " : ".. ",
17069               parseList[moveNumber]);
17070     }
17071     if (text != NULL && (appData.autoDisplayComment || commentUp))
17072         CommentPopUp(title, text);
17073 }
17074
17075 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17076  * might be busy thinking or pondering.  It can be omitted if your
17077  * gnuchess is configured to stop thinking immediately on any user
17078  * input.  However, that gnuchess feature depends on the FIONREAD
17079  * ioctl, which does not work properly on some flavors of Unix.
17080  */
17081 void
17082 Attention (ChessProgramState *cps)
17083 {
17084 #if ATTENTION
17085     if (!cps->useSigint) return;
17086     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17087     switch (gameMode) {
17088       case MachinePlaysWhite:
17089       case MachinePlaysBlack:
17090       case TwoMachinesPlay:
17091       case IcsPlayingWhite:
17092       case IcsPlayingBlack:
17093       case AnalyzeMode:
17094       case AnalyzeFile:
17095         /* Skip if we know it isn't thinking */
17096         if (!cps->maybeThinking) return;
17097         if (appData.debugMode)
17098           fprintf(debugFP, "Interrupting %s\n", cps->which);
17099         InterruptChildProcess(cps->pr);
17100         cps->maybeThinking = FALSE;
17101         break;
17102       default:
17103         break;
17104     }
17105 #endif /*ATTENTION*/
17106 }
17107
17108 int
17109 CheckFlags ()
17110 {
17111     if (whiteTimeRemaining <= 0) {
17112         if (!whiteFlag) {
17113             whiteFlag = TRUE;
17114             if (appData.icsActive) {
17115                 if (appData.autoCallFlag &&
17116                     gameMode == IcsPlayingBlack && !blackFlag) {
17117                   SendToICS(ics_prefix);
17118                   SendToICS("flag\n");
17119                 }
17120             } else {
17121                 if (blackFlag) {
17122                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17123                 } else {
17124                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17125                     if (appData.autoCallFlag) {
17126                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17127                         return TRUE;
17128                     }
17129                 }
17130             }
17131         }
17132     }
17133     if (blackTimeRemaining <= 0) {
17134         if (!blackFlag) {
17135             blackFlag = TRUE;
17136             if (appData.icsActive) {
17137                 if (appData.autoCallFlag &&
17138                     gameMode == IcsPlayingWhite && !whiteFlag) {
17139                   SendToICS(ics_prefix);
17140                   SendToICS("flag\n");
17141                 }
17142             } else {
17143                 if (whiteFlag) {
17144                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17145                 } else {
17146                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17147                     if (appData.autoCallFlag) {
17148                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17149                         return TRUE;
17150                     }
17151                 }
17152             }
17153         }
17154     }
17155     return FALSE;
17156 }
17157
17158 void
17159 CheckTimeControl ()
17160 {
17161     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17162         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17163
17164     /*
17165      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17166      */
17167     if ( !WhiteOnMove(forwardMostMove) ) {
17168         /* White made time control */
17169         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17170         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17171         /* [HGM] time odds: correct new time quota for time odds! */
17172                                             / WhitePlayer()->timeOdds;
17173         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17174     } else {
17175         lastBlack -= blackTimeRemaining;
17176         /* Black made time control */
17177         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17178                                             / WhitePlayer()->other->timeOdds;
17179         lastWhite = whiteTimeRemaining;
17180     }
17181 }
17182
17183 void
17184 DisplayBothClocks ()
17185 {
17186     int wom = gameMode == EditPosition ?
17187       !blackPlaysFirst : WhiteOnMove(currentMove);
17188     DisplayWhiteClock(whiteTimeRemaining, wom);
17189     DisplayBlackClock(blackTimeRemaining, !wom);
17190 }
17191
17192
17193 /* Timekeeping seems to be a portability nightmare.  I think everyone
17194    has ftime(), but I'm really not sure, so I'm including some ifdefs
17195    to use other calls if you don't.  Clocks will be less accurate if
17196    you have neither ftime nor gettimeofday.
17197 */
17198
17199 /* VS 2008 requires the #include outside of the function */
17200 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17201 #include <sys/timeb.h>
17202 #endif
17203
17204 /* Get the current time as a TimeMark */
17205 void
17206 GetTimeMark (TimeMark *tm)
17207 {
17208 #if HAVE_GETTIMEOFDAY
17209
17210     struct timeval timeVal;
17211     struct timezone timeZone;
17212
17213     gettimeofday(&timeVal, &timeZone);
17214     tm->sec = (long) timeVal.tv_sec;
17215     tm->ms = (int) (timeVal.tv_usec / 1000L);
17216
17217 #else /*!HAVE_GETTIMEOFDAY*/
17218 #if HAVE_FTIME
17219
17220 // include <sys/timeb.h> / moved to just above start of function
17221     struct timeb timeB;
17222
17223     ftime(&timeB);
17224     tm->sec = (long) timeB.time;
17225     tm->ms = (int) timeB.millitm;
17226
17227 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17228     tm->sec = (long) time(NULL);
17229     tm->ms = 0;
17230 #endif
17231 #endif
17232 }
17233
17234 /* Return the difference in milliseconds between two
17235    time marks.  We assume the difference will fit in a long!
17236 */
17237 long
17238 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17239 {
17240     return 1000L*(tm2->sec - tm1->sec) +
17241            (long) (tm2->ms - tm1->ms);
17242 }
17243
17244
17245 /*
17246  * Code to manage the game clocks.
17247  *
17248  * In tournament play, black starts the clock and then white makes a move.
17249  * We give the human user a slight advantage if he is playing white---the
17250  * clocks don't run until he makes his first move, so it takes zero time.
17251  * Also, we don't account for network lag, so we could get out of sync
17252  * with GNU Chess's clock -- but then, referees are always right.
17253  */
17254
17255 static TimeMark tickStartTM;
17256 static long intendedTickLength;
17257
17258 long
17259 NextTickLength (long timeRemaining)
17260 {
17261     long nominalTickLength, nextTickLength;
17262
17263     if (timeRemaining > 0L && timeRemaining <= 10000L)
17264       nominalTickLength = 100L;
17265     else
17266       nominalTickLength = 1000L;
17267     nextTickLength = timeRemaining % nominalTickLength;
17268     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17269
17270     return nextTickLength;
17271 }
17272
17273 /* Adjust clock one minute up or down */
17274 void
17275 AdjustClock (Boolean which, int dir)
17276 {
17277     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17278     if(which) blackTimeRemaining += 60000*dir;
17279     else      whiteTimeRemaining += 60000*dir;
17280     DisplayBothClocks();
17281     adjustedClock = TRUE;
17282 }
17283
17284 /* Stop clocks and reset to a fresh time control */
17285 void
17286 ResetClocks ()
17287 {
17288     (void) StopClockTimer();
17289     if (appData.icsActive) {
17290         whiteTimeRemaining = blackTimeRemaining = 0;
17291     } else if (searchTime) {
17292         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17293         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17294     } else { /* [HGM] correct new time quote for time odds */
17295         whiteTC = blackTC = fullTimeControlString;
17296         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17297         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17298     }
17299     if (whiteFlag || blackFlag) {
17300         DisplayTitle("");
17301         whiteFlag = blackFlag = FALSE;
17302     }
17303     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17304     DisplayBothClocks();
17305     adjustedClock = FALSE;
17306 }
17307
17308 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17309
17310 /* Decrement running clock by amount of time that has passed */
17311 void
17312 DecrementClocks ()
17313 {
17314     long timeRemaining;
17315     long lastTickLength, fudge;
17316     TimeMark now;
17317
17318     if (!appData.clockMode) return;
17319     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17320
17321     GetTimeMark(&now);
17322
17323     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17324
17325     /* Fudge if we woke up a little too soon */
17326     fudge = intendedTickLength - lastTickLength;
17327     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17328
17329     if (WhiteOnMove(forwardMostMove)) {
17330         if(whiteNPS >= 0) lastTickLength = 0;
17331         timeRemaining = whiteTimeRemaining -= lastTickLength;
17332         if(timeRemaining < 0 && !appData.icsActive) {
17333             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17334             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17335                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17336                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17337             }
17338         }
17339         DisplayWhiteClock(whiteTimeRemaining - fudge,
17340                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17341     } else {
17342         if(blackNPS >= 0) lastTickLength = 0;
17343         timeRemaining = blackTimeRemaining -= lastTickLength;
17344         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17345             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17346             if(suddenDeath) {
17347                 blackStartMove = forwardMostMove;
17348                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17349             }
17350         }
17351         DisplayBlackClock(blackTimeRemaining - fudge,
17352                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17353     }
17354     if (CheckFlags()) return;
17355
17356     if(twoBoards) { // count down secondary board's clocks as well
17357         activePartnerTime -= lastTickLength;
17358         partnerUp = 1;
17359         if(activePartner == 'W')
17360             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17361         else
17362             DisplayBlackClock(activePartnerTime, TRUE);
17363         partnerUp = 0;
17364     }
17365
17366     tickStartTM = now;
17367     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17368     StartClockTimer(intendedTickLength);
17369
17370     /* if the time remaining has fallen below the alarm threshold, sound the
17371      * alarm. if the alarm has sounded and (due to a takeback or time control
17372      * with increment) the time remaining has increased to a level above the
17373      * threshold, reset the alarm so it can sound again.
17374      */
17375
17376     if (appData.icsActive && appData.icsAlarm) {
17377
17378         /* make sure we are dealing with the user's clock */
17379         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17380                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17381            )) return;
17382
17383         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17384             alarmSounded = FALSE;
17385         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17386             PlayAlarmSound();
17387             alarmSounded = TRUE;
17388         }
17389     }
17390 }
17391
17392
17393 /* A player has just moved, so stop the previously running
17394    clock and (if in clock mode) start the other one.
17395    We redisplay both clocks in case we're in ICS mode, because
17396    ICS gives us an update to both clocks after every move.
17397    Note that this routine is called *after* forwardMostMove
17398    is updated, so the last fractional tick must be subtracted
17399    from the color that is *not* on move now.
17400 */
17401 void
17402 SwitchClocks (int newMoveNr)
17403 {
17404     long lastTickLength;
17405     TimeMark now;
17406     int flagged = FALSE;
17407
17408     GetTimeMark(&now);
17409
17410     if (StopClockTimer() && appData.clockMode) {
17411         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17412         if (!WhiteOnMove(forwardMostMove)) {
17413             if(blackNPS >= 0) lastTickLength = 0;
17414             blackTimeRemaining -= lastTickLength;
17415            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17416 //         if(pvInfoList[forwardMostMove].time == -1)
17417                  pvInfoList[forwardMostMove].time =               // use GUI time
17418                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17419         } else {
17420            if(whiteNPS >= 0) lastTickLength = 0;
17421            whiteTimeRemaining -= lastTickLength;
17422            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17423 //         if(pvInfoList[forwardMostMove].time == -1)
17424                  pvInfoList[forwardMostMove].time =
17425                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17426         }
17427         flagged = CheckFlags();
17428     }
17429     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17430     CheckTimeControl();
17431
17432     if (flagged || !appData.clockMode) return;
17433
17434     switch (gameMode) {
17435       case MachinePlaysBlack:
17436       case MachinePlaysWhite:
17437       case BeginningOfGame:
17438         if (pausing) return;
17439         break;
17440
17441       case EditGame:
17442       case PlayFromGameFile:
17443       case IcsExamining:
17444         return;
17445
17446       default:
17447         break;
17448     }
17449
17450     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17451         if(WhiteOnMove(forwardMostMove))
17452              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17453         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17454     }
17455
17456     tickStartTM = now;
17457     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17458       whiteTimeRemaining : blackTimeRemaining);
17459     StartClockTimer(intendedTickLength);
17460 }
17461
17462
17463 /* Stop both clocks */
17464 void
17465 StopClocks ()
17466 {
17467     long lastTickLength;
17468     TimeMark now;
17469
17470     if (!StopClockTimer()) return;
17471     if (!appData.clockMode) return;
17472
17473     GetTimeMark(&now);
17474
17475     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17476     if (WhiteOnMove(forwardMostMove)) {
17477         if(whiteNPS >= 0) lastTickLength = 0;
17478         whiteTimeRemaining -= lastTickLength;
17479         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17480     } else {
17481         if(blackNPS >= 0) lastTickLength = 0;
17482         blackTimeRemaining -= lastTickLength;
17483         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17484     }
17485     CheckFlags();
17486 }
17487
17488 /* Start clock of player on move.  Time may have been reset, so
17489    if clock is already running, stop and restart it. */
17490 void
17491 StartClocks ()
17492 {
17493     (void) StopClockTimer(); /* in case it was running already */
17494     DisplayBothClocks();
17495     if (CheckFlags()) return;
17496
17497     if (!appData.clockMode) return;
17498     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17499
17500     GetTimeMark(&tickStartTM);
17501     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17502       whiteTimeRemaining : blackTimeRemaining);
17503
17504    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17505     whiteNPS = blackNPS = -1;
17506     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17507        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17508         whiteNPS = first.nps;
17509     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17510        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17511         blackNPS = first.nps;
17512     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17513         whiteNPS = second.nps;
17514     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17515         blackNPS = second.nps;
17516     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17517
17518     StartClockTimer(intendedTickLength);
17519 }
17520
17521 char *
17522 TimeString (long ms)
17523 {
17524     long second, minute, hour, day;
17525     char *sign = "";
17526     static char buf[32];
17527
17528     if (ms > 0 && ms <= 9900) {
17529       /* convert milliseconds to tenths, rounding up */
17530       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17531
17532       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17533       return buf;
17534     }
17535
17536     /* convert milliseconds to seconds, rounding up */
17537     /* use floating point to avoid strangeness of integer division
17538        with negative dividends on many machines */
17539     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17540
17541     if (second < 0) {
17542         sign = "-";
17543         second = -second;
17544     }
17545
17546     day = second / (60 * 60 * 24);
17547     second = second % (60 * 60 * 24);
17548     hour = second / (60 * 60);
17549     second = second % (60 * 60);
17550     minute = second / 60;
17551     second = second % 60;
17552
17553     if (day > 0)
17554       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17555               sign, day, hour, minute, second);
17556     else if (hour > 0)
17557       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17558     else
17559       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17560
17561     return buf;
17562 }
17563
17564
17565 /*
17566  * This is necessary because some C libraries aren't ANSI C compliant yet.
17567  */
17568 char *
17569 StrStr (char *string, char *match)
17570 {
17571     int i, length;
17572
17573     length = strlen(match);
17574
17575     for (i = strlen(string) - length; i >= 0; i--, string++)
17576       if (!strncmp(match, string, length))
17577         return string;
17578
17579     return NULL;
17580 }
17581
17582 char *
17583 StrCaseStr (char *string, char *match)
17584 {
17585     int i, j, length;
17586
17587     length = strlen(match);
17588
17589     for (i = strlen(string) - length; i >= 0; i--, string++) {
17590         for (j = 0; j < length; j++) {
17591             if (ToLower(match[j]) != ToLower(string[j]))
17592               break;
17593         }
17594         if (j == length) return string;
17595     }
17596
17597     return NULL;
17598 }
17599
17600 #ifndef _amigados
17601 int
17602 StrCaseCmp (char *s1, char *s2)
17603 {
17604     char c1, c2;
17605
17606     for (;;) {
17607         c1 = ToLower(*s1++);
17608         c2 = ToLower(*s2++);
17609         if (c1 > c2) return 1;
17610         if (c1 < c2) return -1;
17611         if (c1 == NULLCHAR) return 0;
17612     }
17613 }
17614
17615
17616 int
17617 ToLower (int c)
17618 {
17619     return isupper(c) ? tolower(c) : c;
17620 }
17621
17622
17623 int
17624 ToUpper (int c)
17625 {
17626     return islower(c) ? toupper(c) : c;
17627 }
17628 #endif /* !_amigados    */
17629
17630 char *
17631 StrSave (char *s)
17632 {
17633   char *ret;
17634
17635   if ((ret = (char *) malloc(strlen(s) + 1)))
17636     {
17637       safeStrCpy(ret, s, strlen(s)+1);
17638     }
17639   return ret;
17640 }
17641
17642 char *
17643 StrSavePtr (char *s, char **savePtr)
17644 {
17645     if (*savePtr) {
17646         free(*savePtr);
17647     }
17648     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17649       safeStrCpy(*savePtr, s, strlen(s)+1);
17650     }
17651     return(*savePtr);
17652 }
17653
17654 char *
17655 PGNDate ()
17656 {
17657     time_t clock;
17658     struct tm *tm;
17659     char buf[MSG_SIZ];
17660
17661     clock = time((time_t *)NULL);
17662     tm = localtime(&clock);
17663     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17664             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17665     return StrSave(buf);
17666 }
17667
17668
17669 char *
17670 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17671 {
17672     int i, j, fromX, fromY, toX, toY;
17673     int whiteToPlay;
17674     char buf[MSG_SIZ];
17675     char *p, *q;
17676     int emptycount;
17677     ChessSquare piece;
17678
17679     whiteToPlay = (gameMode == EditPosition) ?
17680       !blackPlaysFirst : (move % 2 == 0);
17681     p = buf;
17682
17683     /* Piece placement data */
17684     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17685         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17686         emptycount = 0;
17687         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17688             if (boards[move][i][j] == EmptySquare) {
17689                 emptycount++;
17690             } else { ChessSquare piece = boards[move][i][j];
17691                 if (emptycount > 0) {
17692                     if(emptycount<10) /* [HGM] can be >= 10 */
17693                         *p++ = '0' + emptycount;
17694                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17695                     emptycount = 0;
17696                 }
17697                 if(PieceToChar(piece) == '+') {
17698                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17699                     *p++ = '+';
17700                     piece = (ChessSquare)(CHUDEMOTED piece);
17701                 }
17702                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17703                 if(p[-1] == '~') {
17704                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17705                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17706                     *p++ = '~';
17707                 }
17708             }
17709         }
17710         if (emptycount > 0) {
17711             if(emptycount<10) /* [HGM] can be >= 10 */
17712                 *p++ = '0' + emptycount;
17713             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17714             emptycount = 0;
17715         }
17716         *p++ = '/';
17717     }
17718     *(p - 1) = ' ';
17719
17720     /* [HGM] print Crazyhouse or Shogi holdings */
17721     if( gameInfo.holdingsWidth ) {
17722         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17723         q = p;
17724         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17725             piece = boards[move][i][BOARD_WIDTH-1];
17726             if( piece != EmptySquare )
17727               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17728                   *p++ = PieceToChar(piece);
17729         }
17730         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17731             piece = boards[move][BOARD_HEIGHT-i-1][0];
17732             if( piece != EmptySquare )
17733               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17734                   *p++ = PieceToChar(piece);
17735         }
17736
17737         if( q == p ) *p++ = '-';
17738         *p++ = ']';
17739         *p++ = ' ';
17740     }
17741
17742     /* Active color */
17743     *p++ = whiteToPlay ? 'w' : 'b';
17744     *p++ = ' ';
17745
17746   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17747     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17748   } else {
17749   if(nrCastlingRights) {
17750      q = p;
17751      if(appData.fischerCastling) {
17752        /* [HGM] write directly from rights */
17753            if(boards[move][CASTLING][2] != NoRights &&
17754               boards[move][CASTLING][0] != NoRights   )
17755                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17756            if(boards[move][CASTLING][2] != NoRights &&
17757               boards[move][CASTLING][1] != NoRights   )
17758                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17759            if(boards[move][CASTLING][5] != NoRights &&
17760               boards[move][CASTLING][3] != NoRights   )
17761                 *p++ = boards[move][CASTLING][3] + AAA;
17762            if(boards[move][CASTLING][5] != NoRights &&
17763               boards[move][CASTLING][4] != NoRights   )
17764                 *p++ = boards[move][CASTLING][4] + AAA;
17765      } else {
17766
17767         /* [HGM] write true castling rights */
17768         if( nrCastlingRights == 6 ) {
17769             int q, k=0;
17770             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17771                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17772             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17773                  boards[move][CASTLING][2] != NoRights  );
17774             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17775                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17776                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17777                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17778                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17779             }
17780             if(q) *p++ = 'Q';
17781             k = 0;
17782             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17783                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17784             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17785                  boards[move][CASTLING][5] != NoRights  );
17786             if(gameInfo.variant == VariantSChess) {
17787                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17788                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17789                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17790                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17791             }
17792             if(q) *p++ = 'q';
17793         }
17794      }
17795      if (q == p) *p++ = '-'; /* No castling rights */
17796      *p++ = ' ';
17797   }
17798
17799   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17800      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17801      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17802     /* En passant target square */
17803     if (move > backwardMostMove) {
17804         fromX = moveList[move - 1][0] - AAA;
17805         fromY = moveList[move - 1][1] - ONE;
17806         toX = moveList[move - 1][2] - AAA;
17807         toY = moveList[move - 1][3] - ONE;
17808         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17809             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17810             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17811             fromX == toX) {
17812             /* 2-square pawn move just happened */
17813             *p++ = toX + AAA;
17814             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17815         } else {
17816             *p++ = '-';
17817         }
17818     } else if(move == backwardMostMove) {
17819         // [HGM] perhaps we should always do it like this, and forget the above?
17820         if((signed char)boards[move][EP_STATUS] >= 0) {
17821             *p++ = boards[move][EP_STATUS] + AAA;
17822             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17823         } else {
17824             *p++ = '-';
17825         }
17826     } else {
17827         *p++ = '-';
17828     }
17829     *p++ = ' ';
17830   }
17831   }
17832
17833     if(moveCounts)
17834     {   int i = 0, j=move;
17835
17836         /* [HGM] find reversible plies */
17837         if (appData.debugMode) { int k;
17838             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17839             for(k=backwardMostMove; k<=forwardMostMove; k++)
17840                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17841
17842         }
17843
17844         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17845         if( j == backwardMostMove ) i += initialRulePlies;
17846         sprintf(p, "%d ", i);
17847         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17848
17849         /* Fullmove number */
17850         sprintf(p, "%d", (move / 2) + 1);
17851     } else *--p = NULLCHAR;
17852
17853     return StrSave(buf);
17854 }
17855
17856 Boolean
17857 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17858 {
17859     int i, j, k, w=0, subst=0, shuffle=0;
17860     char *p, c;
17861     int emptycount, virgin[BOARD_FILES];
17862     ChessSquare piece;
17863
17864     p = fen;
17865
17866     /* Piece placement data */
17867     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17868         j = 0;
17869         for (;;) {
17870             if (*p == '/' || *p == ' ' || *p == '[' ) {
17871                 if(j > w) w = j;
17872                 emptycount = gameInfo.boardWidth - j;
17873                 while (emptycount--)
17874                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17875                 if (*p == '/') p++;
17876                 else if(autoSize) { // we stumbled unexpectedly into end of board
17877                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17878                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17879                     }
17880                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17881                 }
17882                 break;
17883 #if(BOARD_FILES >= 10)*0
17884             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17885                 p++; emptycount=10;
17886                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17887                 while (emptycount--)
17888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17889 #endif
17890             } else if (*p == '*') {
17891                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17892             } else if (isdigit(*p)) {
17893                 emptycount = *p++ - '0';
17894                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17895                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17896                 while (emptycount--)
17897                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17898             } else if (*p == '<') {
17899                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17900                 else if (i != 0 || !shuffle) return FALSE;
17901                 p++;
17902             } else if (shuffle && *p == '>') {
17903                 p++; // for now ignore closing shuffle range, and assume rank-end
17904             } else if (*p == '?') {
17905                 if (j >= gameInfo.boardWidth) return FALSE;
17906                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17907                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17908             } else if (*p == '+' || isalpha(*p)) {
17909                 if (j >= gameInfo.boardWidth) return FALSE;
17910                 if(*p=='+') {
17911                     piece = CharToPiece(*++p);
17912                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17913                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17914                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17915                 } else piece = CharToPiece(*p++);
17916
17917                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17918                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17919                     piece = (ChessSquare) (PROMOTED piece);
17920                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17921                     p++;
17922                 }
17923                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17924             } else {
17925                 return FALSE;
17926             }
17927         }
17928     }
17929     while (*p == '/' || *p == ' ') p++;
17930
17931     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17932
17933     /* [HGM] by default clear Crazyhouse holdings, if present */
17934     if(gameInfo.holdingsWidth) {
17935        for(i=0; i<BOARD_HEIGHT; i++) {
17936            board[i][0]             = EmptySquare; /* black holdings */
17937            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17938            board[i][1]             = (ChessSquare) 0; /* black counts */
17939            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17940        }
17941     }
17942
17943     /* [HGM] look for Crazyhouse holdings here */
17944     while(*p==' ') p++;
17945     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17946         int swap=0, wcnt=0, bcnt=0;
17947         if(*p == '[') p++;
17948         if(*p == '<') swap++, p++;
17949         if(*p == '-' ) p++; /* empty holdings */ else {
17950             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17951             /* if we would allow FEN reading to set board size, we would   */
17952             /* have to add holdings and shift the board read so far here   */
17953             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17954                 p++;
17955                 if((int) piece >= (int) BlackPawn ) {
17956                     i = (int)piece - (int)BlackPawn;
17957                     i = PieceToNumber((ChessSquare)i);
17958                     if( i >= gameInfo.holdingsSize ) return FALSE;
17959                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17960                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17961                     bcnt++;
17962                 } else {
17963                     i = (int)piece - (int)WhitePawn;
17964                     i = PieceToNumber((ChessSquare)i);
17965                     if( i >= gameInfo.holdingsSize ) return FALSE;
17966                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17967                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17968                     wcnt++;
17969                 }
17970             }
17971             if(subst) { // substitute back-rank question marks by holdings pieces
17972                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17973                     int k, m, n = bcnt + 1;
17974                     if(board[0][j] == ClearBoard) {
17975                         if(!wcnt) return FALSE;
17976                         n = rand() % wcnt;
17977                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17978                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17979                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17980                             break;
17981                         }
17982                     }
17983                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17984                         if(!bcnt) return FALSE;
17985                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17986                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17987                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17988                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17989                             break;
17990                         }
17991                     }
17992                 }
17993                 subst = 0;
17994             }
17995         }
17996         if(*p == ']') p++;
17997     }
17998
17999     if(subst) return FALSE; // substitution requested, but no holdings
18000
18001     while(*p == ' ') p++;
18002
18003     /* Active color */
18004     c = *p++;
18005     if(appData.colorNickNames) {
18006       if( c == appData.colorNickNames[0] ) c = 'w'; else
18007       if( c == appData.colorNickNames[1] ) c = 'b';
18008     }
18009     switch (c) {
18010       case 'w':
18011         *blackPlaysFirst = FALSE;
18012         break;
18013       case 'b':
18014         *blackPlaysFirst = TRUE;
18015         break;
18016       default:
18017         return FALSE;
18018     }
18019
18020     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18021     /* return the extra info in global variiables             */
18022
18023     /* set defaults in case FEN is incomplete */
18024     board[EP_STATUS] = EP_UNKNOWN;
18025     for(i=0; i<nrCastlingRights; i++ ) {
18026         board[CASTLING][i] =
18027             appData.fischerCastling ? NoRights : initialRights[i];
18028     }   /* assume possible unless obviously impossible */
18029     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18030     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18031     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18032                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18033     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18034     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18035     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18036                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18037     FENrulePlies = 0;
18038
18039     while(*p==' ') p++;
18040     if(nrCastlingRights) {
18041       int fischer = 0;
18042       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18043       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18044           /* castling indicator present, so default becomes no castlings */
18045           for(i=0; i<nrCastlingRights; i++ ) {
18046                  board[CASTLING][i] = NoRights;
18047           }
18048       }
18049       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18050              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18051              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18052              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18053         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18054
18055         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18056             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18057             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18058         }
18059         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18060             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18061         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18062                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18063         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18064                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18065         switch(c) {
18066           case'K':
18067               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18068               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18069               board[CASTLING][2] = whiteKingFile;
18070               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18071               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18072               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18073               break;
18074           case'Q':
18075               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18076               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18077               board[CASTLING][2] = whiteKingFile;
18078               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18079               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18080               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18081               break;
18082           case'k':
18083               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18084               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18085               board[CASTLING][5] = blackKingFile;
18086               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18087               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18088               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18089               break;
18090           case'q':
18091               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18092               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18093               board[CASTLING][5] = blackKingFile;
18094               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18095               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18096               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18097           case '-':
18098               break;
18099           default: /* FRC castlings */
18100               if(c >= 'a') { /* black rights */
18101                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18102                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18103                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18104                   if(i == BOARD_RGHT) break;
18105                   board[CASTLING][5] = i;
18106                   c -= AAA;
18107                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18108                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18109                   if(c > i)
18110                       board[CASTLING][3] = c;
18111                   else
18112                       board[CASTLING][4] = c;
18113               } else { /* white rights */
18114                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18115                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18116                     if(board[0][i] == WhiteKing) break;
18117                   if(i == BOARD_RGHT) break;
18118                   board[CASTLING][2] = i;
18119                   c -= AAA - 'a' + 'A';
18120                   if(board[0][c] >= WhiteKing) break;
18121                   if(c > i)
18122                       board[CASTLING][0] = c;
18123                   else
18124                       board[CASTLING][1] = c;
18125               }
18126         }
18127       }
18128       for(i=0; i<nrCastlingRights; i++)
18129         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18130       if(gameInfo.variant == VariantSChess)
18131         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18132       if(fischer && shuffle) appData.fischerCastling = TRUE;
18133     if (appData.debugMode) {
18134         fprintf(debugFP, "FEN castling rights:");
18135         for(i=0; i<nrCastlingRights; i++)
18136         fprintf(debugFP, " %d", board[CASTLING][i]);
18137         fprintf(debugFP, "\n");
18138     }
18139
18140       while(*p==' ') p++;
18141     }
18142
18143     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18144
18145     /* read e.p. field in games that know e.p. capture */
18146     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18147        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18148        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18149       if(*p=='-') {
18150         p++; board[EP_STATUS] = EP_NONE;
18151       } else {
18152          char c = *p++ - AAA;
18153
18154          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18155          if(*p >= '0' && *p <='9') p++;
18156          board[EP_STATUS] = c;
18157       }
18158     }
18159
18160
18161     if(sscanf(p, "%d", &i) == 1) {
18162         FENrulePlies = i; /* 50-move ply counter */
18163         /* (The move number is still ignored)    */
18164     }
18165
18166     return TRUE;
18167 }
18168
18169 void
18170 EditPositionPasteFEN (char *fen)
18171 {
18172   if (fen != NULL) {
18173     Board initial_position;
18174
18175     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18176       DisplayError(_("Bad FEN position in clipboard"), 0);
18177       return ;
18178     } else {
18179       int savedBlackPlaysFirst = blackPlaysFirst;
18180       EditPositionEvent();
18181       blackPlaysFirst = savedBlackPlaysFirst;
18182       CopyBoard(boards[0], initial_position);
18183       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18184       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18185       DisplayBothClocks();
18186       DrawPosition(FALSE, boards[currentMove]);
18187     }
18188   }
18189 }
18190
18191 static char cseq[12] = "\\   ";
18192
18193 Boolean
18194 set_cont_sequence (char *new_seq)
18195 {
18196     int len;
18197     Boolean ret;
18198
18199     // handle bad attempts to set the sequence
18200         if (!new_seq)
18201                 return 0; // acceptable error - no debug
18202
18203     len = strlen(new_seq);
18204     ret = (len > 0) && (len < sizeof(cseq));
18205     if (ret)
18206       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18207     else if (appData.debugMode)
18208       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18209     return ret;
18210 }
18211
18212 /*
18213     reformat a source message so words don't cross the width boundary.  internal
18214     newlines are not removed.  returns the wrapped size (no null character unless
18215     included in source message).  If dest is NULL, only calculate the size required
18216     for the dest buffer.  lp argument indicats line position upon entry, and it's
18217     passed back upon exit.
18218 */
18219 int
18220 wrap (char *dest, char *src, int count, int width, int *lp)
18221 {
18222     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18223
18224     cseq_len = strlen(cseq);
18225     old_line = line = *lp;
18226     ansi = len = clen = 0;
18227
18228     for (i=0; i < count; i++)
18229     {
18230         if (src[i] == '\033')
18231             ansi = 1;
18232
18233         // if we hit the width, back up
18234         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18235         {
18236             // store i & len in case the word is too long
18237             old_i = i, old_len = len;
18238
18239             // find the end of the last word
18240             while (i && src[i] != ' ' && src[i] != '\n')
18241             {
18242                 i--;
18243                 len--;
18244             }
18245
18246             // word too long?  restore i & len before splitting it
18247             if ((old_i-i+clen) >= width)
18248             {
18249                 i = old_i;
18250                 len = old_len;
18251             }
18252
18253             // extra space?
18254             if (i && src[i-1] == ' ')
18255                 len--;
18256
18257             if (src[i] != ' ' && src[i] != '\n')
18258             {
18259                 i--;
18260                 if (len)
18261                     len--;
18262             }
18263
18264             // now append the newline and continuation sequence
18265             if (dest)
18266                 dest[len] = '\n';
18267             len++;
18268             if (dest)
18269                 strncpy(dest+len, cseq, cseq_len);
18270             len += cseq_len;
18271             line = cseq_len;
18272             clen = cseq_len;
18273             continue;
18274         }
18275
18276         if (dest)
18277             dest[len] = src[i];
18278         len++;
18279         if (!ansi)
18280             line++;
18281         if (src[i] == '\n')
18282             line = 0;
18283         if (src[i] == 'm')
18284             ansi = 0;
18285     }
18286     if (dest && appData.debugMode)
18287     {
18288         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18289             count, width, line, len, *lp);
18290         show_bytes(debugFP, src, count);
18291         fprintf(debugFP, "\ndest: ");
18292         show_bytes(debugFP, dest, len);
18293         fprintf(debugFP, "\n");
18294     }
18295     *lp = dest ? line : old_line;
18296
18297     return len;
18298 }
18299
18300 // [HGM] vari: routines for shelving variations
18301 Boolean modeRestore = FALSE;
18302
18303 void
18304 PushInner (int firstMove, int lastMove)
18305 {
18306         int i, j, nrMoves = lastMove - firstMove;
18307
18308         // push current tail of game on stack
18309         savedResult[storedGames] = gameInfo.result;
18310         savedDetails[storedGames] = gameInfo.resultDetails;
18311         gameInfo.resultDetails = NULL;
18312         savedFirst[storedGames] = firstMove;
18313         savedLast [storedGames] = lastMove;
18314         savedFramePtr[storedGames] = framePtr;
18315         framePtr -= nrMoves; // reserve space for the boards
18316         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18317             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18318             for(j=0; j<MOVE_LEN; j++)
18319                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18320             for(j=0; j<2*MOVE_LEN; j++)
18321                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18322             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18323             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18324             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18325             pvInfoList[firstMove+i-1].depth = 0;
18326             commentList[framePtr+i] = commentList[firstMove+i];
18327             commentList[firstMove+i] = NULL;
18328         }
18329
18330         storedGames++;
18331         forwardMostMove = firstMove; // truncate game so we can start variation
18332 }
18333
18334 void
18335 PushTail (int firstMove, int lastMove)
18336 {
18337         if(appData.icsActive) { // only in local mode
18338                 forwardMostMove = currentMove; // mimic old ICS behavior
18339                 return;
18340         }
18341         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18342
18343         PushInner(firstMove, lastMove);
18344         if(storedGames == 1) GreyRevert(FALSE);
18345         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18346 }
18347
18348 void
18349 PopInner (Boolean annotate)
18350 {
18351         int i, j, nrMoves;
18352         char buf[8000], moveBuf[20];
18353
18354         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18355         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18356         nrMoves = savedLast[storedGames] - currentMove;
18357         if(annotate) {
18358                 int cnt = 10;
18359                 if(!WhiteOnMove(currentMove))
18360                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18361                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18362                 for(i=currentMove; i<forwardMostMove; i++) {
18363                         if(WhiteOnMove(i))
18364                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18365                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18366                         strcat(buf, moveBuf);
18367                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18368                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18369                 }
18370                 strcat(buf, ")");
18371         }
18372         for(i=1; i<=nrMoves; i++) { // copy last variation back
18373             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18374             for(j=0; j<MOVE_LEN; j++)
18375                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18376             for(j=0; j<2*MOVE_LEN; j++)
18377                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18378             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18379             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18380             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18381             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18382             commentList[currentMove+i] = commentList[framePtr+i];
18383             commentList[framePtr+i] = NULL;
18384         }
18385         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18386         framePtr = savedFramePtr[storedGames];
18387         gameInfo.result = savedResult[storedGames];
18388         if(gameInfo.resultDetails != NULL) {
18389             free(gameInfo.resultDetails);
18390       }
18391         gameInfo.resultDetails = savedDetails[storedGames];
18392         forwardMostMove = currentMove + nrMoves;
18393 }
18394
18395 Boolean
18396 PopTail (Boolean annotate)
18397 {
18398         if(appData.icsActive) return FALSE; // only in local mode
18399         if(!storedGames) return FALSE; // sanity
18400         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18401
18402         PopInner(annotate);
18403         if(currentMove < forwardMostMove) ForwardEvent(); else
18404         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18405
18406         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18407         return TRUE;
18408 }
18409
18410 void
18411 CleanupTail ()
18412 {       // remove all shelved variations
18413         int i;
18414         for(i=0; i<storedGames; i++) {
18415             if(savedDetails[i])
18416                 free(savedDetails[i]);
18417             savedDetails[i] = NULL;
18418         }
18419         for(i=framePtr; i<MAX_MOVES; i++) {
18420                 if(commentList[i]) free(commentList[i]);
18421                 commentList[i] = NULL;
18422         }
18423         framePtr = MAX_MOVES-1;
18424         storedGames = 0;
18425 }
18426
18427 void
18428 LoadVariation (int index, char *text)
18429 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18430         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18431         int level = 0, move;
18432
18433         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18434         // first find outermost bracketing variation
18435         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18436             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18437                 if(*p == '{') wait = '}'; else
18438                 if(*p == '[') wait = ']'; else
18439                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18440                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18441             }
18442             if(*p == wait) wait = NULLCHAR; // closing ]} found
18443             p++;
18444         }
18445         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18446         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18447         end[1] = NULLCHAR; // clip off comment beyond variation
18448         ToNrEvent(currentMove-1);
18449         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18450         // kludge: use ParsePV() to append variation to game
18451         move = currentMove;
18452         ParsePV(start, TRUE, TRUE);
18453         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18454         ClearPremoveHighlights();
18455         CommentPopDown();
18456         ToNrEvent(currentMove+1);
18457 }
18458
18459 void
18460 LoadTheme ()
18461 {
18462     char *p, *q, buf[MSG_SIZ];
18463     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18464         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18465         ParseArgsFromString(buf);
18466         ActivateTheme(TRUE); // also redo colors
18467         return;
18468     }
18469     p = nickName;
18470     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18471     {
18472         int len;
18473         q = appData.themeNames;
18474         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18475       if(appData.useBitmaps) {
18476         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18477                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18478                 appData.liteBackTextureMode,
18479                 appData.darkBackTextureMode );
18480       } else {
18481         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18482                 Col2Text(2),   // lightSquareColor
18483                 Col2Text(3) ); // darkSquareColor
18484       }
18485       if(appData.useBorder) {
18486         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18487                 appData.border);
18488       } else {
18489         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18490       }
18491       if(appData.useFont) {
18492         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18493                 appData.renderPiecesWithFont,
18494                 appData.fontToPieceTable,
18495                 Col2Text(9),    // appData.fontBackColorWhite
18496                 Col2Text(10) ); // appData.fontForeColorBlack
18497       } else {
18498         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18499                 appData.pieceDirectory);
18500         if(!appData.pieceDirectory[0])
18501           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18502                 Col2Text(0),   // whitePieceColor
18503                 Col2Text(1) ); // blackPieceColor
18504       }
18505       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18506                 Col2Text(4),   // highlightSquareColor
18507                 Col2Text(5) ); // premoveHighlightColor
18508         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18509         if(insert != q) insert[-1] = NULLCHAR;
18510         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18511         if(q)   free(q);
18512     }
18513     ActivateTheme(FALSE);
18514 }