Add final piece count to search criteria
[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         loaded = 2; // prepare for failure
8375         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8376         HMODULE lib;
8377         PLOAD_EGBB loadBB;
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 (plyext != ' ' && plyext != '\t') {
9425                     time *= 100;
9426                 }
9427
9428                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9429                 if( cps->scoreIsAbsolute &&
9430                     ( gameMode == MachinePlaysBlack ||
9431                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9432                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9433                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9434                      !WhiteOnMove(currentMove)
9435                     ) )
9436                 {
9437                     curscore = -curscore;
9438                 }
9439
9440                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9441
9442                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9443                         char buf[MSG_SIZ];
9444                         FILE *f;
9445                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9446                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9447                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9448                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9449                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9450                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9451                                 fclose(f);
9452                         }
9453                         else
9454                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9455                           DisplayError(_("failed writing PV"), 0);
9456                 }
9457
9458                 tempStats.depth = plylev;
9459                 tempStats.nodes = nodes;
9460                 tempStats.time = time;
9461                 tempStats.score = curscore;
9462                 tempStats.got_only_move = 0;
9463
9464                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9465                         int ticklen;
9466
9467                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9468                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9469                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9470                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9471                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9472                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9473                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9474                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9475                 }
9476
9477                 /* Buffer overflow protection */
9478                 if (pv[0] != NULLCHAR) {
9479                     if (strlen(pv) >= sizeof(tempStats.movelist)
9480                         && appData.debugMode) {
9481                         fprintf(debugFP,
9482                                 "PV is too long; using the first %u bytes.\n",
9483                                 (unsigned) sizeof(tempStats.movelist) - 1);
9484                     }
9485
9486                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9487                 } else {
9488                     sprintf(tempStats.movelist, " no PV\n");
9489                 }
9490
9491                 if (tempStats.seen_stat) {
9492                     tempStats.ok_to_send = 1;
9493                 }
9494
9495                 if (strchr(tempStats.movelist, '(') != NULL) {
9496                     tempStats.line_is_book = 1;
9497                     tempStats.nr_moves = 0;
9498                     tempStats.moves_left = 0;
9499                 } else {
9500                     tempStats.line_is_book = 0;
9501                 }
9502
9503                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9504                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9505
9506                 SendProgramStatsToFrontend( cps, &tempStats );
9507
9508                 /*
9509                     [AS] Protect the thinkOutput buffer from overflow... this
9510                     is only useful if buf1 hasn't overflowed first!
9511                 */
9512                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9513                          plylev,
9514                          (gameMode == TwoMachinesPlay ?
9515                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9516                          ((double) curscore) / 100.0,
9517                          prefixHint ? lastHint : "",
9518                          prefixHint ? " " : "" );
9519
9520                 if( buf1[0] != NULLCHAR ) {
9521                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9522
9523                     if( strlen(pv) > max_len ) {
9524                         if( appData.debugMode) {
9525                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9526                         }
9527                         pv[max_len+1] = '\0';
9528                     }
9529
9530                     strcat( thinkOutput, pv);
9531                 }
9532
9533                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9534                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9535                     DisplayMove(currentMove - 1);
9536                 }
9537                 return;
9538
9539             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9540                 /* crafty (9.25+) says "(only move) <move>"
9541                  * if there is only 1 legal move
9542                  */
9543                 sscanf(p, "(only move) %s", buf1);
9544                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9545                 sprintf(programStats.movelist, "%s (only move)", buf1);
9546                 programStats.depth = 1;
9547                 programStats.nr_moves = 1;
9548                 programStats.moves_left = 1;
9549                 programStats.nodes = 1;
9550                 programStats.time = 1;
9551                 programStats.got_only_move = 1;
9552
9553                 /* Not really, but we also use this member to
9554                    mean "line isn't going to change" (Crafty
9555                    isn't searching, so stats won't change) */
9556                 programStats.line_is_book = 1;
9557
9558                 SendProgramStatsToFrontend( cps, &programStats );
9559
9560                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9561                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9562                     DisplayMove(currentMove - 1);
9563                 }
9564                 return;
9565             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9566                               &time, &nodes, &plylev, &mvleft,
9567                               &mvtot, mvname) >= 5) {
9568                 /* The stat01: line is from Crafty (9.29+) in response
9569                    to the "." command */
9570                 programStats.seen_stat = 1;
9571                 cps->maybeThinking = TRUE;
9572
9573                 if (programStats.got_only_move || !appData.periodicUpdates)
9574                   return;
9575
9576                 programStats.depth = plylev;
9577                 programStats.time = time;
9578                 programStats.nodes = nodes;
9579                 programStats.moves_left = mvleft;
9580                 programStats.nr_moves = mvtot;
9581                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9582                 programStats.ok_to_send = 1;
9583                 programStats.movelist[0] = '\0';
9584
9585                 SendProgramStatsToFrontend( cps, &programStats );
9586
9587                 return;
9588
9589             } else if (strncmp(message,"++",2) == 0) {
9590                 /* Crafty 9.29+ outputs this */
9591                 programStats.got_fail = 2;
9592                 return;
9593
9594             } else if (strncmp(message,"--",2) == 0) {
9595                 /* Crafty 9.29+ outputs this */
9596                 programStats.got_fail = 1;
9597                 return;
9598
9599             } else if (thinkOutput[0] != NULLCHAR &&
9600                        strncmp(message, "    ", 4) == 0) {
9601                 unsigned message_len;
9602
9603                 p = message;
9604                 while (*p && *p == ' ') p++;
9605
9606                 message_len = strlen( p );
9607
9608                 /* [AS] Avoid buffer overflow */
9609                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9610                     strcat(thinkOutput, " ");
9611                     strcat(thinkOutput, p);
9612                 }
9613
9614                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9615                     strcat(programStats.movelist, " ");
9616                     strcat(programStats.movelist, p);
9617                 }
9618
9619                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9620                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9621                     DisplayMove(currentMove - 1);
9622                 }
9623                 return;
9624             }
9625         }
9626         else {
9627             buf1[0] = NULLCHAR;
9628
9629             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9630                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9631             {
9632                 ChessProgramStats cpstats;
9633
9634                 if (plyext != ' ' && plyext != '\t') {
9635                     time *= 100;
9636                 }
9637
9638                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9639                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9640                     curscore = -curscore;
9641                 }
9642
9643                 cpstats.depth = plylev;
9644                 cpstats.nodes = nodes;
9645                 cpstats.time = time;
9646                 cpstats.score = curscore;
9647                 cpstats.got_only_move = 0;
9648                 cpstats.movelist[0] = '\0';
9649
9650                 if (buf1[0] != NULLCHAR) {
9651                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9652                 }
9653
9654                 cpstats.ok_to_send = 0;
9655                 cpstats.line_is_book = 0;
9656                 cpstats.nr_moves = 0;
9657                 cpstats.moves_left = 0;
9658
9659                 SendProgramStatsToFrontend( cps, &cpstats );
9660             }
9661         }
9662     }
9663 }
9664
9665
9666 /* Parse a game score from the character string "game", and
9667    record it as the history of the current game.  The game
9668    score is NOT assumed to start from the standard position.
9669    The display is not updated in any way.
9670    */
9671 void
9672 ParseGameHistory (char *game)
9673 {
9674     ChessMove moveType;
9675     int fromX, fromY, toX, toY, boardIndex;
9676     char promoChar;
9677     char *p, *q;
9678     char buf[MSG_SIZ];
9679
9680     if (appData.debugMode)
9681       fprintf(debugFP, "Parsing game history: %s\n", game);
9682
9683     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9684     gameInfo.site = StrSave(appData.icsHost);
9685     gameInfo.date = PGNDate();
9686     gameInfo.round = StrSave("-");
9687
9688     /* Parse out names of players */
9689     while (*game == ' ') game++;
9690     p = buf;
9691     while (*game != ' ') *p++ = *game++;
9692     *p = NULLCHAR;
9693     gameInfo.white = StrSave(buf);
9694     while (*game == ' ') game++;
9695     p = buf;
9696     while (*game != ' ' && *game != '\n') *p++ = *game++;
9697     *p = NULLCHAR;
9698     gameInfo.black = StrSave(buf);
9699
9700     /* Parse moves */
9701     boardIndex = blackPlaysFirst ? 1 : 0;
9702     yynewstr(game);
9703     for (;;) {
9704         yyboardindex = boardIndex;
9705         moveType = (ChessMove) Myylex();
9706         switch (moveType) {
9707           case IllegalMove:             /* maybe suicide chess, etc. */
9708   if (appData.debugMode) {
9709     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9710     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9711     setbuf(debugFP, NULL);
9712   }
9713           case WhitePromotion:
9714           case BlackPromotion:
9715           case WhiteNonPromotion:
9716           case BlackNonPromotion:
9717           case NormalMove:
9718           case FirstLeg:
9719           case WhiteCapturesEnPassant:
9720           case BlackCapturesEnPassant:
9721           case WhiteKingSideCastle:
9722           case WhiteQueenSideCastle:
9723           case BlackKingSideCastle:
9724           case BlackQueenSideCastle:
9725           case WhiteKingSideCastleWild:
9726           case WhiteQueenSideCastleWild:
9727           case BlackKingSideCastleWild:
9728           case BlackQueenSideCastleWild:
9729           /* PUSH Fabien */
9730           case WhiteHSideCastleFR:
9731           case WhiteASideCastleFR:
9732           case BlackHSideCastleFR:
9733           case BlackASideCastleFR:
9734           /* POP Fabien */
9735             fromX = currentMoveString[0] - AAA;
9736             fromY = currentMoveString[1] - ONE;
9737             toX = currentMoveString[2] - AAA;
9738             toY = currentMoveString[3] - ONE;
9739             promoChar = currentMoveString[4];
9740             break;
9741           case WhiteDrop:
9742           case BlackDrop:
9743             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9744             fromX = moveType == WhiteDrop ?
9745               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9746             (int) CharToPiece(ToLower(currentMoveString[0]));
9747             fromY = DROP_RANK;
9748             toX = currentMoveString[2] - AAA;
9749             toY = currentMoveString[3] - ONE;
9750             promoChar = NULLCHAR;
9751             break;
9752           case AmbiguousMove:
9753             /* bug? */
9754             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9755   if (appData.debugMode) {
9756     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9757     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9758     setbuf(debugFP, NULL);
9759   }
9760             DisplayError(buf, 0);
9761             return;
9762           case ImpossibleMove:
9763             /* bug? */
9764             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9765   if (appData.debugMode) {
9766     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9767     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9768     setbuf(debugFP, NULL);
9769   }
9770             DisplayError(buf, 0);
9771             return;
9772           case EndOfFile:
9773             if (boardIndex < backwardMostMove) {
9774                 /* Oops, gap.  How did that happen? */
9775                 DisplayError(_("Gap in move list"), 0);
9776                 return;
9777             }
9778             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9779             if (boardIndex > forwardMostMove) {
9780                 forwardMostMove = boardIndex;
9781             }
9782             return;
9783           case ElapsedTime:
9784             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9785                 strcat(parseList[boardIndex-1], " ");
9786                 strcat(parseList[boardIndex-1], yy_text);
9787             }
9788             continue;
9789           case Comment:
9790           case PGNTag:
9791           case NAG:
9792           default:
9793             /* ignore */
9794             continue;
9795           case WhiteWins:
9796           case BlackWins:
9797           case GameIsDrawn:
9798           case GameUnfinished:
9799             if (gameMode == IcsExamining) {
9800                 if (boardIndex < backwardMostMove) {
9801                     /* Oops, gap.  How did that happen? */
9802                     return;
9803                 }
9804                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9805                 return;
9806             }
9807             gameInfo.result = moveType;
9808             p = strchr(yy_text, '{');
9809             if (p == NULL) p = strchr(yy_text, '(');
9810             if (p == NULL) {
9811                 p = yy_text;
9812                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9813             } else {
9814                 q = strchr(p, *p == '{' ? '}' : ')');
9815                 if (q != NULL) *q = NULLCHAR;
9816                 p++;
9817             }
9818             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9819             gameInfo.resultDetails = StrSave(p);
9820             continue;
9821         }
9822         if (boardIndex >= forwardMostMove &&
9823             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9824             backwardMostMove = blackPlaysFirst ? 1 : 0;
9825             return;
9826         }
9827         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9828                                  fromY, fromX, toY, toX, promoChar,
9829                                  parseList[boardIndex]);
9830         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9831         /* currentMoveString is set as a side-effect of yylex */
9832         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9833         strcat(moveList[boardIndex], "\n");
9834         boardIndex++;
9835         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9836         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9837           case MT_NONE:
9838           case MT_STALEMATE:
9839           default:
9840             break;
9841           case MT_CHECK:
9842             if(!IS_SHOGI(gameInfo.variant))
9843                 strcat(parseList[boardIndex - 1], "+");
9844             break;
9845           case MT_CHECKMATE:
9846           case MT_STAINMATE:
9847             strcat(parseList[boardIndex - 1], "#");
9848             break;
9849         }
9850     }
9851 }
9852
9853
9854 /* Apply a move to the given board  */
9855 void
9856 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9857 {
9858   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9859   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9860
9861     /* [HGM] compute & store e.p. status and castling rights for new position */
9862     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9863
9864       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9865       oldEP = (signed char)board[EP_STATUS];
9866       board[EP_STATUS] = EP_NONE;
9867
9868   if (fromY == DROP_RANK) {
9869         /* must be first */
9870         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9871             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9872             return;
9873         }
9874         piece = board[toY][toX] = (ChessSquare) fromX;
9875   } else {
9876 //      ChessSquare victim;
9877       int i;
9878
9879       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9880 //           victim = board[killY][killX],
9881            board[killY][killX] = EmptySquare,
9882            board[EP_STATUS] = EP_CAPTURE;
9883
9884       if( board[toY][toX] != EmptySquare ) {
9885            board[EP_STATUS] = EP_CAPTURE;
9886            if( (fromX != toX || fromY != toY) && // not igui!
9887                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9888                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9889                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9890            }
9891       }
9892
9893       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9894            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9895                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9896       } else
9897       if( board[fromY][fromX] == WhitePawn ) {
9898            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9899                board[EP_STATUS] = EP_PAWN_MOVE;
9900            if( toY-fromY==2) {
9901                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9902                         gameInfo.variant != VariantBerolina || toX < fromX)
9903                       board[EP_STATUS] = toX | berolina;
9904                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9905                         gameInfo.variant != VariantBerolina || toX > fromX)
9906                       board[EP_STATUS] = toX;
9907            }
9908       } else
9909       if( board[fromY][fromX] == BlackPawn ) {
9910            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9911                board[EP_STATUS] = EP_PAWN_MOVE;
9912            if( toY-fromY== -2) {
9913                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9914                         gameInfo.variant != VariantBerolina || toX < fromX)
9915                       board[EP_STATUS] = toX | berolina;
9916                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9917                         gameInfo.variant != VariantBerolina || toX > fromX)
9918                       board[EP_STATUS] = toX;
9919            }
9920        }
9921
9922        for(i=0; i<nrCastlingRights; i++) {
9923            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9924               board[CASTLING][i] == toX   && castlingRank[i] == toY
9925              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9926        }
9927
9928        if(gameInfo.variant == VariantSChess) { // update virginity
9929            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9930            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9931            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9932            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9933        }
9934
9935      if (fromX == toX && fromY == toY) return;
9936
9937      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9938      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9939      if(gameInfo.variant == VariantKnightmate)
9940          king += (int) WhiteUnicorn - (int) WhiteKing;
9941
9942     /* Code added by Tord: */
9943     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9944     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9945         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9946       board[fromY][fromX] = EmptySquare;
9947       board[toY][toX] = EmptySquare;
9948       if((toX > fromX) != (piece == WhiteRook)) {
9949         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9950       } else {
9951         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9952       }
9953     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9954                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9955       board[fromY][fromX] = EmptySquare;
9956       board[toY][toX] = EmptySquare;
9957       if((toX > fromX) != (piece == BlackRook)) {
9958         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9959       } else {
9960         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9961       }
9962     /* End of code added by Tord */
9963
9964     } else if (board[fromY][fromX] == king
9965         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9966         && toY == fromY && toX > fromX+1) {
9967         board[fromY][fromX] = EmptySquare;
9968         board[toY][toX] = king;
9969         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9970         board[fromY][BOARD_RGHT-1] = EmptySquare;
9971     } else if (board[fromY][fromX] == king
9972         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9973                && toY == fromY && toX < fromX-1) {
9974         board[fromY][fromX] = EmptySquare;
9975         board[toY][toX] = king;
9976         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9977         board[fromY][BOARD_LEFT] = EmptySquare;
9978     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9979                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9980                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9981                ) {
9982         /* white pawn promotion */
9983         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9984         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9985             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9986         board[fromY][fromX] = EmptySquare;
9987     } else if ((fromY >= BOARD_HEIGHT>>1)
9988                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9989                && (toX != fromX)
9990                && gameInfo.variant != VariantXiangqi
9991                && gameInfo.variant != VariantBerolina
9992                && (board[fromY][fromX] == WhitePawn)
9993                && (board[toY][toX] == EmptySquare)) {
9994         board[fromY][fromX] = EmptySquare;
9995         board[toY][toX] = WhitePawn;
9996         captured = board[toY - 1][toX];
9997         board[toY - 1][toX] = EmptySquare;
9998     } else if ((fromY == BOARD_HEIGHT-4)
9999                && (toX == fromX)
10000                && gameInfo.variant == VariantBerolina
10001                && (board[fromY][fromX] == WhitePawn)
10002                && (board[toY][toX] == EmptySquare)) {
10003         board[fromY][fromX] = EmptySquare;
10004         board[toY][toX] = WhitePawn;
10005         if(oldEP & EP_BEROLIN_A) {
10006                 captured = board[fromY][fromX-1];
10007                 board[fromY][fromX-1] = EmptySquare;
10008         }else{  captured = board[fromY][fromX+1];
10009                 board[fromY][fromX+1] = EmptySquare;
10010         }
10011     } else if (board[fromY][fromX] == king
10012         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10013                && toY == fromY && toX > fromX+1) {
10014         board[fromY][fromX] = EmptySquare;
10015         board[toY][toX] = king;
10016         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10017         board[fromY][BOARD_RGHT-1] = EmptySquare;
10018     } else if (board[fromY][fromX] == king
10019         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10020                && toY == fromY && toX < fromX-1) {
10021         board[fromY][fromX] = EmptySquare;
10022         board[toY][toX] = king;
10023         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10024         board[fromY][BOARD_LEFT] = EmptySquare;
10025     } else if (fromY == 7 && fromX == 3
10026                && board[fromY][fromX] == BlackKing
10027                && toY == 7 && toX == 5) {
10028         board[fromY][fromX] = EmptySquare;
10029         board[toY][toX] = BlackKing;
10030         board[fromY][7] = EmptySquare;
10031         board[toY][4] = BlackRook;
10032     } else if (fromY == 7 && fromX == 3
10033                && board[fromY][fromX] == BlackKing
10034                && toY == 7 && toX == 1) {
10035         board[fromY][fromX] = EmptySquare;
10036         board[toY][toX] = BlackKing;
10037         board[fromY][0] = EmptySquare;
10038         board[toY][2] = BlackRook;
10039     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10040                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10041                && toY < promoRank && promoChar
10042                ) {
10043         /* black pawn promotion */
10044         board[toY][toX] = CharToPiece(ToLower(promoChar));
10045         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10046             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10047         board[fromY][fromX] = EmptySquare;
10048     } else if ((fromY < BOARD_HEIGHT>>1)
10049                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10050                && (toX != fromX)
10051                && gameInfo.variant != VariantXiangqi
10052                && gameInfo.variant != VariantBerolina
10053                && (board[fromY][fromX] == BlackPawn)
10054                && (board[toY][toX] == EmptySquare)) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = BlackPawn;
10057         captured = board[toY + 1][toX];
10058         board[toY + 1][toX] = EmptySquare;
10059     } else if ((fromY == 3)
10060                && (toX == fromX)
10061                && gameInfo.variant == VariantBerolina
10062                && (board[fromY][fromX] == BlackPawn)
10063                && (board[toY][toX] == EmptySquare)) {
10064         board[fromY][fromX] = EmptySquare;
10065         board[toY][toX] = BlackPawn;
10066         if(oldEP & EP_BEROLIN_A) {
10067                 captured = board[fromY][fromX-1];
10068                 board[fromY][fromX-1] = EmptySquare;
10069         }else{  captured = board[fromY][fromX+1];
10070                 board[fromY][fromX+1] = EmptySquare;
10071         }
10072     } else {
10073         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10074         board[fromY][fromX] = EmptySquare;
10075         board[toY][toX] = piece;
10076     }
10077   }
10078
10079     if (gameInfo.holdingsWidth != 0) {
10080
10081       /* !!A lot more code needs to be written to support holdings  */
10082       /* [HGM] OK, so I have written it. Holdings are stored in the */
10083       /* penultimate board files, so they are automaticlly stored   */
10084       /* in the game history.                                       */
10085       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10086                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10087         /* Delete from holdings, by decreasing count */
10088         /* and erasing image if necessary            */
10089         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10090         if(p < (int) BlackPawn) { /* white drop */
10091              p -= (int)WhitePawn;
10092                  p = PieceToNumber((ChessSquare)p);
10093              if(p >= gameInfo.holdingsSize) p = 0;
10094              if(--board[p][BOARD_WIDTH-2] <= 0)
10095                   board[p][BOARD_WIDTH-1] = EmptySquare;
10096              if((int)board[p][BOARD_WIDTH-2] < 0)
10097                         board[p][BOARD_WIDTH-2] = 0;
10098         } else {                  /* black drop */
10099              p -= (int)BlackPawn;
10100                  p = PieceToNumber((ChessSquare)p);
10101              if(p >= gameInfo.holdingsSize) p = 0;
10102              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10103                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10104              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10105                         board[BOARD_HEIGHT-1-p][1] = 0;
10106         }
10107       }
10108       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10109           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10110         /* [HGM] holdings: Add to holdings, if holdings exist */
10111         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10112                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10113                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10114         }
10115         p = (int) captured;
10116         if (p >= (int) BlackPawn) {
10117           p -= (int)BlackPawn;
10118           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10119                   /* in Shogi restore piece to its original  first */
10120                   captured = (ChessSquare) (DEMOTED captured);
10121                   p = DEMOTED p;
10122           }
10123           p = PieceToNumber((ChessSquare)p);
10124           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10125           board[p][BOARD_WIDTH-2]++;
10126           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10127         } else {
10128           p -= (int)WhitePawn;
10129           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10130                   captured = (ChessSquare) (DEMOTED captured);
10131                   p = DEMOTED p;
10132           }
10133           p = PieceToNumber((ChessSquare)p);
10134           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10135           board[BOARD_HEIGHT-1-p][1]++;
10136           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10137         }
10138       }
10139     } else if (gameInfo.variant == VariantAtomic) {
10140       if (captured != EmptySquare) {
10141         int y, x;
10142         for (y = toY-1; y <= toY+1; y++) {
10143           for (x = toX-1; x <= toX+1; x++) {
10144             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10145                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10146               board[y][x] = EmptySquare;
10147             }
10148           }
10149         }
10150         board[toY][toX] = EmptySquare;
10151       }
10152     }
10153
10154     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10155         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10156     } else
10157     if(promoChar == '+') {
10158         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10159         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10160         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10161           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10162     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10163         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10164         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10165            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10166         board[toY][toX] = newPiece;
10167     }
10168     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10169                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10170         // [HGM] superchess: take promotion piece out of holdings
10171         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10172         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10173             if(!--board[k][BOARD_WIDTH-2])
10174                 board[k][BOARD_WIDTH-1] = EmptySquare;
10175         } else {
10176             if(!--board[BOARD_HEIGHT-1-k][1])
10177                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10178         }
10179     }
10180 }
10181
10182 /* Updates forwardMostMove */
10183 void
10184 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10185 {
10186     int x = toX, y = toY;
10187     char *s = parseList[forwardMostMove];
10188     ChessSquare p = boards[forwardMostMove][toY][toX];
10189 //    forwardMostMove++; // [HGM] bare: moved downstream
10190
10191     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10192     (void) CoordsToAlgebraic(boards[forwardMostMove],
10193                              PosFlags(forwardMostMove),
10194                              fromY, fromX, y, x, promoChar,
10195                              s);
10196     if(killX >= 0 && killY >= 0)
10197         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10198
10199     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10200         int timeLeft; static int lastLoadFlag=0; int king, piece;
10201         piece = boards[forwardMostMove][fromY][fromX];
10202         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10203         if(gameInfo.variant == VariantKnightmate)
10204             king += (int) WhiteUnicorn - (int) WhiteKing;
10205         if(forwardMostMove == 0) {
10206             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10207                 fprintf(serverMoves, "%s;", UserName());
10208             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10209                 fprintf(serverMoves, "%s;", second.tidy);
10210             fprintf(serverMoves, "%s;", first.tidy);
10211             if(gameMode == MachinePlaysWhite)
10212                 fprintf(serverMoves, "%s;", UserName());
10213             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10214                 fprintf(serverMoves, "%s;", second.tidy);
10215         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10216         lastLoadFlag = loadFlag;
10217         // print base move
10218         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10219         // print castling suffix
10220         if( toY == fromY && piece == king ) {
10221             if(toX-fromX > 1)
10222                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10223             if(fromX-toX >1)
10224                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10225         }
10226         // e.p. suffix
10227         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10228              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10229              boards[forwardMostMove][toY][toX] == EmptySquare
10230              && fromX != toX && fromY != toY)
10231                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10232         // promotion suffix
10233         if(promoChar != NULLCHAR) {
10234             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10235                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10236                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10237             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10238         }
10239         if(!loadFlag) {
10240                 char buf[MOVE_LEN*2], *p; int len;
10241             fprintf(serverMoves, "/%d/%d",
10242                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10243             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10244             else                      timeLeft = blackTimeRemaining/1000;
10245             fprintf(serverMoves, "/%d", timeLeft);
10246                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10247                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10248                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10249                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10250             fprintf(serverMoves, "/%s", buf);
10251         }
10252         fflush(serverMoves);
10253     }
10254
10255     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10256         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10257       return;
10258     }
10259     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10260     if (commentList[forwardMostMove+1] != NULL) {
10261         free(commentList[forwardMostMove+1]);
10262         commentList[forwardMostMove+1] = NULL;
10263     }
10264     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10265     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10266     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10267     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10268     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10269     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10270     adjustedClock = FALSE;
10271     gameInfo.result = GameUnfinished;
10272     if (gameInfo.resultDetails != NULL) {
10273         free(gameInfo.resultDetails);
10274         gameInfo.resultDetails = NULL;
10275     }
10276     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10277                               moveList[forwardMostMove - 1]);
10278     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10279       case MT_NONE:
10280       case MT_STALEMATE:
10281       default:
10282         break;
10283       case MT_CHECK:
10284         if(!IS_SHOGI(gameInfo.variant))
10285             strcat(parseList[forwardMostMove - 1], "+");
10286         break;
10287       case MT_CHECKMATE:
10288       case MT_STAINMATE:
10289         strcat(parseList[forwardMostMove - 1], "#");
10290         break;
10291     }
10292 }
10293
10294 /* Updates currentMove if not pausing */
10295 void
10296 ShowMove (int fromX, int fromY, int toX, int toY)
10297 {
10298     int instant = (gameMode == PlayFromGameFile) ?
10299         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10300     if(appData.noGUI) return;
10301     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10302         if (!instant) {
10303             if (forwardMostMove == currentMove + 1) {
10304                 AnimateMove(boards[forwardMostMove - 1],
10305                             fromX, fromY, toX, toY);
10306             }
10307         }
10308         currentMove = forwardMostMove;
10309     }
10310
10311     killX = killY = -1; // [HGM] lion: used up
10312
10313     if (instant) return;
10314
10315     DisplayMove(currentMove - 1);
10316     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10317             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10318                 SetHighlights(fromX, fromY, toX, toY);
10319             }
10320     }
10321     DrawPosition(FALSE, boards[currentMove]);
10322     DisplayBothClocks();
10323     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10324 }
10325
10326 void
10327 SendEgtPath (ChessProgramState *cps)
10328 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10329         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10330
10331         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10332
10333         while(*p) {
10334             char c, *q = name+1, *r, *s;
10335
10336             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10337             while(*p && *p != ',') *q++ = *p++;
10338             *q++ = ':'; *q = 0;
10339             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10340                 strcmp(name, ",nalimov:") == 0 ) {
10341                 // take nalimov path from the menu-changeable option first, if it is defined
10342               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10343                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10344             } else
10345             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10346                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10347                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10348                 s = r = StrStr(s, ":") + 1; // beginning of path info
10349                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10350                 c = *r; *r = 0;             // temporarily null-terminate path info
10351                     *--q = 0;               // strip of trailig ':' from name
10352                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10353                 *r = c;
10354                 SendToProgram(buf,cps);     // send egtbpath command for this format
10355             }
10356             if(*p == ',') p++; // read away comma to position for next format name
10357         }
10358 }
10359
10360 static int
10361 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10362 {
10363       int width = 8, height = 8, holdings = 0;             // most common sizes
10364       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10365       // correct the deviations default for each variant
10366       if( v == VariantXiangqi ) width = 9,  height = 10;
10367       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10368       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10369       if( v == VariantCapablanca || v == VariantCapaRandom ||
10370           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10371                                 width = 10;
10372       if( v == VariantCourier ) width = 12;
10373       if( v == VariantSuper )                            holdings = 8;
10374       if( v == VariantGreat )   width = 10,              holdings = 8;
10375       if( v == VariantSChess )                           holdings = 7;
10376       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10377       if( v == VariantChuChess) width = 10, height = 10;
10378       if( v == VariantChu )     width = 12, height = 12;
10379       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10380              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10381              holdingsSize >= 0 && holdingsSize != holdings;
10382 }
10383
10384 char variantError[MSG_SIZ];
10385
10386 char *
10387 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10388 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10389       char *p, *variant = VariantName(v);
10390       static char b[MSG_SIZ];
10391       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10392            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10393                                                holdingsSize, variant); // cook up sized variant name
10394            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10395            if(StrStr(list, b) == NULL) {
10396                // specific sized variant not known, check if general sizing allowed
10397                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10398                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10399                             boardWidth, boardHeight, holdingsSize, engine);
10400                    return NULL;
10401                }
10402                /* [HGM] here we really should compare with the maximum supported board size */
10403            }
10404       } else snprintf(b, MSG_SIZ,"%s", variant);
10405       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10406       p = StrStr(list, b);
10407       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10408       if(p == NULL) {
10409           // occurs not at all in list, or only as sub-string
10410           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10411           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10412               int l = strlen(variantError);
10413               char *q;
10414               while(p != list && p[-1] != ',') p--;
10415               q = strchr(p, ',');
10416               if(q) *q = NULLCHAR;
10417               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10418               if(q) *q= ',';
10419           }
10420           return NULL;
10421       }
10422       return b;
10423 }
10424
10425 void
10426 InitChessProgram (ChessProgramState *cps, int setup)
10427 /* setup needed to setup FRC opening position */
10428 {
10429     char buf[MSG_SIZ], *b;
10430     if (appData.noChessProgram) return;
10431     hintRequested = FALSE;
10432     bookRequested = FALSE;
10433
10434     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10435     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10436     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10437     if(cps->memSize) { /* [HGM] memory */
10438       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10439         SendToProgram(buf, cps);
10440     }
10441     SendEgtPath(cps); /* [HGM] EGT */
10442     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10443       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10444         SendToProgram(buf, cps);
10445     }
10446
10447     setboardSpoiledMachineBlack = FALSE;
10448     SendToProgram(cps->initString, cps);
10449     if (gameInfo.variant != VariantNormal &&
10450         gameInfo.variant != VariantLoadable
10451         /* [HGM] also send variant if board size non-standard */
10452         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10453
10454       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10455                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10456       if (b == NULL) {
10457         DisplayFatalError(variantError, 0, 1);
10458         return;
10459       }
10460
10461       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10462       SendToProgram(buf, cps);
10463     }
10464     currentlyInitializedVariant = gameInfo.variant;
10465
10466     /* [HGM] send opening position in FRC to first engine */
10467     if(setup) {
10468           SendToProgram("force\n", cps);
10469           SendBoard(cps, 0);
10470           /* engine is now in force mode! Set flag to wake it up after first move. */
10471           setboardSpoiledMachineBlack = 1;
10472     }
10473
10474     if (cps->sendICS) {
10475       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10476       SendToProgram(buf, cps);
10477     }
10478     cps->maybeThinking = FALSE;
10479     cps->offeredDraw = 0;
10480     if (!appData.icsActive) {
10481         SendTimeControl(cps, movesPerSession, timeControl,
10482                         timeIncrement, appData.searchDepth,
10483                         searchTime);
10484     }
10485     if (appData.showThinking
10486         // [HGM] thinking: four options require thinking output to be sent
10487         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10488                                 ) {
10489         SendToProgram("post\n", cps);
10490     }
10491     SendToProgram("hard\n", cps);
10492     if (!appData.ponderNextMove) {
10493         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10494            it without being sure what state we are in first.  "hard"
10495            is not a toggle, so that one is OK.
10496          */
10497         SendToProgram("easy\n", cps);
10498     }
10499     if (cps->usePing) {
10500       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10501       SendToProgram(buf, cps);
10502     }
10503     cps->initDone = TRUE;
10504     ClearEngineOutputPane(cps == &second);
10505 }
10506
10507
10508 void
10509 ResendOptions (ChessProgramState *cps)
10510 { // send the stored value of the options
10511   int i;
10512   char buf[MSG_SIZ];
10513   Option *opt = cps->option;
10514   for(i=0; i<cps->nrOptions; i++, opt++) {
10515       switch(opt->type) {
10516         case Spin:
10517         case Slider:
10518         case CheckBox:
10519             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10520           break;
10521         case ComboBox:
10522           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10523           break;
10524         default:
10525             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10526           break;
10527         case Button:
10528         case SaveButton:
10529           continue;
10530       }
10531       SendToProgram(buf, cps);
10532   }
10533 }
10534
10535 void
10536 StartChessProgram (ChessProgramState *cps)
10537 {
10538     char buf[MSG_SIZ];
10539     int err;
10540
10541     if (appData.noChessProgram) return;
10542     cps->initDone = FALSE;
10543
10544     if (strcmp(cps->host, "localhost") == 0) {
10545         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10546     } else if (*appData.remoteShell == NULLCHAR) {
10547         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10548     } else {
10549         if (*appData.remoteUser == NULLCHAR) {
10550           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10551                     cps->program);
10552         } else {
10553           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10554                     cps->host, appData.remoteUser, cps->program);
10555         }
10556         err = StartChildProcess(buf, "", &cps->pr);
10557     }
10558
10559     if (err != 0) {
10560       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10561         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10562         if(cps != &first) return;
10563         appData.noChessProgram = TRUE;
10564         ThawUI();
10565         SetNCPMode();
10566 //      DisplayFatalError(buf, err, 1);
10567 //      cps->pr = NoProc;
10568 //      cps->isr = NULL;
10569         return;
10570     }
10571
10572     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10573     if (cps->protocolVersion > 1) {
10574       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10575       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10576         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10577         cps->comboCnt = 0;  //                and values of combo boxes
10578       }
10579       SendToProgram(buf, cps);
10580       if(cps->reload) ResendOptions(cps);
10581     } else {
10582       SendToProgram("xboard\n", cps);
10583     }
10584 }
10585
10586 void
10587 TwoMachinesEventIfReady P((void))
10588 {
10589   static int curMess = 0;
10590   if (first.lastPing != first.lastPong) {
10591     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10592     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10593     return;
10594   }
10595   if (second.lastPing != second.lastPong) {
10596     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10597     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10598     return;
10599   }
10600   DisplayMessage("", ""); curMess = 0;
10601   TwoMachinesEvent();
10602 }
10603
10604 char *
10605 MakeName (char *template)
10606 {
10607     time_t clock;
10608     struct tm *tm;
10609     static char buf[MSG_SIZ];
10610     char *p = buf;
10611     int i;
10612
10613     clock = time((time_t *)NULL);
10614     tm = localtime(&clock);
10615
10616     while(*p++ = *template++) if(p[-1] == '%') {
10617         switch(*template++) {
10618           case 0:   *p = 0; return buf;
10619           case 'Y': i = tm->tm_year+1900; break;
10620           case 'y': i = tm->tm_year-100; break;
10621           case 'M': i = tm->tm_mon+1; break;
10622           case 'd': i = tm->tm_mday; break;
10623           case 'h': i = tm->tm_hour; break;
10624           case 'm': i = tm->tm_min; break;
10625           case 's': i = tm->tm_sec; break;
10626           default:  i = 0;
10627         }
10628         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10629     }
10630     return buf;
10631 }
10632
10633 int
10634 CountPlayers (char *p)
10635 {
10636     int n = 0;
10637     while(p = strchr(p, '\n')) p++, n++; // count participants
10638     return n;
10639 }
10640
10641 FILE *
10642 WriteTourneyFile (char *results, FILE *f)
10643 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10644     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10645     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10646         // create a file with tournament description
10647         fprintf(f, "-participants {%s}\n", appData.participants);
10648         fprintf(f, "-seedBase %d\n", appData.seedBase);
10649         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10650         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10651         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10652         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10653         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10654         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10655         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10656         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10657         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10658         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10659         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10660         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10661         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10662         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10663         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10664         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10665         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10666         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10667         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10668         fprintf(f, "-smpCores %d\n", appData.smpCores);
10669         if(searchTime > 0)
10670                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10671         else {
10672                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10673                 fprintf(f, "-tc %s\n", appData.timeControl);
10674                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10675         }
10676         fprintf(f, "-results \"%s\"\n", results);
10677     }
10678     return f;
10679 }
10680
10681 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10682
10683 void
10684 Substitute (char *participants, int expunge)
10685 {
10686     int i, changed, changes=0, nPlayers=0;
10687     char *p, *q, *r, buf[MSG_SIZ];
10688     if(participants == NULL) return;
10689     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10690     r = p = participants; q = appData.participants;
10691     while(*p && *p == *q) {
10692         if(*p == '\n') r = p+1, nPlayers++;
10693         p++; q++;
10694     }
10695     if(*p) { // difference
10696         while(*p && *p++ != '\n');
10697         while(*q && *q++ != '\n');
10698       changed = nPlayers;
10699         changes = 1 + (strcmp(p, q) != 0);
10700     }
10701     if(changes == 1) { // a single engine mnemonic was changed
10702         q = r; while(*q) nPlayers += (*q++ == '\n');
10703         p = buf; while(*r && (*p = *r++) != '\n') p++;
10704         *p = NULLCHAR;
10705         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10706         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10707         if(mnemonic[i]) { // The substitute is valid
10708             FILE *f;
10709             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10710                 flock(fileno(f), LOCK_EX);
10711                 ParseArgsFromFile(f);
10712                 fseek(f, 0, SEEK_SET);
10713                 FREE(appData.participants); appData.participants = participants;
10714                 if(expunge) { // erase results of replaced engine
10715                     int len = strlen(appData.results), w, b, dummy;
10716                     for(i=0; i<len; i++) {
10717                         Pairing(i, nPlayers, &w, &b, &dummy);
10718                         if((w == changed || b == changed) && appData.results[i] == '*') {
10719                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10720                             fclose(f);
10721                             return;
10722                         }
10723                     }
10724                     for(i=0; i<len; i++) {
10725                         Pairing(i, nPlayers, &w, &b, &dummy);
10726                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10727                     }
10728                 }
10729                 WriteTourneyFile(appData.results, f);
10730                 fclose(f); // release lock
10731                 return;
10732             }
10733         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10734     }
10735     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10736     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10737     free(participants);
10738     return;
10739 }
10740
10741 int
10742 CheckPlayers (char *participants)
10743 {
10744         int i;
10745         char buf[MSG_SIZ], *p;
10746         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10747         while(p = strchr(participants, '\n')) {
10748             *p = NULLCHAR;
10749             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10750             if(!mnemonic[i]) {
10751                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10752                 *p = '\n';
10753                 DisplayError(buf, 0);
10754                 return 1;
10755             }
10756             *p = '\n';
10757             participants = p + 1;
10758         }
10759         return 0;
10760 }
10761
10762 int
10763 CreateTourney (char *name)
10764 {
10765         FILE *f;
10766         if(matchMode && strcmp(name, appData.tourneyFile)) {
10767              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10768         }
10769         if(name[0] == NULLCHAR) {
10770             if(appData.participants[0])
10771                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10772             return 0;
10773         }
10774         f = fopen(name, "r");
10775         if(f) { // file exists
10776             ASSIGN(appData.tourneyFile, name);
10777             ParseArgsFromFile(f); // parse it
10778         } else {
10779             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10780             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10781                 DisplayError(_("Not enough participants"), 0);
10782                 return 0;
10783             }
10784             if(CheckPlayers(appData.participants)) return 0;
10785             ASSIGN(appData.tourneyFile, name);
10786             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10787             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10788         }
10789         fclose(f);
10790         appData.noChessProgram = FALSE;
10791         appData.clockMode = TRUE;
10792         SetGNUMode();
10793         return 1;
10794 }
10795
10796 int
10797 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10798 {
10799     char buf[MSG_SIZ], *p, *q;
10800     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10801     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10802     skip = !all && group[0]; // if group requested, we start in skip mode
10803     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10804         p = names; q = buf; header = 0;
10805         while(*p && *p != '\n') *q++ = *p++;
10806         *q = 0;
10807         if(*p == '\n') p++;
10808         if(buf[0] == '#') {
10809             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10810             depth++; // we must be entering a new group
10811             if(all) continue; // suppress printing group headers when complete list requested
10812             header = 1;
10813             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10814         }
10815         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10816         if(engineList[i]) free(engineList[i]);
10817         engineList[i] = strdup(buf);
10818         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10819         if(engineMnemonic[i]) free(engineMnemonic[i]);
10820         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10821             strcat(buf, " (");
10822             sscanf(q + 8, "%s", buf + strlen(buf));
10823             strcat(buf, ")");
10824         }
10825         engineMnemonic[i] = strdup(buf);
10826         i++;
10827     }
10828     engineList[i] = engineMnemonic[i] = NULL;
10829     return i;
10830 }
10831
10832 // following implemented as macro to avoid type limitations
10833 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10834
10835 void
10836 SwapEngines (int n)
10837 {   // swap settings for first engine and other engine (so far only some selected options)
10838     int h;
10839     char *p;
10840     if(n == 0) return;
10841     SWAP(directory, p)
10842     SWAP(chessProgram, p)
10843     SWAP(isUCI, h)
10844     SWAP(hasOwnBookUCI, h)
10845     SWAP(protocolVersion, h)
10846     SWAP(reuse, h)
10847     SWAP(scoreIsAbsolute, h)
10848     SWAP(timeOdds, h)
10849     SWAP(logo, p)
10850     SWAP(pgnName, p)
10851     SWAP(pvSAN, h)
10852     SWAP(engOptions, p)
10853     SWAP(engInitString, p)
10854     SWAP(computerString, p)
10855     SWAP(features, p)
10856     SWAP(fenOverride, p)
10857     SWAP(NPS, h)
10858     SWAP(accumulateTC, h)
10859     SWAP(drawDepth, h)
10860     SWAP(host, p)
10861     SWAP(pseudo, h)
10862 }
10863
10864 int
10865 GetEngineLine (char *s, int n)
10866 {
10867     int i;
10868     char buf[MSG_SIZ];
10869     extern char *icsNames;
10870     if(!s || !*s) return 0;
10871     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10872     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10873     if(!mnemonic[i]) return 0;
10874     if(n == 11) return 1; // just testing if there was a match
10875     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10876     if(n == 1) SwapEngines(n);
10877     ParseArgsFromString(buf);
10878     if(n == 1) SwapEngines(n);
10879     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10880         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10881         ParseArgsFromString(buf);
10882     }
10883     return 1;
10884 }
10885
10886 int
10887 SetPlayer (int player, char *p)
10888 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10889     int i;
10890     char buf[MSG_SIZ], *engineName;
10891     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10892     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10893     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10894     if(mnemonic[i]) {
10895         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10896         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10897         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10898         ParseArgsFromString(buf);
10899     } else { // no engine with this nickname is installed!
10900         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10901         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10902         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10903         ModeHighlight();
10904         DisplayError(buf, 0);
10905         return 0;
10906     }
10907     free(engineName);
10908     return i;
10909 }
10910
10911 char *recentEngines;
10912
10913 void
10914 RecentEngineEvent (int nr)
10915 {
10916     int n;
10917 //    SwapEngines(1); // bump first to second
10918 //    ReplaceEngine(&second, 1); // and load it there
10919     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10920     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10921     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10922         ReplaceEngine(&first, 0);
10923         FloatToFront(&appData.recentEngineList, command[n]);
10924     }
10925 }
10926
10927 int
10928 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10929 {   // determine players from game number
10930     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10931
10932     if(appData.tourneyType == 0) {
10933         roundsPerCycle = (nPlayers - 1) | 1;
10934         pairingsPerRound = nPlayers / 2;
10935     } else if(appData.tourneyType > 0) {
10936         roundsPerCycle = nPlayers - appData.tourneyType;
10937         pairingsPerRound = appData.tourneyType;
10938     }
10939     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10940     gamesPerCycle = gamesPerRound * roundsPerCycle;
10941     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10942     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10943     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10944     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10945     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10946     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10947
10948     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10949     if(appData.roundSync) *syncInterval = gamesPerRound;
10950
10951     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10952
10953     if(appData.tourneyType == 0) {
10954         if(curPairing == (nPlayers-1)/2 ) {
10955             *whitePlayer = curRound;
10956             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10957         } else {
10958             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10959             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10960             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10961             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10962         }
10963     } else if(appData.tourneyType > 1) {
10964         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10965         *whitePlayer = curRound + appData.tourneyType;
10966     } else if(appData.tourneyType > 0) {
10967         *whitePlayer = curPairing;
10968         *blackPlayer = curRound + appData.tourneyType;
10969     }
10970
10971     // take care of white/black alternation per round.
10972     // For cycles and games this is already taken care of by default, derived from matchGame!
10973     return curRound & 1;
10974 }
10975
10976 int
10977 NextTourneyGame (int nr, int *swapColors)
10978 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10979     char *p, *q;
10980     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10981     FILE *tf;
10982     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10983     tf = fopen(appData.tourneyFile, "r");
10984     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10985     ParseArgsFromFile(tf); fclose(tf);
10986     InitTimeControls(); // TC might be altered from tourney file
10987
10988     nPlayers = CountPlayers(appData.participants); // count participants
10989     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10990     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10991
10992     if(syncInterval) {
10993         p = q = appData.results;
10994         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10995         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10996             DisplayMessage(_("Waiting for other game(s)"),"");
10997             waitingForGame = TRUE;
10998             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10999             return 0;
11000         }
11001         waitingForGame = FALSE;
11002     }
11003
11004     if(appData.tourneyType < 0) {
11005         if(nr>=0 && !pairingReceived) {
11006             char buf[1<<16];
11007             if(pairing.pr == NoProc) {
11008                 if(!appData.pairingEngine[0]) {
11009                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11010                     return 0;
11011                 }
11012                 StartChessProgram(&pairing); // starts the pairing engine
11013             }
11014             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11015             SendToProgram(buf, &pairing);
11016             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11017             SendToProgram(buf, &pairing);
11018             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11019         }
11020         pairingReceived = 0;                              // ... so we continue here
11021         *swapColors = 0;
11022         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11023         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11024         matchGame = 1; roundNr = nr / syncInterval + 1;
11025     }
11026
11027     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11028
11029     // redefine engines, engine dir, etc.
11030     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11031     if(first.pr == NoProc) {
11032       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11033       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11034     }
11035     if(second.pr == NoProc) {
11036       SwapEngines(1);
11037       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11038       SwapEngines(1);         // and make that valid for second engine by swapping
11039       InitEngine(&second, 1);
11040     }
11041     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11042     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11043     return OK;
11044 }
11045
11046 void
11047 NextMatchGame ()
11048 {   // performs game initialization that does not invoke engines, and then tries to start the game
11049     int res, firstWhite, swapColors = 0;
11050     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11051     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
11052         char buf[MSG_SIZ];
11053         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11054         if(strcmp(buf, currentDebugFile)) { // name has changed
11055             FILE *f = fopen(buf, "w");
11056             if(f) { // if opening the new file failed, just keep using the old one
11057                 ASSIGN(currentDebugFile, buf);
11058                 fclose(debugFP);
11059                 debugFP = f;
11060             }
11061             if(appData.serverFileName) {
11062                 if(serverFP) fclose(serverFP);
11063                 serverFP = fopen(appData.serverFileName, "w");
11064                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11065                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11066             }
11067         }
11068     }
11069     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11070     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11071     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11072     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11073     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11074     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11075     Reset(FALSE, first.pr != NoProc);
11076     res = LoadGameOrPosition(matchGame); // setup game
11077     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11078     if(!res) return; // abort when bad game/pos file
11079     TwoMachinesEvent();
11080 }
11081
11082 void
11083 UserAdjudicationEvent (int result)
11084 {
11085     ChessMove gameResult = GameIsDrawn;
11086
11087     if( result > 0 ) {
11088         gameResult = WhiteWins;
11089     }
11090     else if( result < 0 ) {
11091         gameResult = BlackWins;
11092     }
11093
11094     if( gameMode == TwoMachinesPlay ) {
11095         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11096     }
11097 }
11098
11099
11100 // [HGM] save: calculate checksum of game to make games easily identifiable
11101 int
11102 StringCheckSum (char *s)
11103 {
11104         int i = 0;
11105         if(s==NULL) return 0;
11106         while(*s) i = i*259 + *s++;
11107         return i;
11108 }
11109
11110 int
11111 GameCheckSum ()
11112 {
11113         int i, sum=0;
11114         for(i=backwardMostMove; i<forwardMostMove; i++) {
11115                 sum += pvInfoList[i].depth;
11116                 sum += StringCheckSum(parseList[i]);
11117                 sum += StringCheckSum(commentList[i]);
11118                 sum *= 261;
11119         }
11120         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11121         return sum + StringCheckSum(commentList[i]);
11122 } // end of save patch
11123
11124 void
11125 GameEnds (ChessMove result, char *resultDetails, int whosays)
11126 {
11127     GameMode nextGameMode;
11128     int isIcsGame;
11129     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11130
11131     if(endingGame) return; /* [HGM] crash: forbid recursion */
11132     endingGame = 1;
11133     if(twoBoards) { // [HGM] dual: switch back to one board
11134         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11135         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11136     }
11137     if (appData.debugMode) {
11138       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11139               result, resultDetails ? resultDetails : "(null)", whosays);
11140     }
11141
11142     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11143
11144     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11145
11146     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11147         /* If we are playing on ICS, the server decides when the
11148            game is over, but the engine can offer to draw, claim
11149            a draw, or resign.
11150          */
11151 #if ZIPPY
11152         if (appData.zippyPlay && first.initDone) {
11153             if (result == GameIsDrawn) {
11154                 /* In case draw still needs to be claimed */
11155                 SendToICS(ics_prefix);
11156                 SendToICS("draw\n");
11157             } else if (StrCaseStr(resultDetails, "resign")) {
11158                 SendToICS(ics_prefix);
11159                 SendToICS("resign\n");
11160             }
11161         }
11162 #endif
11163         endingGame = 0; /* [HGM] crash */
11164         return;
11165     }
11166
11167     /* If we're loading the game from a file, stop */
11168     if (whosays == GE_FILE) {
11169       (void) StopLoadGameTimer();
11170       gameFileFP = NULL;
11171     }
11172
11173     /* Cancel draw offers */
11174     first.offeredDraw = second.offeredDraw = 0;
11175
11176     /* If this is an ICS game, only ICS can really say it's done;
11177        if not, anyone can. */
11178     isIcsGame = (gameMode == IcsPlayingWhite ||
11179                  gameMode == IcsPlayingBlack ||
11180                  gameMode == IcsObserving    ||
11181                  gameMode == IcsExamining);
11182
11183     if (!isIcsGame || whosays == GE_ICS) {
11184         /* OK -- not an ICS game, or ICS said it was done */
11185         StopClocks();
11186         if (!isIcsGame && !appData.noChessProgram)
11187           SetUserThinkingEnables();
11188
11189         /* [HGM] if a machine claims the game end we verify this claim */
11190         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11191             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11192                 char claimer;
11193                 ChessMove trueResult = (ChessMove) -1;
11194
11195                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11196                                             first.twoMachinesColor[0] :
11197                                             second.twoMachinesColor[0] ;
11198
11199                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11200                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11201                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11202                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11203                 } else
11204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11206                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11207                 } else
11208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11209                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11210                 }
11211
11212                 // now verify win claims, but not in drop games, as we don't understand those yet
11213                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11214                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11215                     (result == WhiteWins && claimer == 'w' ||
11216                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11217                       if (appData.debugMode) {
11218                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11219                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11220                       }
11221                       if(result != trueResult) {
11222                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11223                               result = claimer == 'w' ? BlackWins : WhiteWins;
11224                               resultDetails = buf;
11225                       }
11226                 } else
11227                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11228                     && (forwardMostMove <= backwardMostMove ||
11229                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11230                         (claimer=='b')==(forwardMostMove&1))
11231                                                                                   ) {
11232                       /* [HGM] verify: draws that were not flagged are false claims */
11233                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11234                       result = claimer == 'w' ? BlackWins : WhiteWins;
11235                       resultDetails = buf;
11236                 }
11237                 /* (Claiming a loss is accepted no questions asked!) */
11238             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11239                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11240                 result = GameUnfinished;
11241                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11242             }
11243             /* [HGM] bare: don't allow bare King to win */
11244             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11245                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11246                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11247                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11248                && result != GameIsDrawn)
11249             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11250                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11251                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11252                         if(p >= 0 && p <= (int)WhiteKing) k++;
11253                 }
11254                 if (appData.debugMode) {
11255                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11256                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11257                 }
11258                 if(k <= 1) {
11259                         result = GameIsDrawn;
11260                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11261                         resultDetails = buf;
11262                 }
11263             }
11264         }
11265
11266
11267         if(serverMoves != NULL && !loadFlag) { char c = '=';
11268             if(result==WhiteWins) c = '+';
11269             if(result==BlackWins) c = '-';
11270             if(resultDetails != NULL)
11271                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11272         }
11273         if (resultDetails != NULL) {
11274             gameInfo.result = result;
11275             gameInfo.resultDetails = StrSave(resultDetails);
11276
11277             /* display last move only if game was not loaded from file */
11278             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11279                 DisplayMove(currentMove - 1);
11280
11281             if (forwardMostMove != 0) {
11282                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11283                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11284                                                                 ) {
11285                     if (*appData.saveGameFile != NULLCHAR) {
11286                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11287                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11288                         else
11289                         SaveGameToFile(appData.saveGameFile, TRUE);
11290                     } else if (appData.autoSaveGames) {
11291                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11292                     }
11293                     if (*appData.savePositionFile != NULLCHAR) {
11294                         SavePositionToFile(appData.savePositionFile);
11295                     }
11296                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11297                 }
11298             }
11299
11300             /* Tell program how game ended in case it is learning */
11301             /* [HGM] Moved this to after saving the PGN, just in case */
11302             /* engine died and we got here through time loss. In that */
11303             /* case we will get a fatal error writing the pipe, which */
11304             /* would otherwise lose us the PGN.                       */
11305             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11306             /* output during GameEnds should never be fatal anymore   */
11307             if (gameMode == MachinePlaysWhite ||
11308                 gameMode == MachinePlaysBlack ||
11309                 gameMode == TwoMachinesPlay ||
11310                 gameMode == IcsPlayingWhite ||
11311                 gameMode == IcsPlayingBlack ||
11312                 gameMode == BeginningOfGame) {
11313                 char buf[MSG_SIZ];
11314                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11315                         resultDetails);
11316                 if (first.pr != NoProc) {
11317                     SendToProgram(buf, &first);
11318                 }
11319                 if (second.pr != NoProc &&
11320                     gameMode == TwoMachinesPlay) {
11321                     SendToProgram(buf, &second);
11322                 }
11323             }
11324         }
11325
11326         if (appData.icsActive) {
11327             if (appData.quietPlay &&
11328                 (gameMode == IcsPlayingWhite ||
11329                  gameMode == IcsPlayingBlack)) {
11330                 SendToICS(ics_prefix);
11331                 SendToICS("set shout 1\n");
11332             }
11333             nextGameMode = IcsIdle;
11334             ics_user_moved = FALSE;
11335             /* clean up premove.  It's ugly when the game has ended and the
11336              * premove highlights are still on the board.
11337              */
11338             if (gotPremove) {
11339               gotPremove = FALSE;
11340               ClearPremoveHighlights();
11341               DrawPosition(FALSE, boards[currentMove]);
11342             }
11343             if (whosays == GE_ICS) {
11344                 switch (result) {
11345                 case WhiteWins:
11346                     if (gameMode == IcsPlayingWhite)
11347                         PlayIcsWinSound();
11348                     else if(gameMode == IcsPlayingBlack)
11349                         PlayIcsLossSound();
11350                     break;
11351                 case BlackWins:
11352                     if (gameMode == IcsPlayingBlack)
11353                         PlayIcsWinSound();
11354                     else if(gameMode == IcsPlayingWhite)
11355                         PlayIcsLossSound();
11356                     break;
11357                 case GameIsDrawn:
11358                     PlayIcsDrawSound();
11359                     break;
11360                 default:
11361                     PlayIcsUnfinishedSound();
11362                 }
11363             }
11364             if(appData.quitNext) { ExitEvent(0); return; }
11365         } else if (gameMode == EditGame ||
11366                    gameMode == PlayFromGameFile ||
11367                    gameMode == AnalyzeMode ||
11368                    gameMode == AnalyzeFile) {
11369             nextGameMode = gameMode;
11370         } else {
11371             nextGameMode = EndOfGame;
11372         }
11373         pausing = FALSE;
11374         ModeHighlight();
11375     } else {
11376         nextGameMode = gameMode;
11377     }
11378
11379     if (appData.noChessProgram) {
11380         gameMode = nextGameMode;
11381         ModeHighlight();
11382         endingGame = 0; /* [HGM] crash */
11383         return;
11384     }
11385
11386     if (first.reuse) {
11387         /* Put first chess program into idle state */
11388         if (first.pr != NoProc &&
11389             (gameMode == MachinePlaysWhite ||
11390              gameMode == MachinePlaysBlack ||
11391              gameMode == TwoMachinesPlay ||
11392              gameMode == IcsPlayingWhite ||
11393              gameMode == IcsPlayingBlack ||
11394              gameMode == BeginningOfGame)) {
11395             SendToProgram("force\n", &first);
11396             if (first.usePing) {
11397               char buf[MSG_SIZ];
11398               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11399               SendToProgram(buf, &first);
11400             }
11401         }
11402     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11403         /* Kill off first chess program */
11404         if (first.isr != NULL)
11405           RemoveInputSource(first.isr);
11406         first.isr = NULL;
11407
11408         if (first.pr != NoProc) {
11409             ExitAnalyzeMode();
11410             DoSleep( appData.delayBeforeQuit );
11411             SendToProgram("quit\n", &first);
11412             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11413             first.reload = TRUE;
11414         }
11415         first.pr = NoProc;
11416     }
11417     if (second.reuse) {
11418         /* Put second chess program into idle state */
11419         if (second.pr != NoProc &&
11420             gameMode == TwoMachinesPlay) {
11421             SendToProgram("force\n", &second);
11422             if (second.usePing) {
11423               char buf[MSG_SIZ];
11424               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11425               SendToProgram(buf, &second);
11426             }
11427         }
11428     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11429         /* Kill off second chess program */
11430         if (second.isr != NULL)
11431           RemoveInputSource(second.isr);
11432         second.isr = NULL;
11433
11434         if (second.pr != NoProc) {
11435             DoSleep( appData.delayBeforeQuit );
11436             SendToProgram("quit\n", &second);
11437             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11438             second.reload = TRUE;
11439         }
11440         second.pr = NoProc;
11441     }
11442
11443     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11444         char resChar = '=';
11445         switch (result) {
11446         case WhiteWins:
11447           resChar = '+';
11448           if (first.twoMachinesColor[0] == 'w') {
11449             first.matchWins++;
11450           } else {
11451             second.matchWins++;
11452           }
11453           break;
11454         case BlackWins:
11455           resChar = '-';
11456           if (first.twoMachinesColor[0] == 'b') {
11457             first.matchWins++;
11458           } else {
11459             second.matchWins++;
11460           }
11461           break;
11462         case GameUnfinished:
11463           resChar = ' ';
11464         default:
11465           break;
11466         }
11467
11468         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11469         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11470             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11471             ReserveGame(nextGame, resChar); // sets nextGame
11472             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11473             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11474         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11475
11476         if (nextGame <= appData.matchGames && !abortMatch) {
11477             gameMode = nextGameMode;
11478             matchGame = nextGame; // this will be overruled in tourney mode!
11479             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11480             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11481             endingGame = 0; /* [HGM] crash */
11482             return;
11483         } else {
11484             gameMode = nextGameMode;
11485             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11486                      first.tidy, second.tidy,
11487                      first.matchWins, second.matchWins,
11488                      appData.matchGames - (first.matchWins + second.matchWins));
11489             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11490             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11491             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11492             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11493                 first.twoMachinesColor = "black\n";
11494                 second.twoMachinesColor = "white\n";
11495             } else {
11496                 first.twoMachinesColor = "white\n";
11497                 second.twoMachinesColor = "black\n";
11498             }
11499         }
11500     }
11501     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11502         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11503       ExitAnalyzeMode();
11504     gameMode = nextGameMode;
11505     ModeHighlight();
11506     endingGame = 0;  /* [HGM] crash */
11507     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11508         if(matchMode == TRUE) { // match through command line: exit with or without popup
11509             if(ranking) {
11510                 ToNrEvent(forwardMostMove);
11511                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11512                 else ExitEvent(0);
11513             } else DisplayFatalError(buf, 0, 0);
11514         } else { // match through menu; just stop, with or without popup
11515             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11516             ModeHighlight();
11517             if(ranking){
11518                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11519             } else DisplayNote(buf);
11520       }
11521       if(ranking) free(ranking);
11522     }
11523 }
11524
11525 /* Assumes program was just initialized (initString sent).
11526    Leaves program in force mode. */
11527 void
11528 FeedMovesToProgram (ChessProgramState *cps, int upto)
11529 {
11530     int i;
11531
11532     if (appData.debugMode)
11533       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11534               startedFromSetupPosition ? "position and " : "",
11535               backwardMostMove, upto, cps->which);
11536     if(currentlyInitializedVariant != gameInfo.variant) {
11537       char buf[MSG_SIZ];
11538         // [HGM] variantswitch: make engine aware of new variant
11539         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11540                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11541                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11542         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11543         SendToProgram(buf, cps);
11544         currentlyInitializedVariant = gameInfo.variant;
11545     }
11546     SendToProgram("force\n", cps);
11547     if (startedFromSetupPosition) {
11548         SendBoard(cps, backwardMostMove);
11549     if (appData.debugMode) {
11550         fprintf(debugFP, "feedMoves\n");
11551     }
11552     }
11553     for (i = backwardMostMove; i < upto; i++) {
11554         SendMoveToProgram(i, cps);
11555     }
11556 }
11557
11558
11559 int
11560 ResurrectChessProgram ()
11561 {
11562      /* The chess program may have exited.
11563         If so, restart it and feed it all the moves made so far. */
11564     static int doInit = 0;
11565
11566     if (appData.noChessProgram) return 1;
11567
11568     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11569         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11570         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11571         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11572     } else {
11573         if (first.pr != NoProc) return 1;
11574         StartChessProgram(&first);
11575     }
11576     InitChessProgram(&first, FALSE);
11577     FeedMovesToProgram(&first, currentMove);
11578
11579     if (!first.sendTime) {
11580         /* can't tell gnuchess what its clock should read,
11581            so we bow to its notion. */
11582         ResetClocks();
11583         timeRemaining[0][currentMove] = whiteTimeRemaining;
11584         timeRemaining[1][currentMove] = blackTimeRemaining;
11585     }
11586
11587     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11588                 appData.icsEngineAnalyze) && first.analysisSupport) {
11589       SendToProgram("analyze\n", &first);
11590       first.analyzing = TRUE;
11591     }
11592     return 1;
11593 }
11594
11595 /*
11596  * Button procedures
11597  */
11598 void
11599 Reset (int redraw, int init)
11600 {
11601     int i;
11602
11603     if (appData.debugMode) {
11604         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11605                 redraw, init, gameMode);
11606     }
11607     CleanupTail(); // [HGM] vari: delete any stored variations
11608     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11609     pausing = pauseExamInvalid = FALSE;
11610     startedFromSetupPosition = blackPlaysFirst = FALSE;
11611     firstMove = TRUE;
11612     whiteFlag = blackFlag = FALSE;
11613     userOfferedDraw = FALSE;
11614     hintRequested = bookRequested = FALSE;
11615     first.maybeThinking = FALSE;
11616     second.maybeThinking = FALSE;
11617     first.bookSuspend = FALSE; // [HGM] book
11618     second.bookSuspend = FALSE;
11619     thinkOutput[0] = NULLCHAR;
11620     lastHint[0] = NULLCHAR;
11621     ClearGameInfo(&gameInfo);
11622     gameInfo.variant = StringToVariant(appData.variant);
11623     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11624     ics_user_moved = ics_clock_paused = FALSE;
11625     ics_getting_history = H_FALSE;
11626     ics_gamenum = -1;
11627     white_holding[0] = black_holding[0] = NULLCHAR;
11628     ClearProgramStats();
11629     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11630
11631     ResetFrontEnd();
11632     ClearHighlights();
11633     flipView = appData.flipView;
11634     ClearPremoveHighlights();
11635     gotPremove = FALSE;
11636     alarmSounded = FALSE;
11637     killX = killY = -1; // [HGM] lion
11638
11639     GameEnds(EndOfFile, NULL, GE_PLAYER);
11640     if(appData.serverMovesName != NULL) {
11641         /* [HGM] prepare to make moves file for broadcasting */
11642         clock_t t = clock();
11643         if(serverMoves != NULL) fclose(serverMoves);
11644         serverMoves = fopen(appData.serverMovesName, "r");
11645         if(serverMoves != NULL) {
11646             fclose(serverMoves);
11647             /* delay 15 sec before overwriting, so all clients can see end */
11648             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11649         }
11650         serverMoves = fopen(appData.serverMovesName, "w");
11651     }
11652
11653     ExitAnalyzeMode();
11654     gameMode = BeginningOfGame;
11655     ModeHighlight();
11656     if(appData.icsActive) gameInfo.variant = VariantNormal;
11657     currentMove = forwardMostMove = backwardMostMove = 0;
11658     MarkTargetSquares(1);
11659     InitPosition(redraw);
11660     for (i = 0; i < MAX_MOVES; i++) {
11661         if (commentList[i] != NULL) {
11662             free(commentList[i]);
11663             commentList[i] = NULL;
11664         }
11665     }
11666     ResetClocks();
11667     timeRemaining[0][0] = whiteTimeRemaining;
11668     timeRemaining[1][0] = blackTimeRemaining;
11669
11670     if (first.pr == NoProc) {
11671         StartChessProgram(&first);
11672     }
11673     if (init) {
11674             InitChessProgram(&first, startedFromSetupPosition);
11675     }
11676     DisplayTitle("");
11677     DisplayMessage("", "");
11678     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11679     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11680     ClearMap();        // [HGM] exclude: invalidate map
11681 }
11682
11683 void
11684 AutoPlayGameLoop ()
11685 {
11686     for (;;) {
11687         if (!AutoPlayOneMove())
11688           return;
11689         if (matchMode || appData.timeDelay == 0)
11690           continue;
11691         if (appData.timeDelay < 0)
11692           return;
11693         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11694         break;
11695     }
11696 }
11697
11698 void
11699 AnalyzeNextGame()
11700 {
11701     ReloadGame(1); // next game
11702 }
11703
11704 int
11705 AutoPlayOneMove ()
11706 {
11707     int fromX, fromY, toX, toY;
11708
11709     if (appData.debugMode) {
11710       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11711     }
11712
11713     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11714       return FALSE;
11715
11716     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11717       pvInfoList[currentMove].depth = programStats.depth;
11718       pvInfoList[currentMove].score = programStats.score;
11719       pvInfoList[currentMove].time  = 0;
11720       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11721       else { // append analysis of final position as comment
11722         char buf[MSG_SIZ];
11723         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11724         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11725       }
11726       programStats.depth = 0;
11727     }
11728
11729     if (currentMove >= forwardMostMove) {
11730       if(gameMode == AnalyzeFile) {
11731           if(appData.loadGameIndex == -1) {
11732             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11733           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11734           } else {
11735           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11736         }
11737       }
11738 //      gameMode = EndOfGame;
11739 //      ModeHighlight();
11740
11741       /* [AS] Clear current move marker at the end of a game */
11742       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11743
11744       return FALSE;
11745     }
11746
11747     toX = moveList[currentMove][2] - AAA;
11748     toY = moveList[currentMove][3] - ONE;
11749
11750     if (moveList[currentMove][1] == '@') {
11751         if (appData.highlightLastMove) {
11752             SetHighlights(-1, -1, toX, toY);
11753         }
11754     } else {
11755         fromX = moveList[currentMove][0] - AAA;
11756         fromY = moveList[currentMove][1] - ONE;
11757
11758         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11759
11760         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11761
11762         if (appData.highlightLastMove) {
11763             SetHighlights(fromX, fromY, toX, toY);
11764         }
11765     }
11766     DisplayMove(currentMove);
11767     SendMoveToProgram(currentMove++, &first);
11768     DisplayBothClocks();
11769     DrawPosition(FALSE, boards[currentMove]);
11770     // [HGM] PV info: always display, routine tests if empty
11771     DisplayComment(currentMove - 1, commentList[currentMove]);
11772     return TRUE;
11773 }
11774
11775
11776 int
11777 LoadGameOneMove (ChessMove readAhead)
11778 {
11779     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11780     char promoChar = NULLCHAR;
11781     ChessMove moveType;
11782     char move[MSG_SIZ];
11783     char *p, *q;
11784
11785     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11786         gameMode != AnalyzeMode && gameMode != Training) {
11787         gameFileFP = NULL;
11788         return FALSE;
11789     }
11790
11791     yyboardindex = forwardMostMove;
11792     if (readAhead != EndOfFile) {
11793       moveType = readAhead;
11794     } else {
11795       if (gameFileFP == NULL)
11796           return FALSE;
11797       moveType = (ChessMove) Myylex();
11798     }
11799
11800     done = FALSE;
11801     switch (moveType) {
11802       case Comment:
11803         if (appData.debugMode)
11804           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11805         p = yy_text;
11806
11807         /* append the comment but don't display it */
11808         AppendComment(currentMove, p, FALSE);
11809         return TRUE;
11810
11811       case WhiteCapturesEnPassant:
11812       case BlackCapturesEnPassant:
11813       case WhitePromotion:
11814       case BlackPromotion:
11815       case WhiteNonPromotion:
11816       case BlackNonPromotion:
11817       case NormalMove:
11818       case FirstLeg:
11819       case WhiteKingSideCastle:
11820       case WhiteQueenSideCastle:
11821       case BlackKingSideCastle:
11822       case BlackQueenSideCastle:
11823       case WhiteKingSideCastleWild:
11824       case WhiteQueenSideCastleWild:
11825       case BlackKingSideCastleWild:
11826       case BlackQueenSideCastleWild:
11827       /* PUSH Fabien */
11828       case WhiteHSideCastleFR:
11829       case WhiteASideCastleFR:
11830       case BlackHSideCastleFR:
11831       case BlackASideCastleFR:
11832       /* POP Fabien */
11833         if (appData.debugMode)
11834           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11835         fromX = currentMoveString[0] - AAA;
11836         fromY = currentMoveString[1] - ONE;
11837         toX = currentMoveString[2] - AAA;
11838         toY = currentMoveString[3] - ONE;
11839         promoChar = currentMoveString[4];
11840         if(promoChar == ';') promoChar = NULLCHAR;
11841         break;
11842
11843       case WhiteDrop:
11844       case BlackDrop:
11845         if (appData.debugMode)
11846           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11847         fromX = moveType == WhiteDrop ?
11848           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11849         (int) CharToPiece(ToLower(currentMoveString[0]));
11850         fromY = DROP_RANK;
11851         toX = currentMoveString[2] - AAA;
11852         toY = currentMoveString[3] - ONE;
11853         break;
11854
11855       case WhiteWins:
11856       case BlackWins:
11857       case GameIsDrawn:
11858       case GameUnfinished:
11859         if (appData.debugMode)
11860           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11861         p = strchr(yy_text, '{');
11862         if (p == NULL) p = strchr(yy_text, '(');
11863         if (p == NULL) {
11864             p = yy_text;
11865             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11866         } else {
11867             q = strchr(p, *p == '{' ? '}' : ')');
11868             if (q != NULL) *q = NULLCHAR;
11869             p++;
11870         }
11871         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11872         GameEnds(moveType, p, GE_FILE);
11873         done = TRUE;
11874         if (cmailMsgLoaded) {
11875             ClearHighlights();
11876             flipView = WhiteOnMove(currentMove);
11877             if (moveType == GameUnfinished) flipView = !flipView;
11878             if (appData.debugMode)
11879               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11880         }
11881         break;
11882
11883       case EndOfFile:
11884         if (appData.debugMode)
11885           fprintf(debugFP, "Parser hit end of file\n");
11886         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11887           case MT_NONE:
11888           case MT_CHECK:
11889             break;
11890           case MT_CHECKMATE:
11891           case MT_STAINMATE:
11892             if (WhiteOnMove(currentMove)) {
11893                 GameEnds(BlackWins, "Black mates", GE_FILE);
11894             } else {
11895                 GameEnds(WhiteWins, "White mates", GE_FILE);
11896             }
11897             break;
11898           case MT_STALEMATE:
11899             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11900             break;
11901         }
11902         done = TRUE;
11903         break;
11904
11905       case MoveNumberOne:
11906         if (lastLoadGameStart == GNUChessGame) {
11907             /* GNUChessGames have numbers, but they aren't move numbers */
11908             if (appData.debugMode)
11909               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11910                       yy_text, (int) moveType);
11911             return LoadGameOneMove(EndOfFile); /* tail recursion */
11912         }
11913         /* else fall thru */
11914
11915       case XBoardGame:
11916       case GNUChessGame:
11917       case PGNTag:
11918         /* Reached start of next game in file */
11919         if (appData.debugMode)
11920           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11921         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11922           case MT_NONE:
11923           case MT_CHECK:
11924             break;
11925           case MT_CHECKMATE:
11926           case MT_STAINMATE:
11927             if (WhiteOnMove(currentMove)) {
11928                 GameEnds(BlackWins, "Black mates", GE_FILE);
11929             } else {
11930                 GameEnds(WhiteWins, "White mates", GE_FILE);
11931             }
11932             break;
11933           case MT_STALEMATE:
11934             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11935             break;
11936         }
11937         done = TRUE;
11938         break;
11939
11940       case PositionDiagram:     /* should not happen; ignore */
11941       case ElapsedTime:         /* ignore */
11942       case NAG:                 /* ignore */
11943         if (appData.debugMode)
11944           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11945                   yy_text, (int) moveType);
11946         return LoadGameOneMove(EndOfFile); /* tail recursion */
11947
11948       case IllegalMove:
11949         if (appData.testLegality) {
11950             if (appData.debugMode)
11951               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11952             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11953                     (forwardMostMove / 2) + 1,
11954                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11955             DisplayError(move, 0);
11956             done = TRUE;
11957         } else {
11958             if (appData.debugMode)
11959               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11960                       yy_text, currentMoveString);
11961             fromX = currentMoveString[0] - AAA;
11962             fromY = currentMoveString[1] - ONE;
11963             toX = currentMoveString[2] - AAA;
11964             toY = currentMoveString[3] - ONE;
11965             promoChar = currentMoveString[4];
11966         }
11967         break;
11968
11969       case AmbiguousMove:
11970         if (appData.debugMode)
11971           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11972         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11973                 (forwardMostMove / 2) + 1,
11974                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11975         DisplayError(move, 0);
11976         done = TRUE;
11977         break;
11978
11979       default:
11980       case ImpossibleMove:
11981         if (appData.debugMode)
11982           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11983         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11984                 (forwardMostMove / 2) + 1,
11985                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11986         DisplayError(move, 0);
11987         done = TRUE;
11988         break;
11989     }
11990
11991     if (done) {
11992         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11993             DrawPosition(FALSE, boards[currentMove]);
11994             DisplayBothClocks();
11995             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11996               DisplayComment(currentMove - 1, commentList[currentMove]);
11997         }
11998         (void) StopLoadGameTimer();
11999         gameFileFP = NULL;
12000         cmailOldMove = forwardMostMove;
12001         return FALSE;
12002     } else {
12003         /* currentMoveString is set as a side-effect of yylex */
12004
12005         thinkOutput[0] = NULLCHAR;
12006         MakeMove(fromX, fromY, toX, toY, promoChar);
12007         killX = killY = -1; // [HGM] lion: used up
12008         currentMove = forwardMostMove;
12009         return TRUE;
12010     }
12011 }
12012
12013 /* Load the nth game from the given file */
12014 int
12015 LoadGameFromFile (char *filename, int n, char *title, int useList)
12016 {
12017     FILE *f;
12018     char buf[MSG_SIZ];
12019
12020     if (strcmp(filename, "-") == 0) {
12021         f = stdin;
12022         title = "stdin";
12023     } else {
12024         f = fopen(filename, "rb");
12025         if (f == NULL) {
12026           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12027             DisplayError(buf, errno);
12028             return FALSE;
12029         }
12030     }
12031     if (fseek(f, 0, 0) == -1) {
12032         /* f is not seekable; probably a pipe */
12033         useList = FALSE;
12034     }
12035     if (useList && n == 0) {
12036         int error = GameListBuild(f);
12037         if (error) {
12038             DisplayError(_("Cannot build game list"), error);
12039         } else if (!ListEmpty(&gameList) &&
12040                    ((ListGame *) gameList.tailPred)->number > 1) {
12041             GameListPopUp(f, title);
12042             return TRUE;
12043         }
12044         GameListDestroy();
12045         n = 1;
12046     }
12047     if (n == 0) n = 1;
12048     return LoadGame(f, n, title, FALSE);
12049 }
12050
12051
12052 void
12053 MakeRegisteredMove ()
12054 {
12055     int fromX, fromY, toX, toY;
12056     char promoChar;
12057     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12058         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12059           case CMAIL_MOVE:
12060           case CMAIL_DRAW:
12061             if (appData.debugMode)
12062               fprintf(debugFP, "Restoring %s for game %d\n",
12063                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12064
12065             thinkOutput[0] = NULLCHAR;
12066             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12067             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12068             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12069             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12070             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12071             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12072             MakeMove(fromX, fromY, toX, toY, promoChar);
12073             ShowMove(fromX, fromY, toX, toY);
12074
12075             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12076               case MT_NONE:
12077               case MT_CHECK:
12078                 break;
12079
12080               case MT_CHECKMATE:
12081               case MT_STAINMATE:
12082                 if (WhiteOnMove(currentMove)) {
12083                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12084                 } else {
12085                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12086                 }
12087                 break;
12088
12089               case MT_STALEMATE:
12090                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12091                 break;
12092             }
12093
12094             break;
12095
12096           case CMAIL_RESIGN:
12097             if (WhiteOnMove(currentMove)) {
12098                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12099             } else {
12100                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12101             }
12102             break;
12103
12104           case CMAIL_ACCEPT:
12105             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12106             break;
12107
12108           default:
12109             break;
12110         }
12111     }
12112
12113     return;
12114 }
12115
12116 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12117 int
12118 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12119 {
12120     int retVal;
12121
12122     if (gameNumber > nCmailGames) {
12123         DisplayError(_("No more games in this message"), 0);
12124         return FALSE;
12125     }
12126     if (f == lastLoadGameFP) {
12127         int offset = gameNumber - lastLoadGameNumber;
12128         if (offset == 0) {
12129             cmailMsg[0] = NULLCHAR;
12130             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12131                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12132                 nCmailMovesRegistered--;
12133             }
12134             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12135             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12136                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12137             }
12138         } else {
12139             if (! RegisterMove()) return FALSE;
12140         }
12141     }
12142
12143     retVal = LoadGame(f, gameNumber, title, useList);
12144
12145     /* Make move registered during previous look at this game, if any */
12146     MakeRegisteredMove();
12147
12148     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12149         commentList[currentMove]
12150           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12151         DisplayComment(currentMove - 1, commentList[currentMove]);
12152     }
12153
12154     return retVal;
12155 }
12156
12157 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12158 int
12159 ReloadGame (int offset)
12160 {
12161     int gameNumber = lastLoadGameNumber + offset;
12162     if (lastLoadGameFP == NULL) {
12163         DisplayError(_("No game has been loaded yet"), 0);
12164         return FALSE;
12165     }
12166     if (gameNumber <= 0) {
12167         DisplayError(_("Can't back up any further"), 0);
12168         return FALSE;
12169     }
12170     if (cmailMsgLoaded) {
12171         return CmailLoadGame(lastLoadGameFP, gameNumber,
12172                              lastLoadGameTitle, lastLoadGameUseList);
12173     } else {
12174         return LoadGame(lastLoadGameFP, gameNumber,
12175                         lastLoadGameTitle, lastLoadGameUseList);
12176     }
12177 }
12178
12179 int keys[EmptySquare+1];
12180
12181 int
12182 PositionMatches (Board b1, Board b2)
12183 {
12184     int r, f, sum=0;
12185     switch(appData.searchMode) {
12186         case 1: return CompareWithRights(b1, b2);
12187         case 2:
12188             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12190             }
12191             return TRUE;
12192         case 3:
12193             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12195                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12196             }
12197             return sum==0;
12198         case 4:
12199             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12200                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12201             }
12202             return sum==0;
12203     }
12204     return TRUE;
12205 }
12206
12207 #define Q_PROMO  4
12208 #define Q_EP     3
12209 #define Q_BCASTL 2
12210 #define Q_WCASTL 1
12211
12212 int pieceList[256], quickBoard[256];
12213 ChessSquare pieceType[256] = { EmptySquare };
12214 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12215 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12216 int soughtTotal, turn;
12217 Boolean epOK, flipSearch;
12218
12219 typedef struct {
12220     unsigned char piece, to;
12221 } Move;
12222
12223 #define DSIZE (250000)
12224
12225 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12226 Move *moveDatabase = initialSpace;
12227 unsigned int movePtr, dataSize = DSIZE;
12228
12229 int
12230 MakePieceList (Board board, int *counts)
12231 {
12232     int r, f, n=Q_PROMO, total=0;
12233     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12234     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12235         int sq = f + (r<<4);
12236         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12237             quickBoard[sq] = ++n;
12238             pieceList[n] = sq;
12239             pieceType[n] = board[r][f];
12240             counts[board[r][f]]++;
12241             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12242             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12243             total++;
12244         }
12245     }
12246     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12247     return total;
12248 }
12249
12250 void
12251 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12252 {
12253     int sq = fromX + (fromY<<4);
12254     int piece = quickBoard[sq], rook;
12255     quickBoard[sq] = 0;
12256     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12257     if(piece == pieceList[1] && fromY == toY) {
12258       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12259         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12260         moveDatabase[movePtr++].piece = Q_WCASTL;
12261         quickBoard[sq] = piece;
12262         piece = quickBoard[from]; quickBoard[from] = 0;
12263         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12264       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12265         quickBoard[sq] = 0; // remove Rook
12266         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12267         moveDatabase[movePtr++].piece = Q_WCASTL;
12268         quickBoard[sq] = pieceList[1]; // put King
12269         piece = rook;
12270         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12271       }
12272     } else
12273     if(piece == pieceList[2] && fromY == toY) {
12274       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12275         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12276         moveDatabase[movePtr++].piece = Q_BCASTL;
12277         quickBoard[sq] = piece;
12278         piece = quickBoard[from]; quickBoard[from] = 0;
12279         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12280       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12281         quickBoard[sq] = 0; // remove Rook
12282         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12283         moveDatabase[movePtr++].piece = Q_BCASTL;
12284         quickBoard[sq] = pieceList[2]; // put King
12285         piece = rook;
12286         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12287       }
12288     } else
12289     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12290         quickBoard[(fromY<<4)+toX] = 0;
12291         moveDatabase[movePtr].piece = Q_EP;
12292         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12293         moveDatabase[movePtr].to = sq;
12294     } else
12295     if(promoPiece != pieceType[piece]) {
12296         moveDatabase[movePtr++].piece = Q_PROMO;
12297         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12298     }
12299     moveDatabase[movePtr].piece = piece;
12300     quickBoard[sq] = piece;
12301     movePtr++;
12302 }
12303
12304 int
12305 PackGame (Board board)
12306 {
12307     Move *newSpace = NULL;
12308     moveDatabase[movePtr].piece = 0; // terminate previous game
12309     if(movePtr > dataSize) {
12310         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12311         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12312         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12313         if(newSpace) {
12314             int i;
12315             Move *p = moveDatabase, *q = newSpace;
12316             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12317             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12318             moveDatabase = newSpace;
12319         } else { // calloc failed, we must be out of memory. Too bad...
12320             dataSize = 0; // prevent calloc events for all subsequent games
12321             return 0;     // and signal this one isn't cached
12322         }
12323     }
12324     movePtr++;
12325     MakePieceList(board, counts);
12326     return movePtr;
12327 }
12328
12329 int
12330 QuickCompare (Board board, int *minCounts, int *maxCounts)
12331 {   // compare according to search mode
12332     int r, f;
12333     switch(appData.searchMode)
12334     {
12335       case 1: // exact position match
12336         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12337         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12338             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12339         }
12340         break;
12341       case 2: // can have extra material on empty squares
12342         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12343             if(board[r][f] == EmptySquare) continue;
12344             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12345         }
12346         break;
12347       case 3: // material with exact Pawn structure
12348         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12349             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12350             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12351         } // fall through to material comparison
12352       case 4: // exact material
12353         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12354         break;
12355       case 6: // material range with given imbalance
12356         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12357         // fall through to range comparison
12358       case 5: // material range
12359         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12360     }
12361     return TRUE;
12362 }
12363
12364 int
12365 QuickScan (Board board, Move *move)
12366 {   // reconstruct game,and compare all positions in it
12367     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12368     do {
12369         int piece = move->piece;
12370         int to = move->to, from = pieceList[piece];
12371         if(!found) { // if already found just scan to game end for final piece count
12372           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12373            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12374            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12375                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12376             ) {
12377             static int lastCounts[EmptySquare+1];
12378             int i;
12379             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12380             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12381           } else stretch = 0;
12382           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12383           if(found && !appData.minPieces) return found;
12384         }
12385         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12386           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12387           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12388             piece = (++move)->piece;
12389             from = pieceList[piece];
12390             counts[pieceType[piece]]--;
12391             pieceType[piece] = (ChessSquare) move->to;
12392             counts[move->to]++;
12393           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12394             counts[pieceType[quickBoard[to]]]--;
12395             quickBoard[to] = 0; total--;
12396             move++;
12397             continue;
12398           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12399             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12400             from  = pieceList[piece]; // so this must be King
12401             quickBoard[from] = 0;
12402             pieceList[piece] = to;
12403             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12404             quickBoard[from] = 0; // rook
12405             quickBoard[to] = piece;
12406             to = move->to; piece = move->piece;
12407             goto aftercastle;
12408           }
12409         }
12410         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12411         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12412         quickBoard[from] = 0;
12413       aftercastle:
12414         quickBoard[to] = piece;
12415         pieceList[piece] = to;
12416         cnt++; turn ^= 3;
12417         move++;
12418     } while(1);
12419 }
12420
12421 void
12422 InitSearch ()
12423 {
12424     int r, f;
12425     flipSearch = FALSE;
12426     CopyBoard(soughtBoard, boards[currentMove]);
12427     soughtTotal = MakePieceList(soughtBoard, maxSought);
12428     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12429     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12430     CopyBoard(reverseBoard, boards[currentMove]);
12431     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12432         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12433         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12434         reverseBoard[r][f] = piece;
12435     }
12436     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12437     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12438     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12439                  || (boards[currentMove][CASTLING][2] == NoRights ||
12440                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12441                  && (boards[currentMove][CASTLING][5] == NoRights ||
12442                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12443       ) {
12444         flipSearch = TRUE;
12445         CopyBoard(flipBoard, soughtBoard);
12446         CopyBoard(rotateBoard, reverseBoard);
12447         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12448             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12449             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12450         }
12451     }
12452     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12453     if(appData.searchMode >= 5) {
12454         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12455         MakePieceList(soughtBoard, minSought);
12456         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12457     }
12458     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12459         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12460 }
12461
12462 GameInfo dummyInfo;
12463 static int creatingBook;
12464
12465 int
12466 GameContainsPosition (FILE *f, ListGame *lg)
12467 {
12468     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12469     int fromX, fromY, toX, toY;
12470     char promoChar;
12471     static int initDone=FALSE;
12472
12473     // weed out games based on numerical tag comparison
12474     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12475     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12476     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12477     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12478     if(!initDone) {
12479         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12480         initDone = TRUE;
12481     }
12482     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12483     else CopyBoard(boards[scratch], initialPosition); // default start position
12484     if(lg->moves) {
12485         turn = btm + 1;
12486         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12487         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12488     }
12489     if(btm) plyNr++;
12490     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12491     fseek(f, lg->offset, 0);
12492     yynewfile(f);
12493     while(1) {
12494         yyboardindex = scratch;
12495         quickFlag = plyNr+1;
12496         next = Myylex();
12497         quickFlag = 0;
12498         switch(next) {
12499             case PGNTag:
12500                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12501             default:
12502                 continue;
12503
12504             case XBoardGame:
12505             case GNUChessGame:
12506                 if(plyNr) return -1; // after we have seen moves, this is for new game
12507               continue;
12508
12509             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12510             case ImpossibleMove:
12511             case WhiteWins: // game ends here with these four
12512             case BlackWins:
12513             case GameIsDrawn:
12514             case GameUnfinished:
12515                 return -1;
12516
12517             case IllegalMove:
12518                 if(appData.testLegality) return -1;
12519             case WhiteCapturesEnPassant:
12520             case BlackCapturesEnPassant:
12521             case WhitePromotion:
12522             case BlackPromotion:
12523             case WhiteNonPromotion:
12524             case BlackNonPromotion:
12525             case NormalMove:
12526             case FirstLeg:
12527             case WhiteKingSideCastle:
12528             case WhiteQueenSideCastle:
12529             case BlackKingSideCastle:
12530             case BlackQueenSideCastle:
12531             case WhiteKingSideCastleWild:
12532             case WhiteQueenSideCastleWild:
12533             case BlackKingSideCastleWild:
12534             case BlackQueenSideCastleWild:
12535             case WhiteHSideCastleFR:
12536             case WhiteASideCastleFR:
12537             case BlackHSideCastleFR:
12538             case BlackASideCastleFR:
12539                 fromX = currentMoveString[0] - AAA;
12540                 fromY = currentMoveString[1] - ONE;
12541                 toX = currentMoveString[2] - AAA;
12542                 toY = currentMoveString[3] - ONE;
12543                 promoChar = currentMoveString[4];
12544                 break;
12545             case WhiteDrop:
12546             case BlackDrop:
12547                 fromX = next == WhiteDrop ?
12548                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12549                   (int) CharToPiece(ToLower(currentMoveString[0]));
12550                 fromY = DROP_RANK;
12551                 toX = currentMoveString[2] - AAA;
12552                 toY = currentMoveString[3] - ONE;
12553                 promoChar = 0;
12554                 break;
12555         }
12556         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12557         plyNr++;
12558         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12559         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12560         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12561         if(appData.findMirror) {
12562             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12563             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12564         }
12565     }
12566 }
12567
12568 /* Load the nth game from open file f */
12569 int
12570 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12571 {
12572     ChessMove cm;
12573     char buf[MSG_SIZ];
12574     int gn = gameNumber;
12575     ListGame *lg = NULL;
12576     int numPGNTags = 0;
12577     int err, pos = -1;
12578     GameMode oldGameMode;
12579     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12580
12581     if (appData.debugMode)
12582         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12583
12584     if (gameMode == Training )
12585         SetTrainingModeOff();
12586
12587     oldGameMode = gameMode;
12588     if (gameMode != BeginningOfGame) {
12589       Reset(FALSE, TRUE);
12590     }
12591     killX = killY = -1; // [HGM] lion: in case we did not Reset
12592
12593     gameFileFP = f;
12594     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12595         fclose(lastLoadGameFP);
12596     }
12597
12598     if (useList) {
12599         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12600
12601         if (lg) {
12602             fseek(f, lg->offset, 0);
12603             GameListHighlight(gameNumber);
12604             pos = lg->position;
12605             gn = 1;
12606         }
12607         else {
12608             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12609               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12610             else
12611             DisplayError(_("Game number out of range"), 0);
12612             return FALSE;
12613         }
12614     } else {
12615         GameListDestroy();
12616         if (fseek(f, 0, 0) == -1) {
12617             if (f == lastLoadGameFP ?
12618                 gameNumber == lastLoadGameNumber + 1 :
12619                 gameNumber == 1) {
12620                 gn = 1;
12621             } else {
12622                 DisplayError(_("Can't seek on game file"), 0);
12623                 return FALSE;
12624             }
12625         }
12626     }
12627     lastLoadGameFP = f;
12628     lastLoadGameNumber = gameNumber;
12629     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12630     lastLoadGameUseList = useList;
12631
12632     yynewfile(f);
12633
12634     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12635       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12636                 lg->gameInfo.black);
12637             DisplayTitle(buf);
12638     } else if (*title != NULLCHAR) {
12639         if (gameNumber > 1) {
12640           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12641             DisplayTitle(buf);
12642         } else {
12643             DisplayTitle(title);
12644         }
12645     }
12646
12647     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12648         gameMode = PlayFromGameFile;
12649         ModeHighlight();
12650     }
12651
12652     currentMove = forwardMostMove = backwardMostMove = 0;
12653     CopyBoard(boards[0], initialPosition);
12654     StopClocks();
12655
12656     /*
12657      * Skip the first gn-1 games in the file.
12658      * Also skip over anything that precedes an identifiable
12659      * start of game marker, to avoid being confused by
12660      * garbage at the start of the file.  Currently
12661      * recognized start of game markers are the move number "1",
12662      * the pattern "gnuchess .* game", the pattern
12663      * "^[#;%] [^ ]* game file", and a PGN tag block.
12664      * A game that starts with one of the latter two patterns
12665      * will also have a move number 1, possibly
12666      * following a position diagram.
12667      * 5-4-02: Let's try being more lenient and allowing a game to
12668      * start with an unnumbered move.  Does that break anything?
12669      */
12670     cm = lastLoadGameStart = EndOfFile;
12671     while (gn > 0) {
12672         yyboardindex = forwardMostMove;
12673         cm = (ChessMove) Myylex();
12674         switch (cm) {
12675           case EndOfFile:
12676             if (cmailMsgLoaded) {
12677                 nCmailGames = CMAIL_MAX_GAMES - gn;
12678             } else {
12679                 Reset(TRUE, TRUE);
12680                 DisplayError(_("Game not found in file"), 0);
12681             }
12682             return FALSE;
12683
12684           case GNUChessGame:
12685           case XBoardGame:
12686             gn--;
12687             lastLoadGameStart = cm;
12688             break;
12689
12690           case MoveNumberOne:
12691             switch (lastLoadGameStart) {
12692               case GNUChessGame:
12693               case XBoardGame:
12694               case PGNTag:
12695                 break;
12696               case MoveNumberOne:
12697               case EndOfFile:
12698                 gn--;           /* count this game */
12699                 lastLoadGameStart = cm;
12700                 break;
12701               default:
12702                 /* impossible */
12703                 break;
12704             }
12705             break;
12706
12707           case PGNTag:
12708             switch (lastLoadGameStart) {
12709               case GNUChessGame:
12710               case PGNTag:
12711               case MoveNumberOne:
12712               case EndOfFile:
12713                 gn--;           /* count this game */
12714                 lastLoadGameStart = cm;
12715                 break;
12716               case XBoardGame:
12717                 lastLoadGameStart = cm; /* game counted already */
12718                 break;
12719               default:
12720                 /* impossible */
12721                 break;
12722             }
12723             if (gn > 0) {
12724                 do {
12725                     yyboardindex = forwardMostMove;
12726                     cm = (ChessMove) Myylex();
12727                 } while (cm == PGNTag || cm == Comment);
12728             }
12729             break;
12730
12731           case WhiteWins:
12732           case BlackWins:
12733           case GameIsDrawn:
12734             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12735                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12736                     != CMAIL_OLD_RESULT) {
12737                     nCmailResults ++ ;
12738                     cmailResult[  CMAIL_MAX_GAMES
12739                                 - gn - 1] = CMAIL_OLD_RESULT;
12740                 }
12741             }
12742             break;
12743
12744           case NormalMove:
12745           case FirstLeg:
12746             /* Only a NormalMove can be at the start of a game
12747              * without a position diagram. */
12748             if (lastLoadGameStart == EndOfFile ) {
12749               gn--;
12750               lastLoadGameStart = MoveNumberOne;
12751             }
12752             break;
12753
12754           default:
12755             break;
12756         }
12757     }
12758
12759     if (appData.debugMode)
12760       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12761
12762     if (cm == XBoardGame) {
12763         /* Skip any header junk before position diagram and/or move 1 */
12764         for (;;) {
12765             yyboardindex = forwardMostMove;
12766             cm = (ChessMove) Myylex();
12767
12768             if (cm == EndOfFile ||
12769                 cm == GNUChessGame || cm == XBoardGame) {
12770                 /* Empty game; pretend end-of-file and handle later */
12771                 cm = EndOfFile;
12772                 break;
12773             }
12774
12775             if (cm == MoveNumberOne || cm == PositionDiagram ||
12776                 cm == PGNTag || cm == Comment)
12777               break;
12778         }
12779     } else if (cm == GNUChessGame) {
12780         if (gameInfo.event != NULL) {
12781             free(gameInfo.event);
12782         }
12783         gameInfo.event = StrSave(yy_text);
12784     }
12785
12786     startedFromSetupPosition = FALSE;
12787     while (cm == PGNTag) {
12788         if (appData.debugMode)
12789           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12790         err = ParsePGNTag(yy_text, &gameInfo);
12791         if (!err) numPGNTags++;
12792
12793         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12794         if(gameInfo.variant != oldVariant) {
12795             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12796             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12797             InitPosition(TRUE);
12798             oldVariant = gameInfo.variant;
12799             if (appData.debugMode)
12800               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12801         }
12802
12803
12804         if (gameInfo.fen != NULL) {
12805           Board initial_position;
12806           startedFromSetupPosition = TRUE;
12807           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12808             Reset(TRUE, TRUE);
12809             DisplayError(_("Bad FEN position in file"), 0);
12810             return FALSE;
12811           }
12812           CopyBoard(boards[0], initial_position);
12813           if (blackPlaysFirst) {
12814             currentMove = forwardMostMove = backwardMostMove = 1;
12815             CopyBoard(boards[1], initial_position);
12816             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12817             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12818             timeRemaining[0][1] = whiteTimeRemaining;
12819             timeRemaining[1][1] = blackTimeRemaining;
12820             if (commentList[0] != NULL) {
12821               commentList[1] = commentList[0];
12822               commentList[0] = NULL;
12823             }
12824           } else {
12825             currentMove = forwardMostMove = backwardMostMove = 0;
12826           }
12827           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12828           {   int i;
12829               initialRulePlies = FENrulePlies;
12830               for( i=0; i< nrCastlingRights; i++ )
12831                   initialRights[i] = initial_position[CASTLING][i];
12832           }
12833           yyboardindex = forwardMostMove;
12834           free(gameInfo.fen);
12835           gameInfo.fen = NULL;
12836         }
12837
12838         yyboardindex = forwardMostMove;
12839         cm = (ChessMove) Myylex();
12840
12841         /* Handle comments interspersed among the tags */
12842         while (cm == Comment) {
12843             char *p;
12844             if (appData.debugMode)
12845               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12846             p = yy_text;
12847             AppendComment(currentMove, p, FALSE);
12848             yyboardindex = forwardMostMove;
12849             cm = (ChessMove) Myylex();
12850         }
12851     }
12852
12853     /* don't rely on existence of Event tag since if game was
12854      * pasted from clipboard the Event tag may not exist
12855      */
12856     if (numPGNTags > 0){
12857         char *tags;
12858         if (gameInfo.variant == VariantNormal) {
12859           VariantClass v = StringToVariant(gameInfo.event);
12860           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12861           if(v < VariantShogi) gameInfo.variant = v;
12862         }
12863         if (!matchMode) {
12864           if( appData.autoDisplayTags ) {
12865             tags = PGNTags(&gameInfo);
12866             TagsPopUp(tags, CmailMsg());
12867             free(tags);
12868           }
12869         }
12870     } else {
12871         /* Make something up, but don't display it now */
12872         SetGameInfo();
12873         TagsPopDown();
12874     }
12875
12876     if (cm == PositionDiagram) {
12877         int i, j;
12878         char *p;
12879         Board initial_position;
12880
12881         if (appData.debugMode)
12882           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12883
12884         if (!startedFromSetupPosition) {
12885             p = yy_text;
12886             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12887               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12888                 switch (*p) {
12889                   case '{':
12890                   case '[':
12891                   case '-':
12892                   case ' ':
12893                   case '\t':
12894                   case '\n':
12895                   case '\r':
12896                     break;
12897                   default:
12898                     initial_position[i][j++] = CharToPiece(*p);
12899                     break;
12900                 }
12901             while (*p == ' ' || *p == '\t' ||
12902                    *p == '\n' || *p == '\r') p++;
12903
12904             if (strncmp(p, "black", strlen("black"))==0)
12905               blackPlaysFirst = TRUE;
12906             else
12907               blackPlaysFirst = FALSE;
12908             startedFromSetupPosition = TRUE;
12909
12910             CopyBoard(boards[0], initial_position);
12911             if (blackPlaysFirst) {
12912                 currentMove = forwardMostMove = backwardMostMove = 1;
12913                 CopyBoard(boards[1], initial_position);
12914                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12915                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12916                 timeRemaining[0][1] = whiteTimeRemaining;
12917                 timeRemaining[1][1] = blackTimeRemaining;
12918                 if (commentList[0] != NULL) {
12919                     commentList[1] = commentList[0];
12920                     commentList[0] = NULL;
12921                 }
12922             } else {
12923                 currentMove = forwardMostMove = backwardMostMove = 0;
12924             }
12925         }
12926         yyboardindex = forwardMostMove;
12927         cm = (ChessMove) Myylex();
12928     }
12929
12930   if(!creatingBook) {
12931     if (first.pr == NoProc) {
12932         StartChessProgram(&first);
12933     }
12934     InitChessProgram(&first, FALSE);
12935     SendToProgram("force\n", &first);
12936     if (startedFromSetupPosition) {
12937         SendBoard(&first, forwardMostMove);
12938     if (appData.debugMode) {
12939         fprintf(debugFP, "Load Game\n");
12940     }
12941         DisplayBothClocks();
12942     }
12943   }
12944
12945     /* [HGM] server: flag to write setup moves in broadcast file as one */
12946     loadFlag = appData.suppressLoadMoves;
12947
12948     while (cm == Comment) {
12949         char *p;
12950         if (appData.debugMode)
12951           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12952         p = yy_text;
12953         AppendComment(currentMove, p, FALSE);
12954         yyboardindex = forwardMostMove;
12955         cm = (ChessMove) Myylex();
12956     }
12957
12958     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12959         cm == WhiteWins || cm == BlackWins ||
12960         cm == GameIsDrawn || cm == GameUnfinished) {
12961         DisplayMessage("", _("No moves in game"));
12962         if (cmailMsgLoaded) {
12963             if (appData.debugMode)
12964               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12965             ClearHighlights();
12966             flipView = FALSE;
12967         }
12968         DrawPosition(FALSE, boards[currentMove]);
12969         DisplayBothClocks();
12970         gameMode = EditGame;
12971         ModeHighlight();
12972         gameFileFP = NULL;
12973         cmailOldMove = 0;
12974         return TRUE;
12975     }
12976
12977     // [HGM] PV info: routine tests if comment empty
12978     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12979         DisplayComment(currentMove - 1, commentList[currentMove]);
12980     }
12981     if (!matchMode && appData.timeDelay != 0)
12982       DrawPosition(FALSE, boards[currentMove]);
12983
12984     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12985       programStats.ok_to_send = 1;
12986     }
12987
12988     /* if the first token after the PGN tags is a move
12989      * and not move number 1, retrieve it from the parser
12990      */
12991     if (cm != MoveNumberOne)
12992         LoadGameOneMove(cm);
12993
12994     /* load the remaining moves from the file */
12995     while (LoadGameOneMove(EndOfFile)) {
12996       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12997       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12998     }
12999
13000     /* rewind to the start of the game */
13001     currentMove = backwardMostMove;
13002
13003     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13004
13005     if (oldGameMode == AnalyzeFile) {
13006       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13007       AnalyzeFileEvent();
13008     } else
13009     if (oldGameMode == AnalyzeMode) {
13010       AnalyzeFileEvent();
13011     }
13012
13013     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13014         long int w, b; // [HGM] adjourn: restore saved clock times
13015         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13016         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13017             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13018             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13019         }
13020     }
13021
13022     if(creatingBook) return TRUE;
13023     if (!matchMode && pos > 0) {
13024         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13025     } else
13026     if (matchMode || appData.timeDelay == 0) {
13027       ToEndEvent();
13028     } else if (appData.timeDelay > 0) {
13029       AutoPlayGameLoop();
13030     }
13031
13032     if (appData.debugMode)
13033         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13034
13035     loadFlag = 0; /* [HGM] true game starts */
13036     return TRUE;
13037 }
13038
13039 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13040 int
13041 ReloadPosition (int offset)
13042 {
13043     int positionNumber = lastLoadPositionNumber + offset;
13044     if (lastLoadPositionFP == NULL) {
13045         DisplayError(_("No position has been loaded yet"), 0);
13046         return FALSE;
13047     }
13048     if (positionNumber <= 0) {
13049         DisplayError(_("Can't back up any further"), 0);
13050         return FALSE;
13051     }
13052     return LoadPosition(lastLoadPositionFP, positionNumber,
13053                         lastLoadPositionTitle);
13054 }
13055
13056 /* Load the nth position from the given file */
13057 int
13058 LoadPositionFromFile (char *filename, int n, char *title)
13059 {
13060     FILE *f;
13061     char buf[MSG_SIZ];
13062
13063     if (strcmp(filename, "-") == 0) {
13064         return LoadPosition(stdin, n, "stdin");
13065     } else {
13066         f = fopen(filename, "rb");
13067         if (f == NULL) {
13068             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13069             DisplayError(buf, errno);
13070             return FALSE;
13071         } else {
13072             return LoadPosition(f, n, title);
13073         }
13074     }
13075 }
13076
13077 /* Load the nth position from the given open file, and close it */
13078 int
13079 LoadPosition (FILE *f, int positionNumber, char *title)
13080 {
13081     char *p, line[MSG_SIZ];
13082     Board initial_position;
13083     int i, j, fenMode, pn;
13084
13085     if (gameMode == Training )
13086         SetTrainingModeOff();
13087
13088     if (gameMode != BeginningOfGame) {
13089         Reset(FALSE, TRUE);
13090     }
13091     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13092         fclose(lastLoadPositionFP);
13093     }
13094     if (positionNumber == 0) positionNumber = 1;
13095     lastLoadPositionFP = f;
13096     lastLoadPositionNumber = positionNumber;
13097     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13098     if (first.pr == NoProc && !appData.noChessProgram) {
13099       StartChessProgram(&first);
13100       InitChessProgram(&first, FALSE);
13101     }
13102     pn = positionNumber;
13103     if (positionNumber < 0) {
13104         /* Negative position number means to seek to that byte offset */
13105         if (fseek(f, -positionNumber, 0) == -1) {
13106             DisplayError(_("Can't seek on position file"), 0);
13107             return FALSE;
13108         };
13109         pn = 1;
13110     } else {
13111         if (fseek(f, 0, 0) == -1) {
13112             if (f == lastLoadPositionFP ?
13113                 positionNumber == lastLoadPositionNumber + 1 :
13114                 positionNumber == 1) {
13115                 pn = 1;
13116             } else {
13117                 DisplayError(_("Can't seek on position file"), 0);
13118                 return FALSE;
13119             }
13120         }
13121     }
13122     /* See if this file is FEN or old-style xboard */
13123     if (fgets(line, MSG_SIZ, f) == NULL) {
13124         DisplayError(_("Position not found in file"), 0);
13125         return FALSE;
13126     }
13127     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13128     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13129
13130     if (pn >= 2) {
13131         if (fenMode || line[0] == '#') pn--;
13132         while (pn > 0) {
13133             /* skip positions before number pn */
13134             if (fgets(line, MSG_SIZ, f) == NULL) {
13135                 Reset(TRUE, TRUE);
13136                 DisplayError(_("Position not found in file"), 0);
13137                 return FALSE;
13138             }
13139             if (fenMode || line[0] == '#') pn--;
13140         }
13141     }
13142
13143     if (fenMode) {
13144         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13145             DisplayError(_("Bad FEN position in file"), 0);
13146             return FALSE;
13147         }
13148     } else {
13149         (void) fgets(line, MSG_SIZ, f);
13150         (void) fgets(line, MSG_SIZ, f);
13151
13152         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13153             (void) fgets(line, MSG_SIZ, f);
13154             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13155                 if (*p == ' ')
13156                   continue;
13157                 initial_position[i][j++] = CharToPiece(*p);
13158             }
13159         }
13160
13161         blackPlaysFirst = FALSE;
13162         if (!feof(f)) {
13163             (void) fgets(line, MSG_SIZ, f);
13164             if (strncmp(line, "black", strlen("black"))==0)
13165               blackPlaysFirst = TRUE;
13166         }
13167     }
13168     startedFromSetupPosition = TRUE;
13169
13170     CopyBoard(boards[0], initial_position);
13171     if (blackPlaysFirst) {
13172         currentMove = forwardMostMove = backwardMostMove = 1;
13173         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13174         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13175         CopyBoard(boards[1], initial_position);
13176         DisplayMessage("", _("Black to play"));
13177     } else {
13178         currentMove = forwardMostMove = backwardMostMove = 0;
13179         DisplayMessage("", _("White to play"));
13180     }
13181     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13182     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13183         SendToProgram("force\n", &first);
13184         SendBoard(&first, forwardMostMove);
13185     }
13186     if (appData.debugMode) {
13187 int i, j;
13188   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13189   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13190         fprintf(debugFP, "Load Position\n");
13191     }
13192
13193     if (positionNumber > 1) {
13194       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13195         DisplayTitle(line);
13196     } else {
13197         DisplayTitle(title);
13198     }
13199     gameMode = EditGame;
13200     ModeHighlight();
13201     ResetClocks();
13202     timeRemaining[0][1] = whiteTimeRemaining;
13203     timeRemaining[1][1] = blackTimeRemaining;
13204     DrawPosition(FALSE, boards[currentMove]);
13205
13206     return TRUE;
13207 }
13208
13209
13210 void
13211 CopyPlayerNameIntoFileName (char **dest, char *src)
13212 {
13213     while (*src != NULLCHAR && *src != ',') {
13214         if (*src == ' ') {
13215             *(*dest)++ = '_';
13216             src++;
13217         } else {
13218             *(*dest)++ = *src++;
13219         }
13220     }
13221 }
13222
13223 char *
13224 DefaultFileName (char *ext)
13225 {
13226     static char def[MSG_SIZ];
13227     char *p;
13228
13229     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13230         p = def;
13231         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13232         *p++ = '-';
13233         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13234         *p++ = '.';
13235         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13236     } else {
13237         def[0] = NULLCHAR;
13238     }
13239     return def;
13240 }
13241
13242 /* Save the current game to the given file */
13243 int
13244 SaveGameToFile (char *filename, int append)
13245 {
13246     FILE *f;
13247     char buf[MSG_SIZ];
13248     int result, i, t,tot=0;
13249
13250     if (strcmp(filename, "-") == 0) {
13251         return SaveGame(stdout, 0, NULL);
13252     } else {
13253         for(i=0; i<10; i++) { // upto 10 tries
13254              f = fopen(filename, append ? "a" : "w");
13255              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13256              if(f || errno != 13) break;
13257              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13258              tot += t;
13259         }
13260         if (f == NULL) {
13261             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13262             DisplayError(buf, errno);
13263             return FALSE;
13264         } else {
13265             safeStrCpy(buf, lastMsg, MSG_SIZ);
13266             DisplayMessage(_("Waiting for access to save file"), "");
13267             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13268             DisplayMessage(_("Saving game"), "");
13269             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13270             result = SaveGame(f, 0, NULL);
13271             DisplayMessage(buf, "");
13272             return result;
13273         }
13274     }
13275 }
13276
13277 char *
13278 SavePart (char *str)
13279 {
13280     static char buf[MSG_SIZ];
13281     char *p;
13282
13283     p = strchr(str, ' ');
13284     if (p == NULL) return str;
13285     strncpy(buf, str, p - str);
13286     buf[p - str] = NULLCHAR;
13287     return buf;
13288 }
13289
13290 #define PGN_MAX_LINE 75
13291
13292 #define PGN_SIDE_WHITE  0
13293 #define PGN_SIDE_BLACK  1
13294
13295 static int
13296 FindFirstMoveOutOfBook (int side)
13297 {
13298     int result = -1;
13299
13300     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13301         int index = backwardMostMove;
13302         int has_book_hit = 0;
13303
13304         if( (index % 2) != side ) {
13305             index++;
13306         }
13307
13308         while( index < forwardMostMove ) {
13309             /* Check to see if engine is in book */
13310             int depth = pvInfoList[index].depth;
13311             int score = pvInfoList[index].score;
13312             int in_book = 0;
13313
13314             if( depth <= 2 ) {
13315                 in_book = 1;
13316             }
13317             else if( score == 0 && depth == 63 ) {
13318                 in_book = 1; /* Zappa */
13319             }
13320             else if( score == 2 && depth == 99 ) {
13321                 in_book = 1; /* Abrok */
13322             }
13323
13324             has_book_hit += in_book;
13325
13326             if( ! in_book ) {
13327                 result = index;
13328
13329                 break;
13330             }
13331
13332             index += 2;
13333         }
13334     }
13335
13336     return result;
13337 }
13338
13339 void
13340 GetOutOfBookInfo (char * buf)
13341 {
13342     int oob[2];
13343     int i;
13344     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13345
13346     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13347     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13348
13349     *buf = '\0';
13350
13351     if( oob[0] >= 0 || oob[1] >= 0 ) {
13352         for( i=0; i<2; i++ ) {
13353             int idx = oob[i];
13354
13355             if( idx >= 0 ) {
13356                 if( i > 0 && oob[0] >= 0 ) {
13357                     strcat( buf, "   " );
13358                 }
13359
13360                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13361                 sprintf( buf+strlen(buf), "%s%.2f",
13362                     pvInfoList[idx].score >= 0 ? "+" : "",
13363                     pvInfoList[idx].score / 100.0 );
13364             }
13365         }
13366     }
13367 }
13368
13369 /* Save game in PGN style and close the file */
13370 int
13371 SaveGamePGN (FILE *f)
13372 {
13373     int i, offset, linelen, newblock;
13374 //    char *movetext;
13375     char numtext[32];
13376     int movelen, numlen, blank;
13377     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13378
13379     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13380
13381     PrintPGNTags(f, &gameInfo);
13382
13383     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13384
13385     if (backwardMostMove > 0 || startedFromSetupPosition) {
13386         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13387         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13388         fprintf(f, "\n{--------------\n");
13389         PrintPosition(f, backwardMostMove);
13390         fprintf(f, "--------------}\n");
13391         free(fen);
13392     }
13393     else {
13394         /* [AS] Out of book annotation */
13395         if( appData.saveOutOfBookInfo ) {
13396             char buf[64];
13397
13398             GetOutOfBookInfo( buf );
13399
13400             if( buf[0] != '\0' ) {
13401                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13402             }
13403         }
13404
13405         fprintf(f, "\n");
13406     }
13407
13408     i = backwardMostMove;
13409     linelen = 0;
13410     newblock = TRUE;
13411
13412     while (i < forwardMostMove) {
13413         /* Print comments preceding this move */
13414         if (commentList[i] != NULL) {
13415             if (linelen > 0) fprintf(f, "\n");
13416             fprintf(f, "%s", commentList[i]);
13417             linelen = 0;
13418             newblock = TRUE;
13419         }
13420
13421         /* Format move number */
13422         if ((i % 2) == 0)
13423           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13424         else
13425           if (newblock)
13426             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13427           else
13428             numtext[0] = NULLCHAR;
13429
13430         numlen = strlen(numtext);
13431         newblock = FALSE;
13432
13433         /* Print move number */
13434         blank = linelen > 0 && numlen > 0;
13435         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13436             fprintf(f, "\n");
13437             linelen = 0;
13438             blank = 0;
13439         }
13440         if (blank) {
13441             fprintf(f, " ");
13442             linelen++;
13443         }
13444         fprintf(f, "%s", numtext);
13445         linelen += numlen;
13446
13447         /* Get move */
13448         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13449         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13450
13451         /* Print move */
13452         blank = linelen > 0 && movelen > 0;
13453         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13454             fprintf(f, "\n");
13455             linelen = 0;
13456             blank = 0;
13457         }
13458         if (blank) {
13459             fprintf(f, " ");
13460             linelen++;
13461         }
13462         fprintf(f, "%s", move_buffer);
13463         linelen += movelen;
13464
13465         /* [AS] Add PV info if present */
13466         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13467             /* [HGM] add time */
13468             char buf[MSG_SIZ]; int seconds;
13469
13470             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13471
13472             if( seconds <= 0)
13473               buf[0] = 0;
13474             else
13475               if( seconds < 30 )
13476                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13477               else
13478                 {
13479                   seconds = (seconds + 4)/10; // round to full seconds
13480                   if( seconds < 60 )
13481                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13482                   else
13483                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13484                 }
13485
13486             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13487                       pvInfoList[i].score >= 0 ? "+" : "",
13488                       pvInfoList[i].score / 100.0,
13489                       pvInfoList[i].depth,
13490                       buf );
13491
13492             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13493
13494             /* Print score/depth */
13495             blank = linelen > 0 && movelen > 0;
13496             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13497                 fprintf(f, "\n");
13498                 linelen = 0;
13499                 blank = 0;
13500             }
13501             if (blank) {
13502                 fprintf(f, " ");
13503                 linelen++;
13504             }
13505             fprintf(f, "%s", move_buffer);
13506             linelen += movelen;
13507         }
13508
13509         i++;
13510     }
13511
13512     /* Start a new line */
13513     if (linelen > 0) fprintf(f, "\n");
13514
13515     /* Print comments after last move */
13516     if (commentList[i] != NULL) {
13517         fprintf(f, "%s\n", commentList[i]);
13518     }
13519
13520     /* Print result */
13521     if (gameInfo.resultDetails != NULL &&
13522         gameInfo.resultDetails[0] != NULLCHAR) {
13523         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13524         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13525            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13526             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13527         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13528     } else {
13529         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13530     }
13531
13532     fclose(f);
13533     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13534     return TRUE;
13535 }
13536
13537 /* Save game in old style and close the file */
13538 int
13539 SaveGameOldStyle (FILE *f)
13540 {
13541     int i, offset;
13542     time_t tm;
13543
13544     tm = time((time_t *) NULL);
13545
13546     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13547     PrintOpponents(f);
13548
13549     if (backwardMostMove > 0 || startedFromSetupPosition) {
13550         fprintf(f, "\n[--------------\n");
13551         PrintPosition(f, backwardMostMove);
13552         fprintf(f, "--------------]\n");
13553     } else {
13554         fprintf(f, "\n");
13555     }
13556
13557     i = backwardMostMove;
13558     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13559
13560     while (i < forwardMostMove) {
13561         if (commentList[i] != NULL) {
13562             fprintf(f, "[%s]\n", commentList[i]);
13563         }
13564
13565         if ((i % 2) == 1) {
13566             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13567             i++;
13568         } else {
13569             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13570             i++;
13571             if (commentList[i] != NULL) {
13572                 fprintf(f, "\n");
13573                 continue;
13574             }
13575             if (i >= forwardMostMove) {
13576                 fprintf(f, "\n");
13577                 break;
13578             }
13579             fprintf(f, "%s\n", parseList[i]);
13580             i++;
13581         }
13582     }
13583
13584     if (commentList[i] != NULL) {
13585         fprintf(f, "[%s]\n", commentList[i]);
13586     }
13587
13588     /* This isn't really the old style, but it's close enough */
13589     if (gameInfo.resultDetails != NULL &&
13590         gameInfo.resultDetails[0] != NULLCHAR) {
13591         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13592                 gameInfo.resultDetails);
13593     } else {
13594         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13595     }
13596
13597     fclose(f);
13598     return TRUE;
13599 }
13600
13601 /* Save the current game to open file f and close the file */
13602 int
13603 SaveGame (FILE *f, int dummy, char *dummy2)
13604 {
13605     if (gameMode == EditPosition) EditPositionDone(TRUE);
13606     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13607     if (appData.oldSaveStyle)
13608       return SaveGameOldStyle(f);
13609     else
13610       return SaveGamePGN(f);
13611 }
13612
13613 /* Save the current position to the given file */
13614 int
13615 SavePositionToFile (char *filename)
13616 {
13617     FILE *f;
13618     char buf[MSG_SIZ];
13619
13620     if (strcmp(filename, "-") == 0) {
13621         return SavePosition(stdout, 0, NULL);
13622     } else {
13623         f = fopen(filename, "a");
13624         if (f == NULL) {
13625             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13626             DisplayError(buf, errno);
13627             return FALSE;
13628         } else {
13629             safeStrCpy(buf, lastMsg, MSG_SIZ);
13630             DisplayMessage(_("Waiting for access to save file"), "");
13631             flock(fileno(f), LOCK_EX); // [HGM] lock
13632             DisplayMessage(_("Saving position"), "");
13633             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13634             SavePosition(f, 0, NULL);
13635             DisplayMessage(buf, "");
13636             return TRUE;
13637         }
13638     }
13639 }
13640
13641 /* Save the current position to the given open file and close the file */
13642 int
13643 SavePosition (FILE *f, int dummy, char *dummy2)
13644 {
13645     time_t tm;
13646     char *fen;
13647
13648     if (gameMode == EditPosition) EditPositionDone(TRUE);
13649     if (appData.oldSaveStyle) {
13650         tm = time((time_t *) NULL);
13651
13652         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13653         PrintOpponents(f);
13654         fprintf(f, "[--------------\n");
13655         PrintPosition(f, currentMove);
13656         fprintf(f, "--------------]\n");
13657     } else {
13658         fen = PositionToFEN(currentMove, NULL, 1);
13659         fprintf(f, "%s\n", fen);
13660         free(fen);
13661     }
13662     fclose(f);
13663     return TRUE;
13664 }
13665
13666 void
13667 ReloadCmailMsgEvent (int unregister)
13668 {
13669 #if !WIN32
13670     static char *inFilename = NULL;
13671     static char *outFilename;
13672     int i;
13673     struct stat inbuf, outbuf;
13674     int status;
13675
13676     /* Any registered moves are unregistered if unregister is set, */
13677     /* i.e. invoked by the signal handler */
13678     if (unregister) {
13679         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13680             cmailMoveRegistered[i] = FALSE;
13681             if (cmailCommentList[i] != NULL) {
13682                 free(cmailCommentList[i]);
13683                 cmailCommentList[i] = NULL;
13684             }
13685         }
13686         nCmailMovesRegistered = 0;
13687     }
13688
13689     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13690         cmailResult[i] = CMAIL_NOT_RESULT;
13691     }
13692     nCmailResults = 0;
13693
13694     if (inFilename == NULL) {
13695         /* Because the filenames are static they only get malloced once  */
13696         /* and they never get freed                                      */
13697         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13698         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13699
13700         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13701         sprintf(outFilename, "%s.out", appData.cmailGameName);
13702     }
13703
13704     status = stat(outFilename, &outbuf);
13705     if (status < 0) {
13706         cmailMailedMove = FALSE;
13707     } else {
13708         status = stat(inFilename, &inbuf);
13709         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13710     }
13711
13712     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13713        counts the games, notes how each one terminated, etc.
13714
13715        It would be nice to remove this kludge and instead gather all
13716        the information while building the game list.  (And to keep it
13717        in the game list nodes instead of having a bunch of fixed-size
13718        parallel arrays.)  Note this will require getting each game's
13719        termination from the PGN tags, as the game list builder does
13720        not process the game moves.  --mann
13721        */
13722     cmailMsgLoaded = TRUE;
13723     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13724
13725     /* Load first game in the file or popup game menu */
13726     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13727
13728 #endif /* !WIN32 */
13729     return;
13730 }
13731
13732 int
13733 RegisterMove ()
13734 {
13735     FILE *f;
13736     char string[MSG_SIZ];
13737
13738     if (   cmailMailedMove
13739         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13740         return TRUE;            /* Allow free viewing  */
13741     }
13742
13743     /* Unregister move to ensure that we don't leave RegisterMove        */
13744     /* with the move registered when the conditions for registering no   */
13745     /* longer hold                                                       */
13746     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13747         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13748         nCmailMovesRegistered --;
13749
13750         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13751           {
13752               free(cmailCommentList[lastLoadGameNumber - 1]);
13753               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13754           }
13755     }
13756
13757     if (cmailOldMove == -1) {
13758         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13759         return FALSE;
13760     }
13761
13762     if (currentMove > cmailOldMove + 1) {
13763         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13764         return FALSE;
13765     }
13766
13767     if (currentMove < cmailOldMove) {
13768         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13769         return FALSE;
13770     }
13771
13772     if (forwardMostMove > currentMove) {
13773         /* Silently truncate extra moves */
13774         TruncateGame();
13775     }
13776
13777     if (   (currentMove == cmailOldMove + 1)
13778         || (   (currentMove == cmailOldMove)
13779             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13780                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13781         if (gameInfo.result != GameUnfinished) {
13782             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13783         }
13784
13785         if (commentList[currentMove] != NULL) {
13786             cmailCommentList[lastLoadGameNumber - 1]
13787               = StrSave(commentList[currentMove]);
13788         }
13789         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13790
13791         if (appData.debugMode)
13792           fprintf(debugFP, "Saving %s for game %d\n",
13793                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13794
13795         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13796
13797         f = fopen(string, "w");
13798         if (appData.oldSaveStyle) {
13799             SaveGameOldStyle(f); /* also closes the file */
13800
13801             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13802             f = fopen(string, "w");
13803             SavePosition(f, 0, NULL); /* also closes the file */
13804         } else {
13805             fprintf(f, "{--------------\n");
13806             PrintPosition(f, currentMove);
13807             fprintf(f, "--------------}\n\n");
13808
13809             SaveGame(f, 0, NULL); /* also closes the file*/
13810         }
13811
13812         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13813         nCmailMovesRegistered ++;
13814     } else if (nCmailGames == 1) {
13815         DisplayError(_("You have not made a move yet"), 0);
13816         return FALSE;
13817     }
13818
13819     return TRUE;
13820 }
13821
13822 void
13823 MailMoveEvent ()
13824 {
13825 #if !WIN32
13826     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13827     FILE *commandOutput;
13828     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13829     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13830     int nBuffers;
13831     int i;
13832     int archived;
13833     char *arcDir;
13834
13835     if (! cmailMsgLoaded) {
13836         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13837         return;
13838     }
13839
13840     if (nCmailGames == nCmailResults) {
13841         DisplayError(_("No unfinished games"), 0);
13842         return;
13843     }
13844
13845 #if CMAIL_PROHIBIT_REMAIL
13846     if (cmailMailedMove) {
13847       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);
13848         DisplayError(msg, 0);
13849         return;
13850     }
13851 #endif
13852
13853     if (! (cmailMailedMove || RegisterMove())) return;
13854
13855     if (   cmailMailedMove
13856         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13857       snprintf(string, MSG_SIZ, partCommandString,
13858                appData.debugMode ? " -v" : "", appData.cmailGameName);
13859         commandOutput = popen(string, "r");
13860
13861         if (commandOutput == NULL) {
13862             DisplayError(_("Failed to invoke cmail"), 0);
13863         } else {
13864             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13865                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13866             }
13867             if (nBuffers > 1) {
13868                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13869                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13870                 nBytes = MSG_SIZ - 1;
13871             } else {
13872                 (void) memcpy(msg, buffer, nBytes);
13873             }
13874             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13875
13876             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13877                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13878
13879                 archived = TRUE;
13880                 for (i = 0; i < nCmailGames; i ++) {
13881                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13882                         archived = FALSE;
13883                     }
13884                 }
13885                 if (   archived
13886                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13887                         != NULL)) {
13888                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13889                            arcDir,
13890                            appData.cmailGameName,
13891                            gameInfo.date);
13892                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13893                     cmailMsgLoaded = FALSE;
13894                 }
13895             }
13896
13897             DisplayInformation(msg);
13898             pclose(commandOutput);
13899         }
13900     } else {
13901         if ((*cmailMsg) != '\0') {
13902             DisplayInformation(cmailMsg);
13903         }
13904     }
13905
13906     return;
13907 #endif /* !WIN32 */
13908 }
13909
13910 char *
13911 CmailMsg ()
13912 {
13913 #if WIN32
13914     return NULL;
13915 #else
13916     int  prependComma = 0;
13917     char number[5];
13918     char string[MSG_SIZ];       /* Space for game-list */
13919     int  i;
13920
13921     if (!cmailMsgLoaded) return "";
13922
13923     if (cmailMailedMove) {
13924       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13925     } else {
13926         /* Create a list of games left */
13927       snprintf(string, MSG_SIZ, "[");
13928         for (i = 0; i < nCmailGames; i ++) {
13929             if (! (   cmailMoveRegistered[i]
13930                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13931                 if (prependComma) {
13932                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13933                 } else {
13934                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13935                     prependComma = 1;
13936                 }
13937
13938                 strcat(string, number);
13939             }
13940         }
13941         strcat(string, "]");
13942
13943         if (nCmailMovesRegistered + nCmailResults == 0) {
13944             switch (nCmailGames) {
13945               case 1:
13946                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13947                 break;
13948
13949               case 2:
13950                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13951                 break;
13952
13953               default:
13954                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13955                          nCmailGames);
13956                 break;
13957             }
13958         } else {
13959             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13960               case 1:
13961                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13962                          string);
13963                 break;
13964
13965               case 0:
13966                 if (nCmailResults == nCmailGames) {
13967                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13968                 } else {
13969                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13970                 }
13971                 break;
13972
13973               default:
13974                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13975                          string);
13976             }
13977         }
13978     }
13979     return cmailMsg;
13980 #endif /* WIN32 */
13981 }
13982
13983 void
13984 ResetGameEvent ()
13985 {
13986     if (gameMode == Training)
13987       SetTrainingModeOff();
13988
13989     Reset(TRUE, TRUE);
13990     cmailMsgLoaded = FALSE;
13991     if (appData.icsActive) {
13992       SendToICS(ics_prefix);
13993       SendToICS("refresh\n");
13994     }
13995 }
13996
13997 void
13998 ExitEvent (int status)
13999 {
14000     exiting++;
14001     if (exiting > 2) {
14002       /* Give up on clean exit */
14003       exit(status);
14004     }
14005     if (exiting > 1) {
14006       /* Keep trying for clean exit */
14007       return;
14008     }
14009
14010     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14011     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14012
14013     if (telnetISR != NULL) {
14014       RemoveInputSource(telnetISR);
14015     }
14016     if (icsPR != NoProc) {
14017       DestroyChildProcess(icsPR, TRUE);
14018     }
14019
14020     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14021     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14022
14023     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14024     /* make sure this other one finishes before killing it!                  */
14025     if(endingGame) { int count = 0;
14026         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14027         while(endingGame && count++ < 10) DoSleep(1);
14028         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14029     }
14030
14031     /* Kill off chess programs */
14032     if (first.pr != NoProc) {
14033         ExitAnalyzeMode();
14034
14035         DoSleep( appData.delayBeforeQuit );
14036         SendToProgram("quit\n", &first);
14037         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14038     }
14039     if (second.pr != NoProc) {
14040         DoSleep( appData.delayBeforeQuit );
14041         SendToProgram("quit\n", &second);
14042         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14043     }
14044     if (first.isr != NULL) {
14045         RemoveInputSource(first.isr);
14046     }
14047     if (second.isr != NULL) {
14048         RemoveInputSource(second.isr);
14049     }
14050
14051     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14052     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14053
14054     ShutDownFrontEnd();
14055     exit(status);
14056 }
14057
14058 void
14059 PauseEngine (ChessProgramState *cps)
14060 {
14061     SendToProgram("pause\n", cps);
14062     cps->pause = 2;
14063 }
14064
14065 void
14066 UnPauseEngine (ChessProgramState *cps)
14067 {
14068     SendToProgram("resume\n", cps);
14069     cps->pause = 1;
14070 }
14071
14072 void
14073 PauseEvent ()
14074 {
14075     if (appData.debugMode)
14076         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14077     if (pausing) {
14078         pausing = FALSE;
14079         ModeHighlight();
14080         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14081             StartClocks();
14082             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14083                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14084                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14085             }
14086             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14087             HandleMachineMove(stashedInputMove, stalledEngine);
14088             stalledEngine = NULL;
14089             return;
14090         }
14091         if (gameMode == MachinePlaysWhite ||
14092             gameMode == TwoMachinesPlay   ||
14093             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14094             if(first.pause)  UnPauseEngine(&first);
14095             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14096             if(second.pause) UnPauseEngine(&second);
14097             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14098             StartClocks();
14099         } else {
14100             DisplayBothClocks();
14101         }
14102         if (gameMode == PlayFromGameFile) {
14103             if (appData.timeDelay >= 0)
14104                 AutoPlayGameLoop();
14105         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14106             Reset(FALSE, TRUE);
14107             SendToICS(ics_prefix);
14108             SendToICS("refresh\n");
14109         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14110             ForwardInner(forwardMostMove);
14111         }
14112         pauseExamInvalid = FALSE;
14113     } else {
14114         switch (gameMode) {
14115           default:
14116             return;
14117           case IcsExamining:
14118             pauseExamForwardMostMove = forwardMostMove;
14119             pauseExamInvalid = FALSE;
14120             /* fall through */
14121           case IcsObserving:
14122           case IcsPlayingWhite:
14123           case IcsPlayingBlack:
14124             pausing = TRUE;
14125             ModeHighlight();
14126             return;
14127           case PlayFromGameFile:
14128             (void) StopLoadGameTimer();
14129             pausing = TRUE;
14130             ModeHighlight();
14131             break;
14132           case BeginningOfGame:
14133             if (appData.icsActive) return;
14134             /* else fall through */
14135           case MachinePlaysWhite:
14136           case MachinePlaysBlack:
14137           case TwoMachinesPlay:
14138             if (forwardMostMove == 0)
14139               return;           /* don't pause if no one has moved */
14140             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14141                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14142                 if(onMove->pause) {           // thinking engine can be paused
14143                     PauseEngine(onMove);      // do it
14144                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14145                         PauseEngine(onMove->other);
14146                     else
14147                         SendToProgram("easy\n", onMove->other);
14148                     StopClocks();
14149                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14150             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14151                 if(first.pause) {
14152                     PauseEngine(&first);
14153                     StopClocks();
14154                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14155             } else { // human on move, pause pondering by either method
14156                 if(first.pause)
14157                     PauseEngine(&first);
14158                 else if(appData.ponderNextMove)
14159                     SendToProgram("easy\n", &first);
14160                 StopClocks();
14161             }
14162             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14163           case AnalyzeMode:
14164             pausing = TRUE;
14165             ModeHighlight();
14166             break;
14167         }
14168     }
14169 }
14170
14171 void
14172 EditCommentEvent ()
14173 {
14174     char title[MSG_SIZ];
14175
14176     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14177       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14178     } else {
14179       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14180                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14181                parseList[currentMove - 1]);
14182     }
14183
14184     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14185 }
14186
14187
14188 void
14189 EditTagsEvent ()
14190 {
14191     char *tags = PGNTags(&gameInfo);
14192     bookUp = FALSE;
14193     EditTagsPopUp(tags, NULL);
14194     free(tags);
14195 }
14196
14197 void
14198 ToggleSecond ()
14199 {
14200   if(second.analyzing) {
14201     SendToProgram("exit\n", &second);
14202     second.analyzing = FALSE;
14203   } else {
14204     if (second.pr == NoProc) StartChessProgram(&second);
14205     InitChessProgram(&second, FALSE);
14206     FeedMovesToProgram(&second, currentMove);
14207
14208     SendToProgram("analyze\n", &second);
14209     second.analyzing = TRUE;
14210   }
14211 }
14212
14213 /* Toggle ShowThinking */
14214 void
14215 ToggleShowThinking()
14216 {
14217   appData.showThinking = !appData.showThinking;
14218   ShowThinkingEvent();
14219 }
14220
14221 int
14222 AnalyzeModeEvent ()
14223 {
14224     char buf[MSG_SIZ];
14225
14226     if (!first.analysisSupport) {
14227       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14228       DisplayError(buf, 0);
14229       return 0;
14230     }
14231     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14232     if (appData.icsActive) {
14233         if (gameMode != IcsObserving) {
14234           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14235             DisplayError(buf, 0);
14236             /* secure check */
14237             if (appData.icsEngineAnalyze) {
14238                 if (appData.debugMode)
14239                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14240                 ExitAnalyzeMode();
14241                 ModeHighlight();
14242             }
14243             return 0;
14244         }
14245         /* if enable, user wants to disable icsEngineAnalyze */
14246         if (appData.icsEngineAnalyze) {
14247                 ExitAnalyzeMode();
14248                 ModeHighlight();
14249                 return 0;
14250         }
14251         appData.icsEngineAnalyze = TRUE;
14252         if (appData.debugMode)
14253             fprintf(debugFP, "ICS engine analyze starting... \n");
14254     }
14255
14256     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14257     if (appData.noChessProgram || gameMode == AnalyzeMode)
14258       return 0;
14259
14260     if (gameMode != AnalyzeFile) {
14261         if (!appData.icsEngineAnalyze) {
14262                EditGameEvent();
14263                if (gameMode != EditGame) return 0;
14264         }
14265         if (!appData.showThinking) ToggleShowThinking();
14266         ResurrectChessProgram();
14267         SendToProgram("analyze\n", &first);
14268         first.analyzing = TRUE;
14269         /*first.maybeThinking = TRUE;*/
14270         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14271         EngineOutputPopUp();
14272     }
14273     if (!appData.icsEngineAnalyze) {
14274         gameMode = AnalyzeMode;
14275         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14276     }
14277     pausing = FALSE;
14278     ModeHighlight();
14279     SetGameInfo();
14280
14281     StartAnalysisClock();
14282     GetTimeMark(&lastNodeCountTime);
14283     lastNodeCount = 0;
14284     return 1;
14285 }
14286
14287 void
14288 AnalyzeFileEvent ()
14289 {
14290     if (appData.noChessProgram || gameMode == AnalyzeFile)
14291       return;
14292
14293     if (!first.analysisSupport) {
14294       char buf[MSG_SIZ];
14295       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14296       DisplayError(buf, 0);
14297       return;
14298     }
14299
14300     if (gameMode != AnalyzeMode) {
14301         keepInfo = 1; // mere annotating should not alter PGN tags
14302         EditGameEvent();
14303         keepInfo = 0;
14304         if (gameMode != EditGame) return;
14305         if (!appData.showThinking) ToggleShowThinking();
14306         ResurrectChessProgram();
14307         SendToProgram("analyze\n", &first);
14308         first.analyzing = TRUE;
14309         /*first.maybeThinking = TRUE;*/
14310         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14311         EngineOutputPopUp();
14312     }
14313     gameMode = AnalyzeFile;
14314     pausing = FALSE;
14315     ModeHighlight();
14316
14317     StartAnalysisClock();
14318     GetTimeMark(&lastNodeCountTime);
14319     lastNodeCount = 0;
14320     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14321     AnalysisPeriodicEvent(1);
14322 }
14323
14324 void
14325 MachineWhiteEvent ()
14326 {
14327     char buf[MSG_SIZ];
14328     char *bookHit = NULL;
14329
14330     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14331       return;
14332
14333
14334     if (gameMode == PlayFromGameFile ||
14335         gameMode == TwoMachinesPlay  ||
14336         gameMode == Training         ||
14337         gameMode == AnalyzeMode      ||
14338         gameMode == EndOfGame)
14339         EditGameEvent();
14340
14341     if (gameMode == EditPosition)
14342         EditPositionDone(TRUE);
14343
14344     if (!WhiteOnMove(currentMove)) {
14345         DisplayError(_("It is not White's turn"), 0);
14346         return;
14347     }
14348
14349     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14350       ExitAnalyzeMode();
14351
14352     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14353         gameMode == AnalyzeFile)
14354         TruncateGame();
14355
14356     ResurrectChessProgram();    /* in case it isn't running */
14357     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14358         gameMode = MachinePlaysWhite;
14359         ResetClocks();
14360     } else
14361     gameMode = MachinePlaysWhite;
14362     pausing = FALSE;
14363     ModeHighlight();
14364     SetGameInfo();
14365     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14366     DisplayTitle(buf);
14367     if (first.sendName) {
14368       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14369       SendToProgram(buf, &first);
14370     }
14371     if (first.sendTime) {
14372       if (first.useColors) {
14373         SendToProgram("black\n", &first); /*gnu kludge*/
14374       }
14375       SendTimeRemaining(&first, TRUE);
14376     }
14377     if (first.useColors) {
14378       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14379     }
14380     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14381     SetMachineThinkingEnables();
14382     first.maybeThinking = TRUE;
14383     StartClocks();
14384     firstMove = FALSE;
14385
14386     if (appData.autoFlipView && !flipView) {
14387       flipView = !flipView;
14388       DrawPosition(FALSE, NULL);
14389       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14390     }
14391
14392     if(bookHit) { // [HGM] book: simulate book reply
14393         static char bookMove[MSG_SIZ]; // a bit generous?
14394
14395         programStats.nodes = programStats.depth = programStats.time =
14396         programStats.score = programStats.got_only_move = 0;
14397         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14398
14399         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14400         strcat(bookMove, bookHit);
14401         HandleMachineMove(bookMove, &first);
14402     }
14403 }
14404
14405 void
14406 MachineBlackEvent ()
14407 {
14408   char buf[MSG_SIZ];
14409   char *bookHit = NULL;
14410
14411     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14412         return;
14413
14414
14415     if (gameMode == PlayFromGameFile ||
14416         gameMode == TwoMachinesPlay  ||
14417         gameMode == Training         ||
14418         gameMode == AnalyzeMode      ||
14419         gameMode == EndOfGame)
14420         EditGameEvent();
14421
14422     if (gameMode == EditPosition)
14423         EditPositionDone(TRUE);
14424
14425     if (WhiteOnMove(currentMove)) {
14426         DisplayError(_("It is not Black's turn"), 0);
14427         return;
14428     }
14429
14430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14431       ExitAnalyzeMode();
14432
14433     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14434         gameMode == AnalyzeFile)
14435         TruncateGame();
14436
14437     ResurrectChessProgram();    /* in case it isn't running */
14438     gameMode = MachinePlaysBlack;
14439     pausing = FALSE;
14440     ModeHighlight();
14441     SetGameInfo();
14442     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14443     DisplayTitle(buf);
14444     if (first.sendName) {
14445       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14446       SendToProgram(buf, &first);
14447     }
14448     if (first.sendTime) {
14449       if (first.useColors) {
14450         SendToProgram("white\n", &first); /*gnu kludge*/
14451       }
14452       SendTimeRemaining(&first, FALSE);
14453     }
14454     if (first.useColors) {
14455       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14456     }
14457     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14458     SetMachineThinkingEnables();
14459     first.maybeThinking = TRUE;
14460     StartClocks();
14461
14462     if (appData.autoFlipView && flipView) {
14463       flipView = !flipView;
14464       DrawPosition(FALSE, NULL);
14465       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14466     }
14467     if(bookHit) { // [HGM] book: simulate book reply
14468         static char bookMove[MSG_SIZ]; // a bit generous?
14469
14470         programStats.nodes = programStats.depth = programStats.time =
14471         programStats.score = programStats.got_only_move = 0;
14472         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14473
14474         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14475         strcat(bookMove, bookHit);
14476         HandleMachineMove(bookMove, &first);
14477     }
14478 }
14479
14480
14481 void
14482 DisplayTwoMachinesTitle ()
14483 {
14484     char buf[MSG_SIZ];
14485     if (appData.matchGames > 0) {
14486         if(appData.tourneyFile[0]) {
14487           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14488                    gameInfo.white, _("vs."), gameInfo.black,
14489                    nextGame+1, appData.matchGames+1,
14490                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14491         } else
14492         if (first.twoMachinesColor[0] == 'w') {
14493           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14494                    gameInfo.white, _("vs."),  gameInfo.black,
14495                    first.matchWins, second.matchWins,
14496                    matchGame - 1 - (first.matchWins + second.matchWins));
14497         } else {
14498           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14499                    gameInfo.white, _("vs."), gameInfo.black,
14500                    second.matchWins, first.matchWins,
14501                    matchGame - 1 - (first.matchWins + second.matchWins));
14502         }
14503     } else {
14504       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14505     }
14506     DisplayTitle(buf);
14507 }
14508
14509 void
14510 SettingsMenuIfReady ()
14511 {
14512   if (second.lastPing != second.lastPong) {
14513     DisplayMessage("", _("Waiting for second chess program"));
14514     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14515     return;
14516   }
14517   ThawUI();
14518   DisplayMessage("", "");
14519   SettingsPopUp(&second);
14520 }
14521
14522 int
14523 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14524 {
14525     char buf[MSG_SIZ];
14526     if (cps->pr == NoProc) {
14527         StartChessProgram(cps);
14528         if (cps->protocolVersion == 1) {
14529           retry();
14530           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14531         } else {
14532           /* kludge: allow timeout for initial "feature" command */
14533           if(retry != TwoMachinesEventIfReady) FreezeUI();
14534           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14535           DisplayMessage("", buf);
14536           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14537         }
14538         return 1;
14539     }
14540     return 0;
14541 }
14542
14543 void
14544 TwoMachinesEvent P((void))
14545 {
14546     int i;
14547     char buf[MSG_SIZ];
14548     ChessProgramState *onmove;
14549     char *bookHit = NULL;
14550     static int stalling = 0;
14551     TimeMark now;
14552     long wait;
14553
14554     if (appData.noChessProgram) return;
14555
14556     switch (gameMode) {
14557       case TwoMachinesPlay:
14558         return;
14559       case MachinePlaysWhite:
14560       case MachinePlaysBlack:
14561         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14562             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14563             return;
14564         }
14565         /* fall through */
14566       case BeginningOfGame:
14567       case PlayFromGameFile:
14568       case EndOfGame:
14569         EditGameEvent();
14570         if (gameMode != EditGame) return;
14571         break;
14572       case EditPosition:
14573         EditPositionDone(TRUE);
14574         break;
14575       case AnalyzeMode:
14576       case AnalyzeFile:
14577         ExitAnalyzeMode();
14578         break;
14579       case EditGame:
14580       default:
14581         break;
14582     }
14583
14584 //    forwardMostMove = currentMove;
14585     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14586     startingEngine = TRUE;
14587
14588     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14589
14590     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14591     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14592       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14593       return;
14594     }
14595     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14596
14597     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14598                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14599         startingEngine = FALSE;
14600         DisplayError("second engine does not play this", 0);
14601         return;
14602     }
14603
14604     if(!stalling) {
14605       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14606       SendToProgram("force\n", &second);
14607       stalling = 1;
14608       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14609       return;
14610     }
14611     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14612     if(appData.matchPause>10000 || appData.matchPause<10)
14613                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14614     wait = SubtractTimeMarks(&now, &pauseStart);
14615     if(wait < appData.matchPause) {
14616         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14617         return;
14618     }
14619     // we are now committed to starting the game
14620     stalling = 0;
14621     DisplayMessage("", "");
14622     if (startedFromSetupPosition) {
14623         SendBoard(&second, backwardMostMove);
14624     if (appData.debugMode) {
14625         fprintf(debugFP, "Two Machines\n");
14626     }
14627     }
14628     for (i = backwardMostMove; i < forwardMostMove; i++) {
14629         SendMoveToProgram(i, &second);
14630     }
14631
14632     gameMode = TwoMachinesPlay;
14633     pausing = startingEngine = FALSE;
14634     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14635     SetGameInfo();
14636     DisplayTwoMachinesTitle();
14637     firstMove = TRUE;
14638     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14639         onmove = &first;
14640     } else {
14641         onmove = &second;
14642     }
14643     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14644     SendToProgram(first.computerString, &first);
14645     if (first.sendName) {
14646       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14647       SendToProgram(buf, &first);
14648     }
14649     SendToProgram(second.computerString, &second);
14650     if (second.sendName) {
14651       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14652       SendToProgram(buf, &second);
14653     }
14654
14655     ResetClocks();
14656     if (!first.sendTime || !second.sendTime) {
14657         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14658         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14659     }
14660     if (onmove->sendTime) {
14661       if (onmove->useColors) {
14662         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14663       }
14664       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14665     }
14666     if (onmove->useColors) {
14667       SendToProgram(onmove->twoMachinesColor, onmove);
14668     }
14669     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14670 //    SendToProgram("go\n", onmove);
14671     onmove->maybeThinking = TRUE;
14672     SetMachineThinkingEnables();
14673
14674     StartClocks();
14675
14676     if(bookHit) { // [HGM] book: simulate book reply
14677         static char bookMove[MSG_SIZ]; // a bit generous?
14678
14679         programStats.nodes = programStats.depth = programStats.time =
14680         programStats.score = programStats.got_only_move = 0;
14681         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14682
14683         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14684         strcat(bookMove, bookHit);
14685         savedMessage = bookMove; // args for deferred call
14686         savedState = onmove;
14687         ScheduleDelayedEvent(DeferredBookMove, 1);
14688     }
14689 }
14690
14691 void
14692 TrainingEvent ()
14693 {
14694     if (gameMode == Training) {
14695       SetTrainingModeOff();
14696       gameMode = PlayFromGameFile;
14697       DisplayMessage("", _("Training mode off"));
14698     } else {
14699       gameMode = Training;
14700       animateTraining = appData.animate;
14701
14702       /* make sure we are not already at the end of the game */
14703       if (currentMove < forwardMostMove) {
14704         SetTrainingModeOn();
14705         DisplayMessage("", _("Training mode on"));
14706       } else {
14707         gameMode = PlayFromGameFile;
14708         DisplayError(_("Already at end of game"), 0);
14709       }
14710     }
14711     ModeHighlight();
14712 }
14713
14714 void
14715 IcsClientEvent ()
14716 {
14717     if (!appData.icsActive) return;
14718     switch (gameMode) {
14719       case IcsPlayingWhite:
14720       case IcsPlayingBlack:
14721       case IcsObserving:
14722       case IcsIdle:
14723       case BeginningOfGame:
14724       case IcsExamining:
14725         return;
14726
14727       case EditGame:
14728         break;
14729
14730       case EditPosition:
14731         EditPositionDone(TRUE);
14732         break;
14733
14734       case AnalyzeMode:
14735       case AnalyzeFile:
14736         ExitAnalyzeMode();
14737         break;
14738
14739       default:
14740         EditGameEvent();
14741         break;
14742     }
14743
14744     gameMode = IcsIdle;
14745     ModeHighlight();
14746     return;
14747 }
14748
14749 void
14750 EditGameEvent ()
14751 {
14752     int i;
14753
14754     switch (gameMode) {
14755       case Training:
14756         SetTrainingModeOff();
14757         break;
14758       case MachinePlaysWhite:
14759       case MachinePlaysBlack:
14760       case BeginningOfGame:
14761         SendToProgram("force\n", &first);
14762         SetUserThinkingEnables();
14763         break;
14764       case PlayFromGameFile:
14765         (void) StopLoadGameTimer();
14766         if (gameFileFP != NULL) {
14767             gameFileFP = NULL;
14768         }
14769         break;
14770       case EditPosition:
14771         EditPositionDone(TRUE);
14772         break;
14773       case AnalyzeMode:
14774       case AnalyzeFile:
14775         ExitAnalyzeMode();
14776         SendToProgram("force\n", &first);
14777         break;
14778       case TwoMachinesPlay:
14779         GameEnds(EndOfFile, NULL, GE_PLAYER);
14780         ResurrectChessProgram();
14781         SetUserThinkingEnables();
14782         break;
14783       case EndOfGame:
14784         ResurrectChessProgram();
14785         break;
14786       case IcsPlayingBlack:
14787       case IcsPlayingWhite:
14788         DisplayError(_("Warning: You are still playing a game"), 0);
14789         break;
14790       case IcsObserving:
14791         DisplayError(_("Warning: You are still observing a game"), 0);
14792         break;
14793       case IcsExamining:
14794         DisplayError(_("Warning: You are still examining a game"), 0);
14795         break;
14796       case IcsIdle:
14797         break;
14798       case EditGame:
14799       default:
14800         return;
14801     }
14802
14803     pausing = FALSE;
14804     StopClocks();
14805     first.offeredDraw = second.offeredDraw = 0;
14806
14807     if (gameMode == PlayFromGameFile) {
14808         whiteTimeRemaining = timeRemaining[0][currentMove];
14809         blackTimeRemaining = timeRemaining[1][currentMove];
14810         DisplayTitle("");
14811     }
14812
14813     if (gameMode == MachinePlaysWhite ||
14814         gameMode == MachinePlaysBlack ||
14815         gameMode == TwoMachinesPlay ||
14816         gameMode == EndOfGame) {
14817         i = forwardMostMove;
14818         while (i > currentMove) {
14819             SendToProgram("undo\n", &first);
14820             i--;
14821         }
14822         if(!adjustedClock) {
14823         whiteTimeRemaining = timeRemaining[0][currentMove];
14824         blackTimeRemaining = timeRemaining[1][currentMove];
14825         DisplayBothClocks();
14826         }
14827         if (whiteFlag || blackFlag) {
14828             whiteFlag = blackFlag = 0;
14829         }
14830         DisplayTitle("");
14831     }
14832
14833     gameMode = EditGame;
14834     ModeHighlight();
14835     SetGameInfo();
14836 }
14837
14838
14839 void
14840 EditPositionEvent ()
14841 {
14842     if (gameMode == EditPosition) {
14843         EditGameEvent();
14844         return;
14845     }
14846
14847     EditGameEvent();
14848     if (gameMode != EditGame) return;
14849
14850     gameMode = EditPosition;
14851     ModeHighlight();
14852     SetGameInfo();
14853     if (currentMove > 0)
14854       CopyBoard(boards[0], boards[currentMove]);
14855
14856     blackPlaysFirst = !WhiteOnMove(currentMove);
14857     ResetClocks();
14858     currentMove = forwardMostMove = backwardMostMove = 0;
14859     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14860     DisplayMove(-1);
14861     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14862 }
14863
14864 void
14865 ExitAnalyzeMode ()
14866 {
14867     /* [DM] icsEngineAnalyze - possible call from other functions */
14868     if (appData.icsEngineAnalyze) {
14869         appData.icsEngineAnalyze = FALSE;
14870
14871         DisplayMessage("",_("Close ICS engine analyze..."));
14872     }
14873     if (first.analysisSupport && first.analyzing) {
14874       SendToBoth("exit\n");
14875       first.analyzing = second.analyzing = FALSE;
14876     }
14877     thinkOutput[0] = NULLCHAR;
14878 }
14879
14880 void
14881 EditPositionDone (Boolean fakeRights)
14882 {
14883     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14884
14885     startedFromSetupPosition = TRUE;
14886     InitChessProgram(&first, FALSE);
14887     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14888       boards[0][EP_STATUS] = EP_NONE;
14889       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14890       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14891         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14892         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14893       } else boards[0][CASTLING][2] = NoRights;
14894       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14895         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14896         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14897       } else boards[0][CASTLING][5] = NoRights;
14898       if(gameInfo.variant == VariantSChess) {
14899         int i;
14900         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14901           boards[0][VIRGIN][i] = 0;
14902           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14903           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14904         }
14905       }
14906     }
14907     SendToProgram("force\n", &first);
14908     if (blackPlaysFirst) {
14909         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14910         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14911         currentMove = forwardMostMove = backwardMostMove = 1;
14912         CopyBoard(boards[1], boards[0]);
14913     } else {
14914         currentMove = forwardMostMove = backwardMostMove = 0;
14915     }
14916     SendBoard(&first, forwardMostMove);
14917     if (appData.debugMode) {
14918         fprintf(debugFP, "EditPosDone\n");
14919     }
14920     DisplayTitle("");
14921     DisplayMessage("", "");
14922     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14923     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14924     gameMode = EditGame;
14925     ModeHighlight();
14926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14927     ClearHighlights(); /* [AS] */
14928 }
14929
14930 /* Pause for `ms' milliseconds */
14931 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14932 void
14933 TimeDelay (long ms)
14934 {
14935     TimeMark m1, m2;
14936
14937     GetTimeMark(&m1);
14938     do {
14939         GetTimeMark(&m2);
14940     } while (SubtractTimeMarks(&m2, &m1) < ms);
14941 }
14942
14943 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14944 void
14945 SendMultiLineToICS (char *buf)
14946 {
14947     char temp[MSG_SIZ+1], *p;
14948     int len;
14949
14950     len = strlen(buf);
14951     if (len > MSG_SIZ)
14952       len = MSG_SIZ;
14953
14954     strncpy(temp, buf, len);
14955     temp[len] = 0;
14956
14957     p = temp;
14958     while (*p) {
14959         if (*p == '\n' || *p == '\r')
14960           *p = ' ';
14961         ++p;
14962     }
14963
14964     strcat(temp, "\n");
14965     SendToICS(temp);
14966     SendToPlayer(temp, strlen(temp));
14967 }
14968
14969 void
14970 SetWhiteToPlayEvent ()
14971 {
14972     if (gameMode == EditPosition) {
14973         blackPlaysFirst = FALSE;
14974         DisplayBothClocks();    /* works because currentMove is 0 */
14975     } else if (gameMode == IcsExamining) {
14976         SendToICS(ics_prefix);
14977         SendToICS("tomove white\n");
14978     }
14979 }
14980
14981 void
14982 SetBlackToPlayEvent ()
14983 {
14984     if (gameMode == EditPosition) {
14985         blackPlaysFirst = TRUE;
14986         currentMove = 1;        /* kludge */
14987         DisplayBothClocks();
14988         currentMove = 0;
14989     } else if (gameMode == IcsExamining) {
14990         SendToICS(ics_prefix);
14991         SendToICS("tomove black\n");
14992     }
14993 }
14994
14995 void
14996 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14997 {
14998     char buf[MSG_SIZ];
14999     ChessSquare piece = boards[0][y][x];
15000     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15001     static int lastVariant;
15002
15003     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15004
15005     switch (selection) {
15006       case ClearBoard:
15007         CopyBoard(currentBoard, boards[0]);
15008         CopyBoard(menuBoard, initialPosition);
15009         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15010             SendToICS(ics_prefix);
15011             SendToICS("bsetup clear\n");
15012         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15013             SendToICS(ics_prefix);
15014             SendToICS("clearboard\n");
15015         } else {
15016             int nonEmpty = 0;
15017             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15018                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15019                 for (y = 0; y < BOARD_HEIGHT; y++) {
15020                     if (gameMode == IcsExamining) {
15021                         if (boards[currentMove][y][x] != EmptySquare) {
15022                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15023                                     AAA + x, ONE + y);
15024                             SendToICS(buf);
15025                         }
15026                     } else {
15027                         if(boards[0][y][x] != p) nonEmpty++;
15028                         boards[0][y][x] = p;
15029                     }
15030                 }
15031             }
15032             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15033                 int r;
15034                 for(r = 0; r < BOARD_HEIGHT; r++) {
15035                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15036                     ChessSquare p = menuBoard[r][x];
15037                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15038                   }
15039                 }
15040                 DisplayMessage("Clicking clock again restores position", "");
15041                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15042                 if(!nonEmpty) { // asked to clear an empty board
15043                     CopyBoard(boards[0], menuBoard);
15044                 } else
15045                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15046                     CopyBoard(boards[0], initialPosition);
15047                 } else
15048                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15049                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15050                     CopyBoard(boards[0], erasedBoard);
15051                 } else
15052                     CopyBoard(erasedBoard, currentBoard);
15053
15054             }
15055         }
15056         if (gameMode == EditPosition) {
15057             DrawPosition(FALSE, boards[0]);
15058         }
15059         break;
15060
15061       case WhitePlay:
15062         SetWhiteToPlayEvent();
15063         break;
15064
15065       case BlackPlay:
15066         SetBlackToPlayEvent();
15067         break;
15068
15069       case EmptySquare:
15070         if (gameMode == IcsExamining) {
15071             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15072             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15073             SendToICS(buf);
15074         } else {
15075             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15076                 if(x == BOARD_LEFT-2) {
15077                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15078                     boards[0][y][1] = 0;
15079                 } else
15080                 if(x == BOARD_RGHT+1) {
15081                     if(y >= gameInfo.holdingsSize) break;
15082                     boards[0][y][BOARD_WIDTH-2] = 0;
15083                 } else break;
15084             }
15085             boards[0][y][x] = EmptySquare;
15086             DrawPosition(FALSE, boards[0]);
15087         }
15088         break;
15089
15090       case PromotePiece:
15091         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15092            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15093             selection = (ChessSquare) (PROMOTED piece);
15094         } else if(piece == EmptySquare) selection = WhiteSilver;
15095         else selection = (ChessSquare)((int)piece - 1);
15096         goto defaultlabel;
15097
15098       case DemotePiece:
15099         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15100            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15101             selection = (ChessSquare) (DEMOTED piece);
15102         } else if(piece == EmptySquare) selection = BlackSilver;
15103         else selection = (ChessSquare)((int)piece + 1);
15104         goto defaultlabel;
15105
15106       case WhiteQueen:
15107       case BlackQueen:
15108         if(gameInfo.variant == VariantShatranj ||
15109            gameInfo.variant == VariantXiangqi  ||
15110            gameInfo.variant == VariantCourier  ||
15111            gameInfo.variant == VariantASEAN    ||
15112            gameInfo.variant == VariantMakruk     )
15113             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15114         goto defaultlabel;
15115
15116       case WhiteKing:
15117       case BlackKing:
15118         if(gameInfo.variant == VariantXiangqi)
15119             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15120         if(gameInfo.variant == VariantKnightmate)
15121             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15122       default:
15123         defaultlabel:
15124         if (gameMode == IcsExamining) {
15125             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15126             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15127                      PieceToChar(selection), AAA + x, ONE + y);
15128             SendToICS(buf);
15129         } else {
15130             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15131                 int n;
15132                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15133                     n = PieceToNumber(selection - BlackPawn);
15134                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15135                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15136                     boards[0][BOARD_HEIGHT-1-n][1]++;
15137                 } else
15138                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15139                     n = PieceToNumber(selection);
15140                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15141                     boards[0][n][BOARD_WIDTH-1] = selection;
15142                     boards[0][n][BOARD_WIDTH-2]++;
15143                 }
15144             } else
15145             boards[0][y][x] = selection;
15146             DrawPosition(TRUE, boards[0]);
15147             ClearHighlights();
15148             fromX = fromY = -1;
15149         }
15150         break;
15151     }
15152 }
15153
15154
15155 void
15156 DropMenuEvent (ChessSquare selection, int x, int y)
15157 {
15158     ChessMove moveType;
15159
15160     switch (gameMode) {
15161       case IcsPlayingWhite:
15162       case MachinePlaysBlack:
15163         if (!WhiteOnMove(currentMove)) {
15164             DisplayMoveError(_("It is Black's turn"));
15165             return;
15166         }
15167         moveType = WhiteDrop;
15168         break;
15169       case IcsPlayingBlack:
15170       case MachinePlaysWhite:
15171         if (WhiteOnMove(currentMove)) {
15172             DisplayMoveError(_("It is White's turn"));
15173             return;
15174         }
15175         moveType = BlackDrop;
15176         break;
15177       case EditGame:
15178         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15179         break;
15180       default:
15181         return;
15182     }
15183
15184     if (moveType == BlackDrop && selection < BlackPawn) {
15185       selection = (ChessSquare) ((int) selection
15186                                  + (int) BlackPawn - (int) WhitePawn);
15187     }
15188     if (boards[currentMove][y][x] != EmptySquare) {
15189         DisplayMoveError(_("That square is occupied"));
15190         return;
15191     }
15192
15193     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15194 }
15195
15196 void
15197 AcceptEvent ()
15198 {
15199     /* Accept a pending offer of any kind from opponent */
15200
15201     if (appData.icsActive) {
15202         SendToICS(ics_prefix);
15203         SendToICS("accept\n");
15204     } else if (cmailMsgLoaded) {
15205         if (currentMove == cmailOldMove &&
15206             commentList[cmailOldMove] != NULL &&
15207             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15208                    "Black offers a draw" : "White offers a draw")) {
15209             TruncateGame();
15210             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15211             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15212         } else {
15213             DisplayError(_("There is no pending offer on this move"), 0);
15214             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15215         }
15216     } else {
15217         /* Not used for offers from chess program */
15218     }
15219 }
15220
15221 void
15222 DeclineEvent ()
15223 {
15224     /* Decline a pending offer of any kind from opponent */
15225
15226     if (appData.icsActive) {
15227         SendToICS(ics_prefix);
15228         SendToICS("decline\n");
15229     } else if (cmailMsgLoaded) {
15230         if (currentMove == cmailOldMove &&
15231             commentList[cmailOldMove] != NULL &&
15232             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15233                    "Black offers a draw" : "White offers a draw")) {
15234 #ifdef NOTDEF
15235             AppendComment(cmailOldMove, "Draw declined", TRUE);
15236             DisplayComment(cmailOldMove - 1, "Draw declined");
15237 #endif /*NOTDEF*/
15238         } else {
15239             DisplayError(_("There is no pending offer on this move"), 0);
15240         }
15241     } else {
15242         /* Not used for offers from chess program */
15243     }
15244 }
15245
15246 void
15247 RematchEvent ()
15248 {
15249     /* Issue ICS rematch command */
15250     if (appData.icsActive) {
15251         SendToICS(ics_prefix);
15252         SendToICS("rematch\n");
15253     }
15254 }
15255
15256 void
15257 CallFlagEvent ()
15258 {
15259     /* Call your opponent's flag (claim a win on time) */
15260     if (appData.icsActive) {
15261         SendToICS(ics_prefix);
15262         SendToICS("flag\n");
15263     } else {
15264         switch (gameMode) {
15265           default:
15266             return;
15267           case MachinePlaysWhite:
15268             if (whiteFlag) {
15269                 if (blackFlag)
15270                   GameEnds(GameIsDrawn, "Both players ran out of time",
15271                            GE_PLAYER);
15272                 else
15273                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15274             } else {
15275                 DisplayError(_("Your opponent is not out of time"), 0);
15276             }
15277             break;
15278           case MachinePlaysBlack:
15279             if (blackFlag) {
15280                 if (whiteFlag)
15281                   GameEnds(GameIsDrawn, "Both players ran out of time",
15282                            GE_PLAYER);
15283                 else
15284                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15285             } else {
15286                 DisplayError(_("Your opponent is not out of time"), 0);
15287             }
15288             break;
15289         }
15290     }
15291 }
15292
15293 void
15294 ClockClick (int which)
15295 {       // [HGM] code moved to back-end from winboard.c
15296         if(which) { // black clock
15297           if (gameMode == EditPosition || gameMode == IcsExamining) {
15298             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15299             SetBlackToPlayEvent();
15300           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15301                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15302           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15303           } else if (shiftKey) {
15304             AdjustClock(which, -1);
15305           } else if (gameMode == IcsPlayingWhite ||
15306                      gameMode == MachinePlaysBlack) {
15307             CallFlagEvent();
15308           }
15309         } else { // white clock
15310           if (gameMode == EditPosition || gameMode == IcsExamining) {
15311             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15312             SetWhiteToPlayEvent();
15313           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15314                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15315           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15316           } else if (shiftKey) {
15317             AdjustClock(which, -1);
15318           } else if (gameMode == IcsPlayingBlack ||
15319                    gameMode == MachinePlaysWhite) {
15320             CallFlagEvent();
15321           }
15322         }
15323 }
15324
15325 void
15326 DrawEvent ()
15327 {
15328     /* Offer draw or accept pending draw offer from opponent */
15329
15330     if (appData.icsActive) {
15331         /* Note: tournament rules require draw offers to be
15332            made after you make your move but before you punch
15333            your clock.  Currently ICS doesn't let you do that;
15334            instead, you immediately punch your clock after making
15335            a move, but you can offer a draw at any time. */
15336
15337         SendToICS(ics_prefix);
15338         SendToICS("draw\n");
15339         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15340     } else if (cmailMsgLoaded) {
15341         if (currentMove == cmailOldMove &&
15342             commentList[cmailOldMove] != NULL &&
15343             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15344                    "Black offers a draw" : "White offers a draw")) {
15345             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15346             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15347         } else if (currentMove == cmailOldMove + 1) {
15348             char *offer = WhiteOnMove(cmailOldMove) ?
15349               "White offers a draw" : "Black offers a draw";
15350             AppendComment(currentMove, offer, TRUE);
15351             DisplayComment(currentMove - 1, offer);
15352             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15353         } else {
15354             DisplayError(_("You must make your move before offering a draw"), 0);
15355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15356         }
15357     } else if (first.offeredDraw) {
15358         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15359     } else {
15360         if (first.sendDrawOffers) {
15361             SendToProgram("draw\n", &first);
15362             userOfferedDraw = TRUE;
15363         }
15364     }
15365 }
15366
15367 void
15368 AdjournEvent ()
15369 {
15370     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15371
15372     if (appData.icsActive) {
15373         SendToICS(ics_prefix);
15374         SendToICS("adjourn\n");
15375     } else {
15376         /* Currently GNU Chess doesn't offer or accept Adjourns */
15377     }
15378 }
15379
15380
15381 void
15382 AbortEvent ()
15383 {
15384     /* Offer Abort or accept pending Abort offer from opponent */
15385
15386     if (appData.icsActive) {
15387         SendToICS(ics_prefix);
15388         SendToICS("abort\n");
15389     } else {
15390         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15391     }
15392 }
15393
15394 void
15395 ResignEvent ()
15396 {
15397     /* Resign.  You can do this even if it's not your turn. */
15398
15399     if (appData.icsActive) {
15400         SendToICS(ics_prefix);
15401         SendToICS("resign\n");
15402     } else {
15403         switch (gameMode) {
15404           case MachinePlaysWhite:
15405             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15406             break;
15407           case MachinePlaysBlack:
15408             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15409             break;
15410           case EditGame:
15411             if (cmailMsgLoaded) {
15412                 TruncateGame();
15413                 if (WhiteOnMove(cmailOldMove)) {
15414                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15415                 } else {
15416                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15417                 }
15418                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15419             }
15420             break;
15421           default:
15422             break;
15423         }
15424     }
15425 }
15426
15427
15428 void
15429 StopObservingEvent ()
15430 {
15431     /* Stop observing current games */
15432     SendToICS(ics_prefix);
15433     SendToICS("unobserve\n");
15434 }
15435
15436 void
15437 StopExaminingEvent ()
15438 {
15439     /* Stop observing current game */
15440     SendToICS(ics_prefix);
15441     SendToICS("unexamine\n");
15442 }
15443
15444 void
15445 ForwardInner (int target)
15446 {
15447     int limit; int oldSeekGraphUp = seekGraphUp;
15448
15449     if (appData.debugMode)
15450         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15451                 target, currentMove, forwardMostMove);
15452
15453     if (gameMode == EditPosition)
15454       return;
15455
15456     seekGraphUp = FALSE;
15457     MarkTargetSquares(1);
15458
15459     if (gameMode == PlayFromGameFile && !pausing)
15460       PauseEvent();
15461
15462     if (gameMode == IcsExamining && pausing)
15463       limit = pauseExamForwardMostMove;
15464     else
15465       limit = forwardMostMove;
15466
15467     if (target > limit) target = limit;
15468
15469     if (target > 0 && moveList[target - 1][0]) {
15470         int fromX, fromY, toX, toY;
15471         toX = moveList[target - 1][2] - AAA;
15472         toY = moveList[target - 1][3] - ONE;
15473         if (moveList[target - 1][1] == '@') {
15474             if (appData.highlightLastMove) {
15475                 SetHighlights(-1, -1, toX, toY);
15476             }
15477         } else {
15478             fromX = moveList[target - 1][0] - AAA;
15479             fromY = moveList[target - 1][1] - ONE;
15480             if (target == currentMove + 1) {
15481                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15482             }
15483             if (appData.highlightLastMove) {
15484                 SetHighlights(fromX, fromY, toX, toY);
15485             }
15486         }
15487     }
15488     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15489         gameMode == Training || gameMode == PlayFromGameFile ||
15490         gameMode == AnalyzeFile) {
15491         while (currentMove < target) {
15492             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15493             SendMoveToProgram(currentMove++, &first);
15494         }
15495     } else {
15496         currentMove = target;
15497     }
15498
15499     if (gameMode == EditGame || gameMode == EndOfGame) {
15500         whiteTimeRemaining = timeRemaining[0][currentMove];
15501         blackTimeRemaining = timeRemaining[1][currentMove];
15502     }
15503     DisplayBothClocks();
15504     DisplayMove(currentMove - 1);
15505     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15506     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15507     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15508         DisplayComment(currentMove - 1, commentList[currentMove]);
15509     }
15510     ClearMap(); // [HGM] exclude: invalidate map
15511 }
15512
15513
15514 void
15515 ForwardEvent ()
15516 {
15517     if (gameMode == IcsExamining && !pausing) {
15518         SendToICS(ics_prefix);
15519         SendToICS("forward\n");
15520     } else {
15521         ForwardInner(currentMove + 1);
15522     }
15523 }
15524
15525 void
15526 ToEndEvent ()
15527 {
15528     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15529         /* to optimze, we temporarily turn off analysis mode while we feed
15530          * the remaining moves to the engine. Otherwise we get analysis output
15531          * after each move.
15532          */
15533         if (first.analysisSupport) {
15534           SendToProgram("exit\nforce\n", &first);
15535           first.analyzing = FALSE;
15536         }
15537     }
15538
15539     if (gameMode == IcsExamining && !pausing) {
15540         SendToICS(ics_prefix);
15541         SendToICS("forward 999999\n");
15542     } else {
15543         ForwardInner(forwardMostMove);
15544     }
15545
15546     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15547         /* we have fed all the moves, so reactivate analysis mode */
15548         SendToProgram("analyze\n", &first);
15549         first.analyzing = TRUE;
15550         /*first.maybeThinking = TRUE;*/
15551         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15552     }
15553 }
15554
15555 void
15556 BackwardInner (int target)
15557 {
15558     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15559
15560     if (appData.debugMode)
15561         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15562                 target, currentMove, forwardMostMove);
15563
15564     if (gameMode == EditPosition) return;
15565     seekGraphUp = FALSE;
15566     MarkTargetSquares(1);
15567     if (currentMove <= backwardMostMove) {
15568         ClearHighlights();
15569         DrawPosition(full_redraw, boards[currentMove]);
15570         return;
15571     }
15572     if (gameMode == PlayFromGameFile && !pausing)
15573       PauseEvent();
15574
15575     if (moveList[target][0]) {
15576         int fromX, fromY, toX, toY;
15577         toX = moveList[target][2] - AAA;
15578         toY = moveList[target][3] - ONE;
15579         if (moveList[target][1] == '@') {
15580             if (appData.highlightLastMove) {
15581                 SetHighlights(-1, -1, toX, toY);
15582             }
15583         } else {
15584             fromX = moveList[target][0] - AAA;
15585             fromY = moveList[target][1] - ONE;
15586             if (target == currentMove - 1) {
15587                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15588             }
15589             if (appData.highlightLastMove) {
15590                 SetHighlights(fromX, fromY, toX, toY);
15591             }
15592         }
15593     }
15594     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15595         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15596         while (currentMove > target) {
15597             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15598                 // null move cannot be undone. Reload program with move history before it.
15599                 int i;
15600                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15601                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15602                 }
15603                 SendBoard(&first, i);
15604               if(second.analyzing) SendBoard(&second, i);
15605                 for(currentMove=i; currentMove<target; currentMove++) {
15606                     SendMoveToProgram(currentMove, &first);
15607                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15608                 }
15609                 break;
15610             }
15611             SendToBoth("undo\n");
15612             currentMove--;
15613         }
15614     } else {
15615         currentMove = target;
15616     }
15617
15618     if (gameMode == EditGame || gameMode == EndOfGame) {
15619         whiteTimeRemaining = timeRemaining[0][currentMove];
15620         blackTimeRemaining = timeRemaining[1][currentMove];
15621     }
15622     DisplayBothClocks();
15623     DisplayMove(currentMove - 1);
15624     DrawPosition(full_redraw, boards[currentMove]);
15625     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15626     // [HGM] PV info: routine tests if comment empty
15627     DisplayComment(currentMove - 1, commentList[currentMove]);
15628     ClearMap(); // [HGM] exclude: invalidate map
15629 }
15630
15631 void
15632 BackwardEvent ()
15633 {
15634     if (gameMode == IcsExamining && !pausing) {
15635         SendToICS(ics_prefix);
15636         SendToICS("backward\n");
15637     } else {
15638         BackwardInner(currentMove - 1);
15639     }
15640 }
15641
15642 void
15643 ToStartEvent ()
15644 {
15645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15646         /* to optimize, we temporarily turn off analysis mode while we undo
15647          * all the moves. Otherwise we get analysis output after each undo.
15648          */
15649         if (first.analysisSupport) {
15650           SendToProgram("exit\nforce\n", &first);
15651           first.analyzing = FALSE;
15652         }
15653     }
15654
15655     if (gameMode == IcsExamining && !pausing) {
15656         SendToICS(ics_prefix);
15657         SendToICS("backward 999999\n");
15658     } else {
15659         BackwardInner(backwardMostMove);
15660     }
15661
15662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15663         /* we have fed all the moves, so reactivate analysis mode */
15664         SendToProgram("analyze\n", &first);
15665         first.analyzing = TRUE;
15666         /*first.maybeThinking = TRUE;*/
15667         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15668     }
15669 }
15670
15671 void
15672 ToNrEvent (int to)
15673 {
15674   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15675   if (to >= forwardMostMove) to = forwardMostMove;
15676   if (to <= backwardMostMove) to = backwardMostMove;
15677   if (to < currentMove) {
15678     BackwardInner(to);
15679   } else {
15680     ForwardInner(to);
15681   }
15682 }
15683
15684 void
15685 RevertEvent (Boolean annotate)
15686 {
15687     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15688         return;
15689     }
15690     if (gameMode != IcsExamining) {
15691         DisplayError(_("You are not examining a game"), 0);
15692         return;
15693     }
15694     if (pausing) {
15695         DisplayError(_("You can't revert while pausing"), 0);
15696         return;
15697     }
15698     SendToICS(ics_prefix);
15699     SendToICS("revert\n");
15700 }
15701
15702 void
15703 RetractMoveEvent ()
15704 {
15705     switch (gameMode) {
15706       case MachinePlaysWhite:
15707       case MachinePlaysBlack:
15708         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15709             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15710             return;
15711         }
15712         if (forwardMostMove < 2) return;
15713         currentMove = forwardMostMove = forwardMostMove - 2;
15714         whiteTimeRemaining = timeRemaining[0][currentMove];
15715         blackTimeRemaining = timeRemaining[1][currentMove];
15716         DisplayBothClocks();
15717         DisplayMove(currentMove - 1);
15718         ClearHighlights();/*!! could figure this out*/
15719         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15720         SendToProgram("remove\n", &first);
15721         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15722         break;
15723
15724       case BeginningOfGame:
15725       default:
15726         break;
15727
15728       case IcsPlayingWhite:
15729       case IcsPlayingBlack:
15730         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15731             SendToICS(ics_prefix);
15732             SendToICS("takeback 2\n");
15733         } else {
15734             SendToICS(ics_prefix);
15735             SendToICS("takeback 1\n");
15736         }
15737         break;
15738     }
15739 }
15740
15741 void
15742 MoveNowEvent ()
15743 {
15744     ChessProgramState *cps;
15745
15746     switch (gameMode) {
15747       case MachinePlaysWhite:
15748         if (!WhiteOnMove(forwardMostMove)) {
15749             DisplayError(_("It is your turn"), 0);
15750             return;
15751         }
15752         cps = &first;
15753         break;
15754       case MachinePlaysBlack:
15755         if (WhiteOnMove(forwardMostMove)) {
15756             DisplayError(_("It is your turn"), 0);
15757             return;
15758         }
15759         cps = &first;
15760         break;
15761       case TwoMachinesPlay:
15762         if (WhiteOnMove(forwardMostMove) ==
15763             (first.twoMachinesColor[0] == 'w')) {
15764             cps = &first;
15765         } else {
15766             cps = &second;
15767         }
15768         break;
15769       case BeginningOfGame:
15770       default:
15771         return;
15772     }
15773     SendToProgram("?\n", cps);
15774 }
15775
15776 void
15777 TruncateGameEvent ()
15778 {
15779     EditGameEvent();
15780     if (gameMode != EditGame) return;
15781     TruncateGame();
15782 }
15783
15784 void
15785 TruncateGame ()
15786 {
15787     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15788     if (forwardMostMove > currentMove) {
15789         if (gameInfo.resultDetails != NULL) {
15790             free(gameInfo.resultDetails);
15791             gameInfo.resultDetails = NULL;
15792             gameInfo.result = GameUnfinished;
15793         }
15794         forwardMostMove = currentMove;
15795         HistorySet(parseList, backwardMostMove, forwardMostMove,
15796                    currentMove-1);
15797     }
15798 }
15799
15800 void
15801 HintEvent ()
15802 {
15803     if (appData.noChessProgram) return;
15804     switch (gameMode) {
15805       case MachinePlaysWhite:
15806         if (WhiteOnMove(forwardMostMove)) {
15807             DisplayError(_("Wait until your turn."), 0);
15808             return;
15809         }
15810         break;
15811       case BeginningOfGame:
15812       case MachinePlaysBlack:
15813         if (!WhiteOnMove(forwardMostMove)) {
15814             DisplayError(_("Wait until your turn."), 0);
15815             return;
15816         }
15817         break;
15818       default:
15819         DisplayError(_("No hint available"), 0);
15820         return;
15821     }
15822     SendToProgram("hint\n", &first);
15823     hintRequested = TRUE;
15824 }
15825
15826 void
15827 CreateBookEvent ()
15828 {
15829     ListGame * lg = (ListGame *) gameList.head;
15830     FILE *f, *g;
15831     int nItem;
15832     static int secondTime = FALSE;
15833
15834     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15835         DisplayError(_("Game list not loaded or empty"), 0);
15836         return;
15837     }
15838
15839     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15840         fclose(g);
15841         secondTime++;
15842         DisplayNote(_("Book file exists! Try again for overwrite."));
15843         return;
15844     }
15845
15846     creatingBook = TRUE;
15847     secondTime = FALSE;
15848
15849     /* Get list size */
15850     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15851         LoadGame(f, nItem, "", TRUE);
15852         AddGameToBook(TRUE);
15853         lg = (ListGame *) lg->node.succ;
15854     }
15855
15856     creatingBook = FALSE;
15857     FlushBook();
15858 }
15859
15860 void
15861 BookEvent ()
15862 {
15863     if (appData.noChessProgram) return;
15864     switch (gameMode) {
15865       case MachinePlaysWhite:
15866         if (WhiteOnMove(forwardMostMove)) {
15867             DisplayError(_("Wait until your turn."), 0);
15868             return;
15869         }
15870         break;
15871       case BeginningOfGame:
15872       case MachinePlaysBlack:
15873         if (!WhiteOnMove(forwardMostMove)) {
15874             DisplayError(_("Wait until your turn."), 0);
15875             return;
15876         }
15877         break;
15878       case EditPosition:
15879         EditPositionDone(TRUE);
15880         break;
15881       case TwoMachinesPlay:
15882         return;
15883       default:
15884         break;
15885     }
15886     SendToProgram("bk\n", &first);
15887     bookOutput[0] = NULLCHAR;
15888     bookRequested = TRUE;
15889 }
15890
15891 void
15892 AboutGameEvent ()
15893 {
15894     char *tags = PGNTags(&gameInfo);
15895     TagsPopUp(tags, CmailMsg());
15896     free(tags);
15897 }
15898
15899 /* end button procedures */
15900
15901 void
15902 PrintPosition (FILE *fp, int move)
15903 {
15904     int i, j;
15905
15906     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15907         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15908             char c = PieceToChar(boards[move][i][j]);
15909             fputc(c == 'x' ? '.' : c, fp);
15910             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15911         }
15912     }
15913     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15914       fprintf(fp, "white to play\n");
15915     else
15916       fprintf(fp, "black to play\n");
15917 }
15918
15919 void
15920 PrintOpponents (FILE *fp)
15921 {
15922     if (gameInfo.white != NULL) {
15923         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15924     } else {
15925         fprintf(fp, "\n");
15926     }
15927 }
15928
15929 /* Find last component of program's own name, using some heuristics */
15930 void
15931 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15932 {
15933     char *p, *q, c;
15934     int local = (strcmp(host, "localhost") == 0);
15935     while (!local && (p = strchr(prog, ';')) != NULL) {
15936         p++;
15937         while (*p == ' ') p++;
15938         prog = p;
15939     }
15940     if (*prog == '"' || *prog == '\'') {
15941         q = strchr(prog + 1, *prog);
15942     } else {
15943         q = strchr(prog, ' ');
15944     }
15945     if (q == NULL) q = prog + strlen(prog);
15946     p = q;
15947     while (p >= prog && *p != '/' && *p != '\\') p--;
15948     p++;
15949     if(p == prog && *p == '"') p++;
15950     c = *q; *q = 0;
15951     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15952     memcpy(buf, p, q - p);
15953     buf[q - p] = NULLCHAR;
15954     if (!local) {
15955         strcat(buf, "@");
15956         strcat(buf, host);
15957     }
15958 }
15959
15960 char *
15961 TimeControlTagValue ()
15962 {
15963     char buf[MSG_SIZ];
15964     if (!appData.clockMode) {
15965       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15966     } else if (movesPerSession > 0) {
15967       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15968     } else if (timeIncrement == 0) {
15969       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15970     } else {
15971       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15972     }
15973     return StrSave(buf);
15974 }
15975
15976 void
15977 SetGameInfo ()
15978 {
15979     /* This routine is used only for certain modes */
15980     VariantClass v = gameInfo.variant;
15981     ChessMove r = GameUnfinished;
15982     char *p = NULL;
15983
15984     if(keepInfo) return;
15985
15986     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15987         r = gameInfo.result;
15988         p = gameInfo.resultDetails;
15989         gameInfo.resultDetails = NULL;
15990     }
15991     ClearGameInfo(&gameInfo);
15992     gameInfo.variant = v;
15993
15994     switch (gameMode) {
15995       case MachinePlaysWhite:
15996         gameInfo.event = StrSave( appData.pgnEventHeader );
15997         gameInfo.site = StrSave(HostName());
15998         gameInfo.date = PGNDate();
15999         gameInfo.round = StrSave("-");
16000         gameInfo.white = StrSave(first.tidy);
16001         gameInfo.black = StrSave(UserName());
16002         gameInfo.timeControl = TimeControlTagValue();
16003         break;
16004
16005       case MachinePlaysBlack:
16006         gameInfo.event = StrSave( appData.pgnEventHeader );
16007         gameInfo.site = StrSave(HostName());
16008         gameInfo.date = PGNDate();
16009         gameInfo.round = StrSave("-");
16010         gameInfo.white = StrSave(UserName());
16011         gameInfo.black = StrSave(first.tidy);
16012         gameInfo.timeControl = TimeControlTagValue();
16013         break;
16014
16015       case TwoMachinesPlay:
16016         gameInfo.event = StrSave( appData.pgnEventHeader );
16017         gameInfo.site = StrSave(HostName());
16018         gameInfo.date = PGNDate();
16019         if (roundNr > 0) {
16020             char buf[MSG_SIZ];
16021             snprintf(buf, MSG_SIZ, "%d", roundNr);
16022             gameInfo.round = StrSave(buf);
16023         } else {
16024             gameInfo.round = StrSave("-");
16025         }
16026         if (first.twoMachinesColor[0] == 'w') {
16027             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16028             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16029         } else {
16030             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16031             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16032         }
16033         gameInfo.timeControl = TimeControlTagValue();
16034         break;
16035
16036       case EditGame:
16037         gameInfo.event = StrSave("Edited game");
16038         gameInfo.site = StrSave(HostName());
16039         gameInfo.date = PGNDate();
16040         gameInfo.round = StrSave("-");
16041         gameInfo.white = StrSave("-");
16042         gameInfo.black = StrSave("-");
16043         gameInfo.result = r;
16044         gameInfo.resultDetails = p;
16045         break;
16046
16047       case EditPosition:
16048         gameInfo.event = StrSave("Edited position");
16049         gameInfo.site = StrSave(HostName());
16050         gameInfo.date = PGNDate();
16051         gameInfo.round = StrSave("-");
16052         gameInfo.white = StrSave("-");
16053         gameInfo.black = StrSave("-");
16054         break;
16055
16056       case IcsPlayingWhite:
16057       case IcsPlayingBlack:
16058       case IcsObserving:
16059       case IcsExamining:
16060         break;
16061
16062       case PlayFromGameFile:
16063         gameInfo.event = StrSave("Game from non-PGN file");
16064         gameInfo.site = StrSave(HostName());
16065         gameInfo.date = PGNDate();
16066         gameInfo.round = StrSave("-");
16067         gameInfo.white = StrSave("?");
16068         gameInfo.black = StrSave("?");
16069         break;
16070
16071       default:
16072         break;
16073     }
16074 }
16075
16076 void
16077 ReplaceComment (int index, char *text)
16078 {
16079     int len;
16080     char *p;
16081     float score;
16082
16083     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16084        pvInfoList[index-1].depth == len &&
16085        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16086        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16087     while (*text == '\n') text++;
16088     len = strlen(text);
16089     while (len > 0 && text[len - 1] == '\n') len--;
16090
16091     if (commentList[index] != NULL)
16092       free(commentList[index]);
16093
16094     if (len == 0) {
16095         commentList[index] = NULL;
16096         return;
16097     }
16098   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16099       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16100       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16101     commentList[index] = (char *) malloc(len + 2);
16102     strncpy(commentList[index], text, len);
16103     commentList[index][len] = '\n';
16104     commentList[index][len + 1] = NULLCHAR;
16105   } else {
16106     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16107     char *p;
16108     commentList[index] = (char *) malloc(len + 7);
16109     safeStrCpy(commentList[index], "{\n", 3);
16110     safeStrCpy(commentList[index]+2, text, len+1);
16111     commentList[index][len+2] = NULLCHAR;
16112     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16113     strcat(commentList[index], "\n}\n");
16114   }
16115 }
16116
16117 void
16118 CrushCRs (char *text)
16119 {
16120   char *p = text;
16121   char *q = text;
16122   char ch;
16123
16124   do {
16125     ch = *p++;
16126     if (ch == '\r') continue;
16127     *q++ = ch;
16128   } while (ch != '\0');
16129 }
16130
16131 void
16132 AppendComment (int index, char *text, Boolean addBraces)
16133 /* addBraces  tells if we should add {} */
16134 {
16135     int oldlen, len;
16136     char *old;
16137
16138 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16139     if(addBraces == 3) addBraces = 0; else // force appending literally
16140     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16141
16142     CrushCRs(text);
16143     while (*text == '\n') text++;
16144     len = strlen(text);
16145     while (len > 0 && text[len - 1] == '\n') len--;
16146     text[len] = NULLCHAR;
16147
16148     if (len == 0) return;
16149
16150     if (commentList[index] != NULL) {
16151       Boolean addClosingBrace = addBraces;
16152         old = commentList[index];
16153         oldlen = strlen(old);
16154         while(commentList[index][oldlen-1] ==  '\n')
16155           commentList[index][--oldlen] = NULLCHAR;
16156         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16157         safeStrCpy(commentList[index], old, oldlen + len + 6);
16158         free(old);
16159         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16160         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16161           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16162           while (*text == '\n') { text++; len--; }
16163           commentList[index][--oldlen] = NULLCHAR;
16164       }
16165         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16166         else          strcat(commentList[index], "\n");
16167         strcat(commentList[index], text);
16168         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16169         else          strcat(commentList[index], "\n");
16170     } else {
16171         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16172         if(addBraces)
16173           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16174         else commentList[index][0] = NULLCHAR;
16175         strcat(commentList[index], text);
16176         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16177         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16178     }
16179 }
16180
16181 static char *
16182 FindStr (char * text, char * sub_text)
16183 {
16184     char * result = strstr( text, sub_text );
16185
16186     if( result != NULL ) {
16187         result += strlen( sub_text );
16188     }
16189
16190     return result;
16191 }
16192
16193 /* [AS] Try to extract PV info from PGN comment */
16194 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16195 char *
16196 GetInfoFromComment (int index, char * text)
16197 {
16198     char * sep = text, *p;
16199
16200     if( text != NULL && index > 0 ) {
16201         int score = 0;
16202         int depth = 0;
16203         int time = -1, sec = 0, deci;
16204         char * s_eval = FindStr( text, "[%eval " );
16205         char * s_emt = FindStr( text, "[%emt " );
16206 #if 0
16207         if( s_eval != NULL || s_emt != NULL ) {
16208 #else
16209         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16210 #endif
16211             /* New style */
16212             char delim;
16213
16214             if( s_eval != NULL ) {
16215                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16216                     return text;
16217                 }
16218
16219                 if( delim != ']' ) {
16220                     return text;
16221                 }
16222             }
16223
16224             if( s_emt != NULL ) {
16225             }
16226                 return text;
16227         }
16228         else {
16229             /* We expect something like: [+|-]nnn.nn/dd */
16230             int score_lo = 0;
16231
16232             if(*text != '{') return text; // [HGM] braces: must be normal comment
16233
16234             sep = strchr( text, '/' );
16235             if( sep == NULL || sep < (text+4) ) {
16236                 return text;
16237             }
16238
16239             p = text;
16240             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16241             if(p[1] == '(') { // comment starts with PV
16242                p = strchr(p, ')'); // locate end of PV
16243                if(p == NULL || sep < p+5) return text;
16244                // at this point we have something like "{(.*) +0.23/6 ..."
16245                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16246                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16247                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16248             }
16249             time = -1; sec = -1; deci = -1;
16250             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16251                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16252                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16253                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16254                 return text;
16255             }
16256
16257             if( score_lo < 0 || score_lo >= 100 ) {
16258                 return text;
16259             }
16260
16261             if(sec >= 0) time = 600*time + 10*sec; else
16262             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16263
16264             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16265
16266             /* [HGM] PV time: now locate end of PV info */
16267             while( *++sep >= '0' && *sep <= '9'); // strip depth
16268             if(time >= 0)
16269             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16270             if(sec >= 0)
16271             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16272             if(deci >= 0)
16273             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16274             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16275         }
16276
16277         if( depth <= 0 ) {
16278             return text;
16279         }
16280
16281         if( time < 0 ) {
16282             time = -1;
16283         }
16284
16285         pvInfoList[index-1].depth = depth;
16286         pvInfoList[index-1].score = score;
16287         pvInfoList[index-1].time  = 10*time; // centi-sec
16288         if(*sep == '}') *sep = 0; else *--sep = '{';
16289         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16290     }
16291     return sep;
16292 }
16293
16294 void
16295 SendToProgram (char *message, ChessProgramState *cps)
16296 {
16297     int count, outCount, error;
16298     char buf[MSG_SIZ];
16299
16300     if (cps->pr == NoProc) return;
16301     Attention(cps);
16302
16303     if (appData.debugMode) {
16304         TimeMark now;
16305         GetTimeMark(&now);
16306         fprintf(debugFP, "%ld >%-6s: %s",
16307                 SubtractTimeMarks(&now, &programStartTime),
16308                 cps->which, message);
16309         if(serverFP)
16310             fprintf(serverFP, "%ld >%-6s: %s",
16311                 SubtractTimeMarks(&now, &programStartTime),
16312                 cps->which, message), fflush(serverFP);
16313     }
16314
16315     count = strlen(message);
16316     outCount = OutputToProcess(cps->pr, message, count, &error);
16317     if (outCount < count && !exiting
16318                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16319       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16320       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16321         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16322             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16323                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16324                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16325                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16326             } else {
16327                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16328                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16329                 gameInfo.result = res;
16330             }
16331             gameInfo.resultDetails = StrSave(buf);
16332         }
16333         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16334         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16335     }
16336 }
16337
16338 void
16339 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16340 {
16341     char *end_str;
16342     char buf[MSG_SIZ];
16343     ChessProgramState *cps = (ChessProgramState *)closure;
16344
16345     if (isr != cps->isr) return; /* Killed intentionally */
16346     if (count <= 0) {
16347         if (count == 0) {
16348             RemoveInputSource(cps->isr);
16349             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16350                     _(cps->which), cps->program);
16351             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16352             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16353                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16354                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16355                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16356                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16357                 } else {
16358                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16359                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16360                     gameInfo.result = res;
16361                 }
16362                 gameInfo.resultDetails = StrSave(buf);
16363             }
16364             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16365             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16366         } else {
16367             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16368                     _(cps->which), cps->program);
16369             RemoveInputSource(cps->isr);
16370
16371             /* [AS] Program is misbehaving badly... kill it */
16372             if( count == -2 ) {
16373                 DestroyChildProcess( cps->pr, 9 );
16374                 cps->pr = NoProc;
16375             }
16376
16377             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16378         }
16379         return;
16380     }
16381
16382     if ((end_str = strchr(message, '\r')) != NULL)
16383       *end_str = NULLCHAR;
16384     if ((end_str = strchr(message, '\n')) != NULL)
16385       *end_str = NULLCHAR;
16386
16387     if (appData.debugMode) {
16388         TimeMark now; int print = 1;
16389         char *quote = ""; char c; int i;
16390
16391         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16392                 char start = message[0];
16393                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16394                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16395                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16396                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16397                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16398                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16399                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16400                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16401                    sscanf(message, "hint: %c", &c)!=1 &&
16402                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16403                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16404                     print = (appData.engineComments >= 2);
16405                 }
16406                 message[0] = start; // restore original message
16407         }
16408         if(print) {
16409                 GetTimeMark(&now);
16410                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16411                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16412                         quote,
16413                         message);
16414                 if(serverFP)
16415                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16416                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16417                         quote,
16418                         message), fflush(serverFP);
16419         }
16420     }
16421
16422     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16423     if (appData.icsEngineAnalyze) {
16424         if (strstr(message, "whisper") != NULL ||
16425              strstr(message, "kibitz") != NULL ||
16426             strstr(message, "tellics") != NULL) return;
16427     }
16428
16429     HandleMachineMove(message, cps);
16430 }
16431
16432
16433 void
16434 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16435 {
16436     char buf[MSG_SIZ];
16437     int seconds;
16438
16439     if( timeControl_2 > 0 ) {
16440         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16441             tc = timeControl_2;
16442         }
16443     }
16444     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16445     inc /= cps->timeOdds;
16446     st  /= cps->timeOdds;
16447
16448     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16449
16450     if (st > 0) {
16451       /* Set exact time per move, normally using st command */
16452       if (cps->stKludge) {
16453         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16454         seconds = st % 60;
16455         if (seconds == 0) {
16456           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16457         } else {
16458           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16459         }
16460       } else {
16461         snprintf(buf, MSG_SIZ, "st %d\n", st);
16462       }
16463     } else {
16464       /* Set conventional or incremental time control, using level command */
16465       if (seconds == 0) {
16466         /* Note old gnuchess bug -- minutes:seconds used to not work.
16467            Fixed in later versions, but still avoid :seconds
16468            when seconds is 0. */
16469         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16470       } else {
16471         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16472                  seconds, inc/1000.);
16473       }
16474     }
16475     SendToProgram(buf, cps);
16476
16477     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16478     /* Orthogonally, limit search to given depth */
16479     if (sd > 0) {
16480       if (cps->sdKludge) {
16481         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16482       } else {
16483         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16484       }
16485       SendToProgram(buf, cps);
16486     }
16487
16488     if(cps->nps >= 0) { /* [HGM] nps */
16489         if(cps->supportsNPS == FALSE)
16490           cps->nps = -1; // don't use if engine explicitly says not supported!
16491         else {
16492           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16493           SendToProgram(buf, cps);
16494         }
16495     }
16496 }
16497
16498 ChessProgramState *
16499 WhitePlayer ()
16500 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16501 {
16502     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16503        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16504         return &second;
16505     return &first;
16506 }
16507
16508 void
16509 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16510 {
16511     char message[MSG_SIZ];
16512     long time, otime;
16513
16514     /* Note: this routine must be called when the clocks are stopped
16515        or when they have *just* been set or switched; otherwise
16516        it will be off by the time since the current tick started.
16517     */
16518     if (machineWhite) {
16519         time = whiteTimeRemaining / 10;
16520         otime = blackTimeRemaining / 10;
16521     } else {
16522         time = blackTimeRemaining / 10;
16523         otime = whiteTimeRemaining / 10;
16524     }
16525     /* [HGM] translate opponent's time by time-odds factor */
16526     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16527
16528     if (time <= 0) time = 1;
16529     if (otime <= 0) otime = 1;
16530
16531     snprintf(message, MSG_SIZ, "time %ld\n", time);
16532     SendToProgram(message, cps);
16533
16534     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16535     SendToProgram(message, cps);
16536 }
16537
16538 char *
16539 EngineDefinedVariant (ChessProgramState *cps, int n)
16540 {   // return name of n-th unknown variant that engine supports
16541     static char buf[MSG_SIZ];
16542     char *p, *s = cps->variants;
16543     if(!s) return NULL;
16544     do { // parse string from variants feature
16545       VariantClass v;
16546         p = strchr(s, ',');
16547         if(p) *p = NULLCHAR;
16548       v = StringToVariant(s);
16549       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16550         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16551             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16552         }
16553         if(p) *p++ = ',';
16554         if(n < 0) return buf;
16555     } while(s = p);
16556     return NULL;
16557 }
16558
16559 int
16560 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16561 {
16562   char buf[MSG_SIZ];
16563   int len = strlen(name);
16564   int val;
16565
16566   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16567     (*p) += len + 1;
16568     sscanf(*p, "%d", &val);
16569     *loc = (val != 0);
16570     while (**p && **p != ' ')
16571       (*p)++;
16572     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16573     SendToProgram(buf, cps);
16574     return TRUE;
16575   }
16576   return FALSE;
16577 }
16578
16579 int
16580 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16581 {
16582   char buf[MSG_SIZ];
16583   int len = strlen(name);
16584   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16585     (*p) += len + 1;
16586     sscanf(*p, "%d", loc);
16587     while (**p && **p != ' ') (*p)++;
16588     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16589     SendToProgram(buf, cps);
16590     return TRUE;
16591   }
16592   return FALSE;
16593 }
16594
16595 int
16596 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16597 {
16598   char buf[MSG_SIZ];
16599   int len = strlen(name);
16600   if (strncmp((*p), name, len) == 0
16601       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16602     (*p) += len + 2;
16603     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16604     sscanf(*p, "%[^\"]", *loc);
16605     while (**p && **p != '\"') (*p)++;
16606     if (**p == '\"') (*p)++;
16607     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16608     SendToProgram(buf, cps);
16609     return TRUE;
16610   }
16611   return FALSE;
16612 }
16613
16614 int
16615 ParseOption (Option *opt, ChessProgramState *cps)
16616 // [HGM] options: process the string that defines an engine option, and determine
16617 // name, type, default value, and allowed value range
16618 {
16619         char *p, *q, buf[MSG_SIZ];
16620         int n, min = (-1)<<31, max = 1<<31, def;
16621
16622         if(p = strstr(opt->name, " -spin ")) {
16623             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16624             if(max < min) max = min; // enforce consistency
16625             if(def < min) def = min;
16626             if(def > max) def = max;
16627             opt->value = def;
16628             opt->min = min;
16629             opt->max = max;
16630             opt->type = Spin;
16631         } else if((p = strstr(opt->name, " -slider "))) {
16632             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16633             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16634             if(max < min) max = min; // enforce consistency
16635             if(def < min) def = min;
16636             if(def > max) def = max;
16637             opt->value = def;
16638             opt->min = min;
16639             opt->max = max;
16640             opt->type = Spin; // Slider;
16641         } else if((p = strstr(opt->name, " -string "))) {
16642             opt->textValue = p+9;
16643             opt->type = TextBox;
16644         } else if((p = strstr(opt->name, " -file "))) {
16645             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16646             opt->textValue = p+7;
16647             opt->type = FileName; // FileName;
16648         } else if((p = strstr(opt->name, " -path "))) {
16649             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16650             opt->textValue = p+7;
16651             opt->type = PathName; // PathName;
16652         } else if(p = strstr(opt->name, " -check ")) {
16653             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16654             opt->value = (def != 0);
16655             opt->type = CheckBox;
16656         } else if(p = strstr(opt->name, " -combo ")) {
16657             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16658             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16659             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16660             opt->value = n = 0;
16661             while(q = StrStr(q, " /// ")) {
16662                 n++; *q = 0;    // count choices, and null-terminate each of them
16663                 q += 5;
16664                 if(*q == '*') { // remember default, which is marked with * prefix
16665                     q++;
16666                     opt->value = n;
16667                 }
16668                 cps->comboList[cps->comboCnt++] = q;
16669             }
16670             cps->comboList[cps->comboCnt++] = NULL;
16671             opt->max = n + 1;
16672             opt->type = ComboBox;
16673         } else if(p = strstr(opt->name, " -button")) {
16674             opt->type = Button;
16675         } else if(p = strstr(opt->name, " -save")) {
16676             opt->type = SaveButton;
16677         } else return FALSE;
16678         *p = 0; // terminate option name
16679         // now look if the command-line options define a setting for this engine option.
16680         if(cps->optionSettings && cps->optionSettings[0])
16681             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16682         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16683           snprintf(buf, MSG_SIZ, "option %s", p);
16684                 if(p = strstr(buf, ",")) *p = 0;
16685                 if(q = strchr(buf, '=')) switch(opt->type) {
16686                     case ComboBox:
16687                         for(n=0; n<opt->max; n++)
16688                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16689                         break;
16690                     case TextBox:
16691                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16692                         break;
16693                     case Spin:
16694                     case CheckBox:
16695                         opt->value = atoi(q+1);
16696                     default:
16697                         break;
16698                 }
16699                 strcat(buf, "\n");
16700                 SendToProgram(buf, cps);
16701         }
16702         return TRUE;
16703 }
16704
16705 void
16706 FeatureDone (ChessProgramState *cps, int val)
16707 {
16708   DelayedEventCallback cb = GetDelayedEvent();
16709   if ((cb == InitBackEnd3 && cps == &first) ||
16710       (cb == SettingsMenuIfReady && cps == &second) ||
16711       (cb == LoadEngine) ||
16712       (cb == TwoMachinesEventIfReady)) {
16713     CancelDelayedEvent();
16714     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16715   }
16716   cps->initDone = val;
16717   if(val) cps->reload = FALSE;
16718 }
16719
16720 /* Parse feature command from engine */
16721 void
16722 ParseFeatures (char *args, ChessProgramState *cps)
16723 {
16724   char *p = args;
16725   char *q = NULL;
16726   int val;
16727   char buf[MSG_SIZ];
16728
16729   for (;;) {
16730     while (*p == ' ') p++;
16731     if (*p == NULLCHAR) return;
16732
16733     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16734     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16735     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16736     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16737     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16738     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16739     if (BoolFeature(&p, "reuse", &val, cps)) {
16740       /* Engine can disable reuse, but can't enable it if user said no */
16741       if (!val) cps->reuse = FALSE;
16742       continue;
16743     }
16744     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16745     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16746       if (gameMode == TwoMachinesPlay) {
16747         DisplayTwoMachinesTitle();
16748       } else {
16749         DisplayTitle("");
16750       }
16751       continue;
16752     }
16753     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16754     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16755     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16756     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16757     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16758     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16759     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16760     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16761     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16762     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16763     if (IntFeature(&p, "done", &val, cps)) {
16764       FeatureDone(cps, val);
16765       continue;
16766     }
16767     /* Added by Tord: */
16768     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16769     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16770     /* End of additions by Tord */
16771
16772     /* [HGM] added features: */
16773     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16774     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16775     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16776     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16777     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16778     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16779     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16780     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16781         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16782         FREE(cps->option[cps->nrOptions].name);
16783         cps->option[cps->nrOptions].name = q; q = NULL;
16784         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16785           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16786             SendToProgram(buf, cps);
16787             continue;
16788         }
16789         if(cps->nrOptions >= MAX_OPTIONS) {
16790             cps->nrOptions--;
16791             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16792             DisplayError(buf, 0);
16793         }
16794         continue;
16795     }
16796     /* End of additions by HGM */
16797
16798     /* unknown feature: complain and skip */
16799     q = p;
16800     while (*q && *q != '=') q++;
16801     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16802     SendToProgram(buf, cps);
16803     p = q;
16804     if (*p == '=') {
16805       p++;
16806       if (*p == '\"') {
16807         p++;
16808         while (*p && *p != '\"') p++;
16809         if (*p == '\"') p++;
16810       } else {
16811         while (*p && *p != ' ') p++;
16812       }
16813     }
16814   }
16815
16816 }
16817
16818 void
16819 PeriodicUpdatesEvent (int newState)
16820 {
16821     if (newState == appData.periodicUpdates)
16822       return;
16823
16824     appData.periodicUpdates=newState;
16825
16826     /* Display type changes, so update it now */
16827 //    DisplayAnalysis();
16828
16829     /* Get the ball rolling again... */
16830     if (newState) {
16831         AnalysisPeriodicEvent(1);
16832         StartAnalysisClock();
16833     }
16834 }
16835
16836 void
16837 PonderNextMoveEvent (int newState)
16838 {
16839     if (newState == appData.ponderNextMove) return;
16840     if (gameMode == EditPosition) EditPositionDone(TRUE);
16841     if (newState) {
16842         SendToProgram("hard\n", &first);
16843         if (gameMode == TwoMachinesPlay) {
16844             SendToProgram("hard\n", &second);
16845         }
16846     } else {
16847         SendToProgram("easy\n", &first);
16848         thinkOutput[0] = NULLCHAR;
16849         if (gameMode == TwoMachinesPlay) {
16850             SendToProgram("easy\n", &second);
16851         }
16852     }
16853     appData.ponderNextMove = newState;
16854 }
16855
16856 void
16857 NewSettingEvent (int option, int *feature, char *command, int value)
16858 {
16859     char buf[MSG_SIZ];
16860
16861     if (gameMode == EditPosition) EditPositionDone(TRUE);
16862     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16863     if(feature == NULL || *feature) SendToProgram(buf, &first);
16864     if (gameMode == TwoMachinesPlay) {
16865         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16866     }
16867 }
16868
16869 void
16870 ShowThinkingEvent ()
16871 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16872 {
16873     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16874     int newState = appData.showThinking
16875         // [HGM] thinking: other features now need thinking output as well
16876         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16877
16878     if (oldState == newState) return;
16879     oldState = newState;
16880     if (gameMode == EditPosition) EditPositionDone(TRUE);
16881     if (oldState) {
16882         SendToProgram("post\n", &first);
16883         if (gameMode == TwoMachinesPlay) {
16884             SendToProgram("post\n", &second);
16885         }
16886     } else {
16887         SendToProgram("nopost\n", &first);
16888         thinkOutput[0] = NULLCHAR;
16889         if (gameMode == TwoMachinesPlay) {
16890             SendToProgram("nopost\n", &second);
16891         }
16892     }
16893 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16894 }
16895
16896 void
16897 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16898 {
16899   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16900   if (pr == NoProc) return;
16901   AskQuestion(title, question, replyPrefix, pr);
16902 }
16903
16904 void
16905 TypeInEvent (char firstChar)
16906 {
16907     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16908         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16909         gameMode == AnalyzeMode || gameMode == EditGame ||
16910         gameMode == EditPosition || gameMode == IcsExamining ||
16911         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16912         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16913                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16914                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16915         gameMode == Training) PopUpMoveDialog(firstChar);
16916 }
16917
16918 void
16919 TypeInDoneEvent (char *move)
16920 {
16921         Board board;
16922         int n, fromX, fromY, toX, toY;
16923         char promoChar;
16924         ChessMove moveType;
16925
16926         // [HGM] FENedit
16927         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16928                 EditPositionPasteFEN(move);
16929                 return;
16930         }
16931         // [HGM] movenum: allow move number to be typed in any mode
16932         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16933           ToNrEvent(2*n-1);
16934           return;
16935         }
16936         // undocumented kludge: allow command-line option to be typed in!
16937         // (potentially fatal, and does not implement the effect of the option.)
16938         // should only be used for options that are values on which future decisions will be made,
16939         // and definitely not on options that would be used during initialization.
16940         if(strstr(move, "!!! -") == move) {
16941             ParseArgsFromString(move+4);
16942             return;
16943         }
16944
16945       if (gameMode != EditGame && currentMove != forwardMostMove &&
16946         gameMode != Training) {
16947         DisplayMoveError(_("Displayed move is not current"));
16948       } else {
16949         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16950           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16951         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16952         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16953           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16954           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16955         } else {
16956           DisplayMoveError(_("Could not parse move"));
16957         }
16958       }
16959 }
16960
16961 void
16962 DisplayMove (int moveNumber)
16963 {
16964     char message[MSG_SIZ];
16965     char res[MSG_SIZ];
16966     char cpThinkOutput[MSG_SIZ];
16967
16968     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16969
16970     if (moveNumber == forwardMostMove - 1 ||
16971         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16972
16973         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16974
16975         if (strchr(cpThinkOutput, '\n')) {
16976             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16977         }
16978     } else {
16979         *cpThinkOutput = NULLCHAR;
16980     }
16981
16982     /* [AS] Hide thinking from human user */
16983     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16984         *cpThinkOutput = NULLCHAR;
16985         if( thinkOutput[0] != NULLCHAR ) {
16986             int i;
16987
16988             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16989                 cpThinkOutput[i] = '.';
16990             }
16991             cpThinkOutput[i] = NULLCHAR;
16992             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16993         }
16994     }
16995
16996     if (moveNumber == forwardMostMove - 1 &&
16997         gameInfo.resultDetails != NULL) {
16998         if (gameInfo.resultDetails[0] == NULLCHAR) {
16999           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17000         } else {
17001           snprintf(res, MSG_SIZ, " {%s} %s",
17002                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17003         }
17004     } else {
17005         res[0] = NULLCHAR;
17006     }
17007
17008     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17009         DisplayMessage(res, cpThinkOutput);
17010     } else {
17011       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17012                 WhiteOnMove(moveNumber) ? " " : ".. ",
17013                 parseList[moveNumber], res);
17014         DisplayMessage(message, cpThinkOutput);
17015     }
17016 }
17017
17018 void
17019 DisplayComment (int moveNumber, char *text)
17020 {
17021     char title[MSG_SIZ];
17022
17023     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17024       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17025     } else {
17026       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17027               WhiteOnMove(moveNumber) ? " " : ".. ",
17028               parseList[moveNumber]);
17029     }
17030     if (text != NULL && (appData.autoDisplayComment || commentUp))
17031         CommentPopUp(title, text);
17032 }
17033
17034 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17035  * might be busy thinking or pondering.  It can be omitted if your
17036  * gnuchess is configured to stop thinking immediately on any user
17037  * input.  However, that gnuchess feature depends on the FIONREAD
17038  * ioctl, which does not work properly on some flavors of Unix.
17039  */
17040 void
17041 Attention (ChessProgramState *cps)
17042 {
17043 #if ATTENTION
17044     if (!cps->useSigint) return;
17045     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17046     switch (gameMode) {
17047       case MachinePlaysWhite:
17048       case MachinePlaysBlack:
17049       case TwoMachinesPlay:
17050       case IcsPlayingWhite:
17051       case IcsPlayingBlack:
17052       case AnalyzeMode:
17053       case AnalyzeFile:
17054         /* Skip if we know it isn't thinking */
17055         if (!cps->maybeThinking) return;
17056         if (appData.debugMode)
17057           fprintf(debugFP, "Interrupting %s\n", cps->which);
17058         InterruptChildProcess(cps->pr);
17059         cps->maybeThinking = FALSE;
17060         break;
17061       default:
17062         break;
17063     }
17064 #endif /*ATTENTION*/
17065 }
17066
17067 int
17068 CheckFlags ()
17069 {
17070     if (whiteTimeRemaining <= 0) {
17071         if (!whiteFlag) {
17072             whiteFlag = TRUE;
17073             if (appData.icsActive) {
17074                 if (appData.autoCallFlag &&
17075                     gameMode == IcsPlayingBlack && !blackFlag) {
17076                   SendToICS(ics_prefix);
17077                   SendToICS("flag\n");
17078                 }
17079             } else {
17080                 if (blackFlag) {
17081                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17082                 } else {
17083                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17084                     if (appData.autoCallFlag) {
17085                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17086                         return TRUE;
17087                     }
17088                 }
17089             }
17090         }
17091     }
17092     if (blackTimeRemaining <= 0) {
17093         if (!blackFlag) {
17094             blackFlag = TRUE;
17095             if (appData.icsActive) {
17096                 if (appData.autoCallFlag &&
17097                     gameMode == IcsPlayingWhite && !whiteFlag) {
17098                   SendToICS(ics_prefix);
17099                   SendToICS("flag\n");
17100                 }
17101             } else {
17102                 if (whiteFlag) {
17103                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17104                 } else {
17105                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17106                     if (appData.autoCallFlag) {
17107                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17108                         return TRUE;
17109                     }
17110                 }
17111             }
17112         }
17113     }
17114     return FALSE;
17115 }
17116
17117 void
17118 CheckTimeControl ()
17119 {
17120     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17121         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17122
17123     /*
17124      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17125      */
17126     if ( !WhiteOnMove(forwardMostMove) ) {
17127         /* White made time control */
17128         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17129         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17130         /* [HGM] time odds: correct new time quota for time odds! */
17131                                             / WhitePlayer()->timeOdds;
17132         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17133     } else {
17134         lastBlack -= blackTimeRemaining;
17135         /* Black made time control */
17136         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17137                                             / WhitePlayer()->other->timeOdds;
17138         lastWhite = whiteTimeRemaining;
17139     }
17140 }
17141
17142 void
17143 DisplayBothClocks ()
17144 {
17145     int wom = gameMode == EditPosition ?
17146       !blackPlaysFirst : WhiteOnMove(currentMove);
17147     DisplayWhiteClock(whiteTimeRemaining, wom);
17148     DisplayBlackClock(blackTimeRemaining, !wom);
17149 }
17150
17151
17152 /* Timekeeping seems to be a portability nightmare.  I think everyone
17153    has ftime(), but I'm really not sure, so I'm including some ifdefs
17154    to use other calls if you don't.  Clocks will be less accurate if
17155    you have neither ftime nor gettimeofday.
17156 */
17157
17158 /* VS 2008 requires the #include outside of the function */
17159 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17160 #include <sys/timeb.h>
17161 #endif
17162
17163 /* Get the current time as a TimeMark */
17164 void
17165 GetTimeMark (TimeMark *tm)
17166 {
17167 #if HAVE_GETTIMEOFDAY
17168
17169     struct timeval timeVal;
17170     struct timezone timeZone;
17171
17172     gettimeofday(&timeVal, &timeZone);
17173     tm->sec = (long) timeVal.tv_sec;
17174     tm->ms = (int) (timeVal.tv_usec / 1000L);
17175
17176 #else /*!HAVE_GETTIMEOFDAY*/
17177 #if HAVE_FTIME
17178
17179 // include <sys/timeb.h> / moved to just above start of function
17180     struct timeb timeB;
17181
17182     ftime(&timeB);
17183     tm->sec = (long) timeB.time;
17184     tm->ms = (int) timeB.millitm;
17185
17186 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17187     tm->sec = (long) time(NULL);
17188     tm->ms = 0;
17189 #endif
17190 #endif
17191 }
17192
17193 /* Return the difference in milliseconds between two
17194    time marks.  We assume the difference will fit in a long!
17195 */
17196 long
17197 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17198 {
17199     return 1000L*(tm2->sec - tm1->sec) +
17200            (long) (tm2->ms - tm1->ms);
17201 }
17202
17203
17204 /*
17205  * Code to manage the game clocks.
17206  *
17207  * In tournament play, black starts the clock and then white makes a move.
17208  * We give the human user a slight advantage if he is playing white---the
17209  * clocks don't run until he makes his first move, so it takes zero time.
17210  * Also, we don't account for network lag, so we could get out of sync
17211  * with GNU Chess's clock -- but then, referees are always right.
17212  */
17213
17214 static TimeMark tickStartTM;
17215 static long intendedTickLength;
17216
17217 long
17218 NextTickLength (long timeRemaining)
17219 {
17220     long nominalTickLength, nextTickLength;
17221
17222     if (timeRemaining > 0L && timeRemaining <= 10000L)
17223       nominalTickLength = 100L;
17224     else
17225       nominalTickLength = 1000L;
17226     nextTickLength = timeRemaining % nominalTickLength;
17227     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17228
17229     return nextTickLength;
17230 }
17231
17232 /* Adjust clock one minute up or down */
17233 void
17234 AdjustClock (Boolean which, int dir)
17235 {
17236     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17237     if(which) blackTimeRemaining += 60000*dir;
17238     else      whiteTimeRemaining += 60000*dir;
17239     DisplayBothClocks();
17240     adjustedClock = TRUE;
17241 }
17242
17243 /* Stop clocks and reset to a fresh time control */
17244 void
17245 ResetClocks ()
17246 {
17247     (void) StopClockTimer();
17248     if (appData.icsActive) {
17249         whiteTimeRemaining = blackTimeRemaining = 0;
17250     } else if (searchTime) {
17251         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17252         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17253     } else { /* [HGM] correct new time quote for time odds */
17254         whiteTC = blackTC = fullTimeControlString;
17255         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17256         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17257     }
17258     if (whiteFlag || blackFlag) {
17259         DisplayTitle("");
17260         whiteFlag = blackFlag = FALSE;
17261     }
17262     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17263     DisplayBothClocks();
17264     adjustedClock = FALSE;
17265 }
17266
17267 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17268
17269 /* Decrement running clock by amount of time that has passed */
17270 void
17271 DecrementClocks ()
17272 {
17273     long timeRemaining;
17274     long lastTickLength, fudge;
17275     TimeMark now;
17276
17277     if (!appData.clockMode) return;
17278     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17279
17280     GetTimeMark(&now);
17281
17282     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17283
17284     /* Fudge if we woke up a little too soon */
17285     fudge = intendedTickLength - lastTickLength;
17286     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17287
17288     if (WhiteOnMove(forwardMostMove)) {
17289         if(whiteNPS >= 0) lastTickLength = 0;
17290         timeRemaining = whiteTimeRemaining -= lastTickLength;
17291         if(timeRemaining < 0 && !appData.icsActive) {
17292             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17293             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17294                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17295                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17296             }
17297         }
17298         DisplayWhiteClock(whiteTimeRemaining - fudge,
17299                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17300     } else {
17301         if(blackNPS >= 0) lastTickLength = 0;
17302         timeRemaining = blackTimeRemaining -= lastTickLength;
17303         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17304             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17305             if(suddenDeath) {
17306                 blackStartMove = forwardMostMove;
17307                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17308             }
17309         }
17310         DisplayBlackClock(blackTimeRemaining - fudge,
17311                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17312     }
17313     if (CheckFlags()) return;
17314
17315     if(twoBoards) { // count down secondary board's clocks as well
17316         activePartnerTime -= lastTickLength;
17317         partnerUp = 1;
17318         if(activePartner == 'W')
17319             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17320         else
17321             DisplayBlackClock(activePartnerTime, TRUE);
17322         partnerUp = 0;
17323     }
17324
17325     tickStartTM = now;
17326     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17327     StartClockTimer(intendedTickLength);
17328
17329     /* if the time remaining has fallen below the alarm threshold, sound the
17330      * alarm. if the alarm has sounded and (due to a takeback or time control
17331      * with increment) the time remaining has increased to a level above the
17332      * threshold, reset the alarm so it can sound again.
17333      */
17334
17335     if (appData.icsActive && appData.icsAlarm) {
17336
17337         /* make sure we are dealing with the user's clock */
17338         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17339                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17340            )) return;
17341
17342         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17343             alarmSounded = FALSE;
17344         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17345             PlayAlarmSound();
17346             alarmSounded = TRUE;
17347         }
17348     }
17349 }
17350
17351
17352 /* A player has just moved, so stop the previously running
17353    clock and (if in clock mode) start the other one.
17354    We redisplay both clocks in case we're in ICS mode, because
17355    ICS gives us an update to both clocks after every move.
17356    Note that this routine is called *after* forwardMostMove
17357    is updated, so the last fractional tick must be subtracted
17358    from the color that is *not* on move now.
17359 */
17360 void
17361 SwitchClocks (int newMoveNr)
17362 {
17363     long lastTickLength;
17364     TimeMark now;
17365     int flagged = FALSE;
17366
17367     GetTimeMark(&now);
17368
17369     if (StopClockTimer() && appData.clockMode) {
17370         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17371         if (!WhiteOnMove(forwardMostMove)) {
17372             if(blackNPS >= 0) lastTickLength = 0;
17373             blackTimeRemaining -= lastTickLength;
17374            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17375 //         if(pvInfoList[forwardMostMove].time == -1)
17376                  pvInfoList[forwardMostMove].time =               // use GUI time
17377                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17378         } else {
17379            if(whiteNPS >= 0) lastTickLength = 0;
17380            whiteTimeRemaining -= lastTickLength;
17381            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17382 //         if(pvInfoList[forwardMostMove].time == -1)
17383                  pvInfoList[forwardMostMove].time =
17384                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17385         }
17386         flagged = CheckFlags();
17387     }
17388     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17389     CheckTimeControl();
17390
17391     if (flagged || !appData.clockMode) return;
17392
17393     switch (gameMode) {
17394       case MachinePlaysBlack:
17395       case MachinePlaysWhite:
17396       case BeginningOfGame:
17397         if (pausing) return;
17398         break;
17399
17400       case EditGame:
17401       case PlayFromGameFile:
17402       case IcsExamining:
17403         return;
17404
17405       default:
17406         break;
17407     }
17408
17409     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17410         if(WhiteOnMove(forwardMostMove))
17411              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17412         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17413     }
17414
17415     tickStartTM = now;
17416     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17417       whiteTimeRemaining : blackTimeRemaining);
17418     StartClockTimer(intendedTickLength);
17419 }
17420
17421
17422 /* Stop both clocks */
17423 void
17424 StopClocks ()
17425 {
17426     long lastTickLength;
17427     TimeMark now;
17428
17429     if (!StopClockTimer()) return;
17430     if (!appData.clockMode) return;
17431
17432     GetTimeMark(&now);
17433
17434     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17435     if (WhiteOnMove(forwardMostMove)) {
17436         if(whiteNPS >= 0) lastTickLength = 0;
17437         whiteTimeRemaining -= lastTickLength;
17438         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17439     } else {
17440         if(blackNPS >= 0) lastTickLength = 0;
17441         blackTimeRemaining -= lastTickLength;
17442         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17443     }
17444     CheckFlags();
17445 }
17446
17447 /* Start clock of player on move.  Time may have been reset, so
17448    if clock is already running, stop and restart it. */
17449 void
17450 StartClocks ()
17451 {
17452     (void) StopClockTimer(); /* in case it was running already */
17453     DisplayBothClocks();
17454     if (CheckFlags()) return;
17455
17456     if (!appData.clockMode) return;
17457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17458
17459     GetTimeMark(&tickStartTM);
17460     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17461       whiteTimeRemaining : blackTimeRemaining);
17462
17463    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17464     whiteNPS = blackNPS = -1;
17465     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17466        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17467         whiteNPS = first.nps;
17468     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17469        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17470         blackNPS = first.nps;
17471     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17472         whiteNPS = second.nps;
17473     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17474         blackNPS = second.nps;
17475     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17476
17477     StartClockTimer(intendedTickLength);
17478 }
17479
17480 char *
17481 TimeString (long ms)
17482 {
17483     long second, minute, hour, day;
17484     char *sign = "";
17485     static char buf[32];
17486
17487     if (ms > 0 && ms <= 9900) {
17488       /* convert milliseconds to tenths, rounding up */
17489       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17490
17491       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17492       return buf;
17493     }
17494
17495     /* convert milliseconds to seconds, rounding up */
17496     /* use floating point to avoid strangeness of integer division
17497        with negative dividends on many machines */
17498     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17499
17500     if (second < 0) {
17501         sign = "-";
17502         second = -second;
17503     }
17504
17505     day = second / (60 * 60 * 24);
17506     second = second % (60 * 60 * 24);
17507     hour = second / (60 * 60);
17508     second = second % (60 * 60);
17509     minute = second / 60;
17510     second = second % 60;
17511
17512     if (day > 0)
17513       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17514               sign, day, hour, minute, second);
17515     else if (hour > 0)
17516       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17517     else
17518       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17519
17520     return buf;
17521 }
17522
17523
17524 /*
17525  * This is necessary because some C libraries aren't ANSI C compliant yet.
17526  */
17527 char *
17528 StrStr (char *string, char *match)
17529 {
17530     int i, length;
17531
17532     length = strlen(match);
17533
17534     for (i = strlen(string) - length; i >= 0; i--, string++)
17535       if (!strncmp(match, string, length))
17536         return string;
17537
17538     return NULL;
17539 }
17540
17541 char *
17542 StrCaseStr (char *string, char *match)
17543 {
17544     int i, j, length;
17545
17546     length = strlen(match);
17547
17548     for (i = strlen(string) - length; i >= 0; i--, string++) {
17549         for (j = 0; j < length; j++) {
17550             if (ToLower(match[j]) != ToLower(string[j]))
17551               break;
17552         }
17553         if (j == length) return string;
17554     }
17555
17556     return NULL;
17557 }
17558
17559 #ifndef _amigados
17560 int
17561 StrCaseCmp (char *s1, char *s2)
17562 {
17563     char c1, c2;
17564
17565     for (;;) {
17566         c1 = ToLower(*s1++);
17567         c2 = ToLower(*s2++);
17568         if (c1 > c2) return 1;
17569         if (c1 < c2) return -1;
17570         if (c1 == NULLCHAR) return 0;
17571     }
17572 }
17573
17574
17575 int
17576 ToLower (int c)
17577 {
17578     return isupper(c) ? tolower(c) : c;
17579 }
17580
17581
17582 int
17583 ToUpper (int c)
17584 {
17585     return islower(c) ? toupper(c) : c;
17586 }
17587 #endif /* !_amigados    */
17588
17589 char *
17590 StrSave (char *s)
17591 {
17592   char *ret;
17593
17594   if ((ret = (char *) malloc(strlen(s) + 1)))
17595     {
17596       safeStrCpy(ret, s, strlen(s)+1);
17597     }
17598   return ret;
17599 }
17600
17601 char *
17602 StrSavePtr (char *s, char **savePtr)
17603 {
17604     if (*savePtr) {
17605         free(*savePtr);
17606     }
17607     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17608       safeStrCpy(*savePtr, s, strlen(s)+1);
17609     }
17610     return(*savePtr);
17611 }
17612
17613 char *
17614 PGNDate ()
17615 {
17616     time_t clock;
17617     struct tm *tm;
17618     char buf[MSG_SIZ];
17619
17620     clock = time((time_t *)NULL);
17621     tm = localtime(&clock);
17622     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17623             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17624     return StrSave(buf);
17625 }
17626
17627
17628 char *
17629 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17630 {
17631     int i, j, fromX, fromY, toX, toY;
17632     int whiteToPlay;
17633     char buf[MSG_SIZ];
17634     char *p, *q;
17635     int emptycount;
17636     ChessSquare piece;
17637
17638     whiteToPlay = (gameMode == EditPosition) ?
17639       !blackPlaysFirst : (move % 2 == 0);
17640     p = buf;
17641
17642     /* Piece placement data */
17643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17644         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17645         emptycount = 0;
17646         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17647             if (boards[move][i][j] == EmptySquare) {
17648                 emptycount++;
17649             } else { ChessSquare piece = boards[move][i][j];
17650                 if (emptycount > 0) {
17651                     if(emptycount<10) /* [HGM] can be >= 10 */
17652                         *p++ = '0' + emptycount;
17653                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17654                     emptycount = 0;
17655                 }
17656                 if(PieceToChar(piece) == '+') {
17657                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17658                     *p++ = '+';
17659                     piece = (ChessSquare)(CHUDEMOTED piece);
17660                 }
17661                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17662                 if(p[-1] == '~') {
17663                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17664                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17665                     *p++ = '~';
17666                 }
17667             }
17668         }
17669         if (emptycount > 0) {
17670             if(emptycount<10) /* [HGM] can be >= 10 */
17671                 *p++ = '0' + emptycount;
17672             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17673             emptycount = 0;
17674         }
17675         *p++ = '/';
17676     }
17677     *(p - 1) = ' ';
17678
17679     /* [HGM] print Crazyhouse or Shogi holdings */
17680     if( gameInfo.holdingsWidth ) {
17681         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17682         q = p;
17683         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17684             piece = boards[move][i][BOARD_WIDTH-1];
17685             if( piece != EmptySquare )
17686               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17687                   *p++ = PieceToChar(piece);
17688         }
17689         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17690             piece = boards[move][BOARD_HEIGHT-i-1][0];
17691             if( piece != EmptySquare )
17692               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17693                   *p++ = PieceToChar(piece);
17694         }
17695
17696         if( q == p ) *p++ = '-';
17697         *p++ = ']';
17698         *p++ = ' ';
17699     }
17700
17701     /* Active color */
17702     *p++ = whiteToPlay ? 'w' : 'b';
17703     *p++ = ' ';
17704
17705   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17706     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17707   } else {
17708   if(nrCastlingRights) {
17709      q = p;
17710      if(appData.fischerCastling) {
17711        /* [HGM] write directly from rights */
17712            if(boards[move][CASTLING][2] != NoRights &&
17713               boards[move][CASTLING][0] != NoRights   )
17714                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17715            if(boards[move][CASTLING][2] != NoRights &&
17716               boards[move][CASTLING][1] != NoRights   )
17717                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17718            if(boards[move][CASTLING][5] != NoRights &&
17719               boards[move][CASTLING][3] != NoRights   )
17720                 *p++ = boards[move][CASTLING][3] + AAA;
17721            if(boards[move][CASTLING][5] != NoRights &&
17722               boards[move][CASTLING][4] != NoRights   )
17723                 *p++ = boards[move][CASTLING][4] + AAA;
17724      } else {
17725
17726         /* [HGM] write true castling rights */
17727         if( nrCastlingRights == 6 ) {
17728             int q, k=0;
17729             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17730                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17731             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17732                  boards[move][CASTLING][2] != NoRights  );
17733             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17734                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17735                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17736                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17737                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17738             }
17739             if(q) *p++ = 'Q';
17740             k = 0;
17741             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17742                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17743             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17744                  boards[move][CASTLING][5] != NoRights  );
17745             if(gameInfo.variant == VariantSChess) {
17746                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17747                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17748                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17749                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17750             }
17751             if(q) *p++ = 'q';
17752         }
17753      }
17754      if (q == p) *p++ = '-'; /* No castling rights */
17755      *p++ = ' ';
17756   }
17757
17758   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17759      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17760      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17761     /* En passant target square */
17762     if (move > backwardMostMove) {
17763         fromX = moveList[move - 1][0] - AAA;
17764         fromY = moveList[move - 1][1] - ONE;
17765         toX = moveList[move - 1][2] - AAA;
17766         toY = moveList[move - 1][3] - ONE;
17767         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17768             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17769             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17770             fromX == toX) {
17771             /* 2-square pawn move just happened */
17772             *p++ = toX + AAA;
17773             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17774         } else {
17775             *p++ = '-';
17776         }
17777     } else if(move == backwardMostMove) {
17778         // [HGM] perhaps we should always do it like this, and forget the above?
17779         if((signed char)boards[move][EP_STATUS] >= 0) {
17780             *p++ = boards[move][EP_STATUS] + AAA;
17781             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17782         } else {
17783             *p++ = '-';
17784         }
17785     } else {
17786         *p++ = '-';
17787     }
17788     *p++ = ' ';
17789   }
17790   }
17791
17792     if(moveCounts)
17793     {   int i = 0, j=move;
17794
17795         /* [HGM] find reversible plies */
17796         if (appData.debugMode) { int k;
17797             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17798             for(k=backwardMostMove; k<=forwardMostMove; k++)
17799                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17800
17801         }
17802
17803         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17804         if( j == backwardMostMove ) i += initialRulePlies;
17805         sprintf(p, "%d ", i);
17806         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17807
17808         /* Fullmove number */
17809         sprintf(p, "%d", (move / 2) + 1);
17810     } else *--p = NULLCHAR;
17811
17812     return StrSave(buf);
17813 }
17814
17815 Boolean
17816 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17817 {
17818     int i, j, k, w=0, subst=0, shuffle=0;
17819     char *p, c;
17820     int emptycount, virgin[BOARD_FILES];
17821     ChessSquare piece;
17822
17823     p = fen;
17824
17825     /* Piece placement data */
17826     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17827         j = 0;
17828         for (;;) {
17829             if (*p == '/' || *p == ' ' || *p == '[' ) {
17830                 if(j > w) w = j;
17831                 emptycount = gameInfo.boardWidth - j;
17832                 while (emptycount--)
17833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17834                 if (*p == '/') p++;
17835                 else if(autoSize) { // we stumbled unexpectedly into end of board
17836                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17837                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17838                     }
17839                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17840                 }
17841                 break;
17842 #if(BOARD_FILES >= 10)*0
17843             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17844                 p++; emptycount=10;
17845                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17846                 while (emptycount--)
17847                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17848 #endif
17849             } else if (*p == '*') {
17850                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17851             } else if (isdigit(*p)) {
17852                 emptycount = *p++ - '0';
17853                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17854                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17855                 while (emptycount--)
17856                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17857             } else if (*p == '<') {
17858                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17859                 else if (i != 0 || !shuffle) return FALSE;
17860                 p++;
17861             } else if (shuffle && *p == '>') {
17862                 p++; // for now ignore closing shuffle range, and assume rank-end
17863             } else if (*p == '?') {
17864                 if (j >= gameInfo.boardWidth) return FALSE;
17865                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17866                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17867             } else if (*p == '+' || isalpha(*p)) {
17868                 if (j >= gameInfo.boardWidth) return FALSE;
17869                 if(*p=='+') {
17870                     piece = CharToPiece(*++p);
17871                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17872                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17873                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17874                 } else piece = CharToPiece(*p++);
17875
17876                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17877                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17878                     piece = (ChessSquare) (PROMOTED piece);
17879                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17880                     p++;
17881                 }
17882                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17883             } else {
17884                 return FALSE;
17885             }
17886         }
17887     }
17888     while (*p == '/' || *p == ' ') p++;
17889
17890     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17891
17892     /* [HGM] by default clear Crazyhouse holdings, if present */
17893     if(gameInfo.holdingsWidth) {
17894        for(i=0; i<BOARD_HEIGHT; i++) {
17895            board[i][0]             = EmptySquare; /* black holdings */
17896            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17897            board[i][1]             = (ChessSquare) 0; /* black counts */
17898            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17899        }
17900     }
17901
17902     /* [HGM] look for Crazyhouse holdings here */
17903     while(*p==' ') p++;
17904     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17905         int swap=0, wcnt=0, bcnt=0;
17906         if(*p == '[') p++;
17907         if(*p == '<') swap++, p++;
17908         if(*p == '-' ) p++; /* empty holdings */ else {
17909             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17910             /* if we would allow FEN reading to set board size, we would   */
17911             /* have to add holdings and shift the board read so far here   */
17912             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17913                 p++;
17914                 if((int) piece >= (int) BlackPawn ) {
17915                     i = (int)piece - (int)BlackPawn;
17916                     i = PieceToNumber((ChessSquare)i);
17917                     if( i >= gameInfo.holdingsSize ) return FALSE;
17918                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17919                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17920                     bcnt++;
17921                 } else {
17922                     i = (int)piece - (int)WhitePawn;
17923                     i = PieceToNumber((ChessSquare)i);
17924                     if( i >= gameInfo.holdingsSize ) return FALSE;
17925                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17926                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17927                     wcnt++;
17928                 }
17929             }
17930             if(subst) { // substitute back-rank question marks by holdings pieces
17931                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17932                     int k, m, n = bcnt + 1;
17933                     if(board[0][j] == ClearBoard) {
17934                         if(!wcnt) return FALSE;
17935                         n = rand() % wcnt;
17936                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17937                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17938                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17939                             break;
17940                         }
17941                     }
17942                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17943                         if(!bcnt) return FALSE;
17944                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17945                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17946                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17947                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17948                             break;
17949                         }
17950                     }
17951                 }
17952                 subst = 0;
17953             }
17954         }
17955         if(*p == ']') p++;
17956     }
17957
17958     if(subst) return FALSE; // substitution requested, but no holdings
17959
17960     while(*p == ' ') p++;
17961
17962     /* Active color */
17963     c = *p++;
17964     if(appData.colorNickNames) {
17965       if( c == appData.colorNickNames[0] ) c = 'w'; else
17966       if( c == appData.colorNickNames[1] ) c = 'b';
17967     }
17968     switch (c) {
17969       case 'w':
17970         *blackPlaysFirst = FALSE;
17971         break;
17972       case 'b':
17973         *blackPlaysFirst = TRUE;
17974         break;
17975       default:
17976         return FALSE;
17977     }
17978
17979     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17980     /* return the extra info in global variiables             */
17981
17982     /* set defaults in case FEN is incomplete */
17983     board[EP_STATUS] = EP_UNKNOWN;
17984     for(i=0; i<nrCastlingRights; i++ ) {
17985         board[CASTLING][i] =
17986             appData.fischerCastling ? NoRights : initialRights[i];
17987     }   /* assume possible unless obviously impossible */
17988     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17989     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17990     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17991                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17992     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17993     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17994     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17995                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17996     FENrulePlies = 0;
17997
17998     while(*p==' ') p++;
17999     if(nrCastlingRights) {
18000       int fischer = 0;
18001       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18002       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18003           /* castling indicator present, so default becomes no castlings */
18004           for(i=0; i<nrCastlingRights; i++ ) {
18005                  board[CASTLING][i] = NoRights;
18006           }
18007       }
18008       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18009              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18010              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18011              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18012         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18013
18014         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18015             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18016             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18017         }
18018         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18019             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18020         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18021                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18022         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18023                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18024         switch(c) {
18025           case'K':
18026               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18027               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18028               board[CASTLING][2] = whiteKingFile;
18029               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18030               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18031               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18032               break;
18033           case'Q':
18034               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18035               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18036               board[CASTLING][2] = whiteKingFile;
18037               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18038               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18039               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18040               break;
18041           case'k':
18042               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18043               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18044               board[CASTLING][5] = blackKingFile;
18045               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18046               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18047               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18048               break;
18049           case'q':
18050               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18051               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18052               board[CASTLING][5] = blackKingFile;
18053               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18054               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18055               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18056           case '-':
18057               break;
18058           default: /* FRC castlings */
18059               if(c >= 'a') { /* black rights */
18060                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18061                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18062                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18063                   if(i == BOARD_RGHT) break;
18064                   board[CASTLING][5] = i;
18065                   c -= AAA;
18066                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18067                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18068                   if(c > i)
18069                       board[CASTLING][3] = c;
18070                   else
18071                       board[CASTLING][4] = c;
18072               } else { /* white rights */
18073                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18074                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18075                     if(board[0][i] == WhiteKing) break;
18076                   if(i == BOARD_RGHT) break;
18077                   board[CASTLING][2] = i;
18078                   c -= AAA - 'a' + 'A';
18079                   if(board[0][c] >= WhiteKing) break;
18080                   if(c > i)
18081                       board[CASTLING][0] = c;
18082                   else
18083                       board[CASTLING][1] = c;
18084               }
18085         }
18086       }
18087       for(i=0; i<nrCastlingRights; i++)
18088         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18089       if(gameInfo.variant == VariantSChess)
18090         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18091       if(fischer && shuffle) appData.fischerCastling = TRUE;
18092     if (appData.debugMode) {
18093         fprintf(debugFP, "FEN castling rights:");
18094         for(i=0; i<nrCastlingRights; i++)
18095         fprintf(debugFP, " %d", board[CASTLING][i]);
18096         fprintf(debugFP, "\n");
18097     }
18098
18099       while(*p==' ') p++;
18100     }
18101
18102     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18103
18104     /* read e.p. field in games that know e.p. capture */
18105     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18106        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18107        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18108       if(*p=='-') {
18109         p++; board[EP_STATUS] = EP_NONE;
18110       } else {
18111          char c = *p++ - AAA;
18112
18113          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18114          if(*p >= '0' && *p <='9') p++;
18115          board[EP_STATUS] = c;
18116       }
18117     }
18118
18119
18120     if(sscanf(p, "%d", &i) == 1) {
18121         FENrulePlies = i; /* 50-move ply counter */
18122         /* (The move number is still ignored)    */
18123     }
18124
18125     return TRUE;
18126 }
18127
18128 void
18129 EditPositionPasteFEN (char *fen)
18130 {
18131   if (fen != NULL) {
18132     Board initial_position;
18133
18134     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18135       DisplayError(_("Bad FEN position in clipboard"), 0);
18136       return ;
18137     } else {
18138       int savedBlackPlaysFirst = blackPlaysFirst;
18139       EditPositionEvent();
18140       blackPlaysFirst = savedBlackPlaysFirst;
18141       CopyBoard(boards[0], initial_position);
18142       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18143       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18144       DisplayBothClocks();
18145       DrawPosition(FALSE, boards[currentMove]);
18146     }
18147   }
18148 }
18149
18150 static char cseq[12] = "\\   ";
18151
18152 Boolean
18153 set_cont_sequence (char *new_seq)
18154 {
18155     int len;
18156     Boolean ret;
18157
18158     // handle bad attempts to set the sequence
18159         if (!new_seq)
18160                 return 0; // acceptable error - no debug
18161
18162     len = strlen(new_seq);
18163     ret = (len > 0) && (len < sizeof(cseq));
18164     if (ret)
18165       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18166     else if (appData.debugMode)
18167       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18168     return ret;
18169 }
18170
18171 /*
18172     reformat a source message so words don't cross the width boundary.  internal
18173     newlines are not removed.  returns the wrapped size (no null character unless
18174     included in source message).  If dest is NULL, only calculate the size required
18175     for the dest buffer.  lp argument indicats line position upon entry, and it's
18176     passed back upon exit.
18177 */
18178 int
18179 wrap (char *dest, char *src, int count, int width, int *lp)
18180 {
18181     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18182
18183     cseq_len = strlen(cseq);
18184     old_line = line = *lp;
18185     ansi = len = clen = 0;
18186
18187     for (i=0; i < count; i++)
18188     {
18189         if (src[i] == '\033')
18190             ansi = 1;
18191
18192         // if we hit the width, back up
18193         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18194         {
18195             // store i & len in case the word is too long
18196             old_i = i, old_len = len;
18197
18198             // find the end of the last word
18199             while (i && src[i] != ' ' && src[i] != '\n')
18200             {
18201                 i--;
18202                 len--;
18203             }
18204
18205             // word too long?  restore i & len before splitting it
18206             if ((old_i-i+clen) >= width)
18207             {
18208                 i = old_i;
18209                 len = old_len;
18210             }
18211
18212             // extra space?
18213             if (i && src[i-1] == ' ')
18214                 len--;
18215
18216             if (src[i] != ' ' && src[i] != '\n')
18217             {
18218                 i--;
18219                 if (len)
18220                     len--;
18221             }
18222
18223             // now append the newline and continuation sequence
18224             if (dest)
18225                 dest[len] = '\n';
18226             len++;
18227             if (dest)
18228                 strncpy(dest+len, cseq, cseq_len);
18229             len += cseq_len;
18230             line = cseq_len;
18231             clen = cseq_len;
18232             continue;
18233         }
18234
18235         if (dest)
18236             dest[len] = src[i];
18237         len++;
18238         if (!ansi)
18239             line++;
18240         if (src[i] == '\n')
18241             line = 0;
18242         if (src[i] == 'm')
18243             ansi = 0;
18244     }
18245     if (dest && appData.debugMode)
18246     {
18247         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18248             count, width, line, len, *lp);
18249         show_bytes(debugFP, src, count);
18250         fprintf(debugFP, "\ndest: ");
18251         show_bytes(debugFP, dest, len);
18252         fprintf(debugFP, "\n");
18253     }
18254     *lp = dest ? line : old_line;
18255
18256     return len;
18257 }
18258
18259 // [HGM] vari: routines for shelving variations
18260 Boolean modeRestore = FALSE;
18261
18262 void
18263 PushInner (int firstMove, int lastMove)
18264 {
18265         int i, j, nrMoves = lastMove - firstMove;
18266
18267         // push current tail of game on stack
18268         savedResult[storedGames] = gameInfo.result;
18269         savedDetails[storedGames] = gameInfo.resultDetails;
18270         gameInfo.resultDetails = NULL;
18271         savedFirst[storedGames] = firstMove;
18272         savedLast [storedGames] = lastMove;
18273         savedFramePtr[storedGames] = framePtr;
18274         framePtr -= nrMoves; // reserve space for the boards
18275         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18276             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18277             for(j=0; j<MOVE_LEN; j++)
18278                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18279             for(j=0; j<2*MOVE_LEN; j++)
18280                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18281             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18282             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18283             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18284             pvInfoList[firstMove+i-1].depth = 0;
18285             commentList[framePtr+i] = commentList[firstMove+i];
18286             commentList[firstMove+i] = NULL;
18287         }
18288
18289         storedGames++;
18290         forwardMostMove = firstMove; // truncate game so we can start variation
18291 }
18292
18293 void
18294 PushTail (int firstMove, int lastMove)
18295 {
18296         if(appData.icsActive) { // only in local mode
18297                 forwardMostMove = currentMove; // mimic old ICS behavior
18298                 return;
18299         }
18300         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18301
18302         PushInner(firstMove, lastMove);
18303         if(storedGames == 1) GreyRevert(FALSE);
18304         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18305 }
18306
18307 void
18308 PopInner (Boolean annotate)
18309 {
18310         int i, j, nrMoves;
18311         char buf[8000], moveBuf[20];
18312
18313         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18314         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18315         nrMoves = savedLast[storedGames] - currentMove;
18316         if(annotate) {
18317                 int cnt = 10;
18318                 if(!WhiteOnMove(currentMove))
18319                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18320                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18321                 for(i=currentMove; i<forwardMostMove; i++) {
18322                         if(WhiteOnMove(i))
18323                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18324                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18325                         strcat(buf, moveBuf);
18326                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18327                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18328                 }
18329                 strcat(buf, ")");
18330         }
18331         for(i=1; i<=nrMoves; i++) { // copy last variation back
18332             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18333             for(j=0; j<MOVE_LEN; j++)
18334                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18335             for(j=0; j<2*MOVE_LEN; j++)
18336                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18337             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18338             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18339             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18340             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18341             commentList[currentMove+i] = commentList[framePtr+i];
18342             commentList[framePtr+i] = NULL;
18343         }
18344         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18345         framePtr = savedFramePtr[storedGames];
18346         gameInfo.result = savedResult[storedGames];
18347         if(gameInfo.resultDetails != NULL) {
18348             free(gameInfo.resultDetails);
18349       }
18350         gameInfo.resultDetails = savedDetails[storedGames];
18351         forwardMostMove = currentMove + nrMoves;
18352 }
18353
18354 Boolean
18355 PopTail (Boolean annotate)
18356 {
18357         if(appData.icsActive) return FALSE; // only in local mode
18358         if(!storedGames) return FALSE; // sanity
18359         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18360
18361         PopInner(annotate);
18362         if(currentMove < forwardMostMove) ForwardEvent(); else
18363         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18364
18365         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18366         return TRUE;
18367 }
18368
18369 void
18370 CleanupTail ()
18371 {       // remove all shelved variations
18372         int i;
18373         for(i=0; i<storedGames; i++) {
18374             if(savedDetails[i])
18375                 free(savedDetails[i]);
18376             savedDetails[i] = NULL;
18377         }
18378         for(i=framePtr; i<MAX_MOVES; i++) {
18379                 if(commentList[i]) free(commentList[i]);
18380                 commentList[i] = NULL;
18381         }
18382         framePtr = MAX_MOVES-1;
18383         storedGames = 0;
18384 }
18385
18386 void
18387 LoadVariation (int index, char *text)
18388 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18389         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18390         int level = 0, move;
18391
18392         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18393         // first find outermost bracketing variation
18394         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18395             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18396                 if(*p == '{') wait = '}'; else
18397                 if(*p == '[') wait = ']'; else
18398                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18399                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18400             }
18401             if(*p == wait) wait = NULLCHAR; // closing ]} found
18402             p++;
18403         }
18404         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18405         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18406         end[1] = NULLCHAR; // clip off comment beyond variation
18407         ToNrEvent(currentMove-1);
18408         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18409         // kludge: use ParsePV() to append variation to game
18410         move = currentMove;
18411         ParsePV(start, TRUE, TRUE);
18412         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18413         ClearPremoveHighlights();
18414         CommentPopDown();
18415         ToNrEvent(currentMove+1);
18416 }
18417
18418 void
18419 LoadTheme ()
18420 {
18421     char *p, *q, buf[MSG_SIZ];
18422     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18423         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18424         ParseArgsFromString(buf);
18425         ActivateTheme(TRUE); // also redo colors
18426         return;
18427     }
18428     p = nickName;
18429     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18430     {
18431         int len;
18432         q = appData.themeNames;
18433         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18434       if(appData.useBitmaps) {
18435         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18436                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18437                 appData.liteBackTextureMode,
18438                 appData.darkBackTextureMode );
18439       } else {
18440         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18441                 Col2Text(2),   // lightSquareColor
18442                 Col2Text(3) ); // darkSquareColor
18443       }
18444       if(appData.useBorder) {
18445         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18446                 appData.border);
18447       } else {
18448         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18449       }
18450       if(appData.useFont) {
18451         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18452                 appData.renderPiecesWithFont,
18453                 appData.fontToPieceTable,
18454                 Col2Text(9),    // appData.fontBackColorWhite
18455                 Col2Text(10) ); // appData.fontForeColorBlack
18456       } else {
18457         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18458                 appData.pieceDirectory);
18459         if(!appData.pieceDirectory[0])
18460           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18461                 Col2Text(0),   // whitePieceColor
18462                 Col2Text(1) ); // blackPieceColor
18463       }
18464       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18465                 Col2Text(4),   // highlightSquareColor
18466                 Col2Text(5) ); // premoveHighlightColor
18467         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18468         if(insert != q) insert[-1] = NULLCHAR;
18469         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18470         if(q)   free(q);
18471     }
18472     ActivateTheme(FALSE);
18473 }